Test-Path fails but doesn't do else - powershell

I am trying to test several paths and if any fail, create the paths.
$subFolders = "$sortByProject$projectName\Originals", "$sortByProject$projectName\Pulled", "$sortByProject$projectName\Retouched", "$sortByProject$projectName\Uploaded"
if(!(Test-Path -Path "$sortByProject$projectName", "$subFolders")){
New-Item -ItemType Directory -Path "$sortByProject$projectName", "$subFolders"
}
The test finds "$sortByProject$projectName" exists but "$subFolders" fails, so output appears like:
True
False
I would think since a false is returned, it would move to the new-item command and build all four requested folders (.\originals, .\pulled, .\retouched and .\uploaded). All the variable are properly named and return the desired path when called independently. I think the mess up is because there are multiple items assigned to $subfolders, but I don't understand why.
I also think this code is sloppy and would love to learn a better way to do a multiple path test and create any of the missing paths.

You don't need to explicitly test the existence of your directory paths:
Just use New-Item's -Force switch in combination with -ItemType Directory, which will create the specified directories on demand - including parent directories - while leaving existing directories alone (and returning directory-info objects describing the preexisting / newly created directories).
New-Item -Force -ItemType Directory -Path $subFolders
As for what you tried:
Your Test-Path command returns an array of Booleans, and PowerShell considers any 2+-element array $true in a Boolean context - irrespective of its element values; see the bottom section of this answer for a summary of the PowerShell's to-Boolean coercion rules.
-Path "$sortByProject$projectName", "$subFolders" has two problems:
Not only is double-quoting variable values passed as command arguments never necessary in PowerShell, in the case of the array variable $subFolders using "$subFolders" turns the array into a single string containing the (stringified) elements separated with spaces.
If you correct this immediate problem - by using
-Path $sortByProject$projectName, $subFolders - another problem is revealed: you aren then in effect passing a jagged array, whose first element is a string, and whose second element is a nested array, which would break the invocation.
Correcting this problem requires a perhaps non-obvious approach: you must use + rather than , in order to construct a flat array, which in turn requires that the LHS already be an array, which requires using the unary form of ,, the array constructor operator, and switching to an expression, enclosed in (...), the grouping operator:
-Path (, $sortByProject$projectName + $subFolders)

$subfolders | where {-not (Test-Path $_)} | Foreach-Object { New-Item -ItemType Directory -Path $_ }

Related

How to set PowerShell PSDefaultParameterValues conditionally based on other parameters

Without going much into details on why am I even trying this out, is it possible to set PSDefaultParameterValues conditionally based on other parameter values?
Let's say I would like to set -Force if ItemType is Directory in New-Item call.
$PSDefaultParameterValues = #{ "New-Item:Force" = {
# TODO: if Itemtype is Directory, return $true
# else return default: false
return $false
}
}
New-Item -ItemType Directory
Problem is, that I can get the parameters used in $args but I do not have access to their values.
As you've observed, the argument passed to your script block via the automatic $args variable contains the names of the bound parameters in the New-Item call at hand, but lacks their values.
This looks like an oversight, which GitHub proposal #16011 aims to correct.
The following workaround isn't foolproof, but may suffice in practice:
$PSDefaultParameterValues = #{
'New-Item:Force' = {
($false, $true)[
$args.BoundParameters.Contains('ItemType') -and
(Get-PSCallStack)[1].Position.Text -match '\bDirectory\b'
]
}
}
You could tweak the regex to be stricter, but note that PowerShell's elastic syntax and parameter aliases make it hard to match a parameter name reliably; e.g., -Type Directory, -it Directory and -ty Directory are all acceptable variations of -ItemType Directory.
A caveat is that this won't work if you pass the Directory argument to -ItemType in New-Item calls via a variable; e.g., $type='Directory'; New-Item -ItemType $type ... would not be recognized by the script block. Handling that case would require substantially more work.
Note:
The parent call-stack entry, which you can obtain as the 2nd element of the call-stack array returned by Get-PSCallStack, contains the raw command text of the New-Item call at hand (in property .Position.Text), which the solution above examines.
However, since it is the raw command text, it doesn't include the expanded argument values that are ultimately seen by the command; that is, what variable references and expression evaluate to isn't directly available.
You could perform your own expansion, assuming you've reliably identified the variable reference / subexpression of interest, but note that, at least in principle, evaluating a subexpression can have side effects (and possibly also take a long time to execute), so effectively executing it twice may be undesirable.

Removing the front part of a string based on an specific character. (\)

I first create my array with a list of files in a directory (and subdirectories) using the Cmdlet Get-ChildItem, and store them in a variable
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak).FullName
I echo the variable ($PSVariable), this is my output (as desired):
F:\SQL_Backups\INTRAPORTAL\StoreDevelopment\StoreDevelopment_backup_2021_02_11_003002_3930170.bak
F:\SQL_Backups\INTRAPORTAL\StoreDevelopment\StoreDevelopment_backup_2021_02_12_003002_4780885.bak
F:\SQL_Backups\JDASQL\DEVMOD\DEVMOD_backup_2021_02_10_190002_5130923.bak
F:\SQL_Backups\JDASQL\DEVMOD\DEVMOD_backup_2021_02_11_190003_7621021.bak
Goal:
I need to remove the directory path from each array entries so it only contains the file name that will be stored in a temporary variable within a foreach loop:
StoreDevelopment_backup_2021_02_11_003002_3930170.bak
StoreDevelopment_backup_2021_02_12_003002_4780885.bak
DEVMOD_backup_2021_02_10_190002_5130923.bak
DEVMOD_backup_2021_02_11_190003_7621021.bak
Some will recommend simply using (.Name) in the Get-ChildItem command, but I need the array to have both the path and filename (FullName) as the array's contents are being used for other parts of the function. I'm a novice when it comes to regular expressions and I can't seem to get the results in the goal section. I've even tried using trim() methods, but no luck. Any recommendations would greatly be appreciated. Thank you.
Expanding on what #AdminOfThings recommended, you are making more work for yourself than you need. PowerShell is an object based scripting language, so to succeed you should use its full POWER.
The approach you're taking now is to take only one property from this useful object and then find you need to start slicing and dicing it in order to make it work.
There's an easier way. We love easy here, and the easy way to do this is to take the full object and then pick and chose its properties where it makes sense, like this:
$i = 0
#changed to remove the .FullName at then end
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak)
ForEach ($item in $psVariable){
$i++
Write-host "Processing [$($item.Name)], item number $i of $($psVariable.Count)"
Copy-item -Path $item.FullName -Destination C:\temp -WhatIf
}
It gives you meaningful output and then you have the full selection of properties to work with.
The one that makes the most sense to use is just .Name as you reference above. But then you still have .FullName, which includes the qualified path as well.
If you want to see the full selection of properties, try this:
$PsVariable[0] | Format-list *
Offered only as an inferior option to that of FoxDeploy's you can also use Split-Path to get the filename from a path
$PSVariable = (Get-ChildItem -Path "F:\SQL_Backups" -Recurse *.bak).FullName
$PSVariable | Split-Path -Leaf

Powershell Script that recursively searches for specific file and containing directory and copy it to another location

I'm attempting to write a powershell script that will search recursively for a file and copy it to another location on a local drive with the date appended to it.
However that file could be in multiple different directories. (ex. c:\users\default\Bookmark.txt, c:\users\profile1\Bookmark.txt, c:\users\profile2\Bookmark.txt...etc.)
To distinguish between the different directories I was thinking of appending the directory name containing the file to the filename along with the date. (ex. filename-directoryName-date)
Here is what I have so far:
get-childitem -path "$env:userprofile\AppData\Local\Microsoft\Edge\User Data" -filter Bookmarks -Recurse -ErrorAction SilentlyContinue -Force | copy-item -Destination $env:userprofile\Bookmarks-$(get-date -UFormat %d-%m-%Y)
This works if it only finds 1 copy of the Bookmarks file and it only appends the date.
To figure out the names of the containing folders I used this command.
(get-childitem -path "$env:userprofile\AppData\Local\Microsoft\Edge\User Data" -filter Bookmarks -Recurse -ErrorAction SilentlyContinue -Force).Directory.Name
I need to somehow put this two together and so it outputs :
c:(whateverlocation)\Bookmark-DirectoryName-Date
I hope I'm making sense.
Dan,
When you use the Get-ChildItem command (alias gci) it will return to you an array of DirectoryInfo and FileInfo objects, one for each item it finds. If you are looking for files called "Bookmarks" (or bookmark.txt...can't tell from your examples which one you're looking for) then you can use the following command to get you a list of all of them:
[array]$FileList = gci -Recurse -File -Filter "Bookmarks"
The [array] designation is necessary to be sure the object is an array regardless of how many items are returned. The filter can have wildcards if you don't know the exact filename. What that leaves me with is an array object named $FileList containing all of the information about the files. You can read all about the properties and methods available in these objects at this Microsoft help page.
For this task, we'll need the .FullName property, which tells you the full path & name of each item (which can be used as your source) and the .BaseName & .Extension properties, which give you the filename and extension respectively. Using these properties, you can copy each of the files you find to a destination. Putting it all together, you get something like this:
$SourceFolder = "$($env:userprofile)\AppData\Local\Microsoft\Edge\User Data"
$DestFolder = '' #Path to Destination Folder
[array]$FileList = gci -Path $SourceFolder -Recurse -File -Filter "Bookmarks"
ForEach ($F in $FileList) {
Copy-Item $F.FullName (Join-Path $DestFolder ($F.Name + 'stuff to add to end of filename' + $F.Extension))
}
In this case, since the files are named 'Bookmarks', the $F.Extension should be blank. If using a more traditional filename, the pattern above will help you fit your changes in between the filename and the extension.
Hope this helps you out. If it does, please accept the answer using the check mark on the left.

How to -Include using a variable with multiple criteria

I am trying to copy files with names that fulfills a given set of criteria from one folder to another. From this answer, I found that the -Include statement could be used for this.
My issue is that I need the criteria to be user-specified (through a variable), with the possibility of containing multiple criteria. That is, I try to do the following:
# THIS WORKS FINE
$includeFiles = "*tests.jar"
Copy-Item "from/path" "to/path" -Include $includeFiles
# THIS RETURNS NOTHING
$includeFiles = "*tests.jar, other.jar"
Copy-Item "from/path" "to/path" -Include $includeFiles
How do I work around this issue? Is it somehow possible to "destring" the variable, use another syntax or similar?
# THIS WORKS FINE
$copyPattern = "*tests.jar", "other.jar"
Copy-Item "from/path" "to/path" -Include $copyPattern
The point is that the argument to -Include is an array of strings, each string is a pattern to include. The way you wrote it would require a comma and space in the file name. So, unless you have such files, nothing would be included.
If you are positive that the pattern will never include commas and you'd rather just have a single string, you can of course convert it to an array:
$copyPatterns = "*tests.jar, other.jar" -split ', '
Note that parsing works differently in arguments to commands, which is why the following does work as well:
Copy-Item from/path to/path -Include *tests.jar,other.jar

In function repeat an action for each entered parameter

My main script run once gci on a specified drive via -path parameter , then it does multiple different tables from this output. Here below is a part of my script which does a specific table from an directory specified via -folder parameter, for example :
my-globalfunction -path d:\ -folder d:\folder
It work fine, but only for one entered folder path, the goal of this script is that user can enter multiple folders path and get a tables for each entered -folder parameter value, like this :
This clause in your Where-Object would be the issue:
$_.FullName.StartsWith($folder, [System.StringComparison]::OrdinalIgnoreCase)
The array of folders passed are most likely being cast as one long string which would never match. I had a regex solution posted but remembered a simpler way after looking at what your logic was trying to do.
Simpler Way
Even easier way is to put this information right into Get-ChildItem since it accepts string arrays for -Path. This way I don't think you even need to have 2 parameters since you never again use the results from $fol anyway. Based on the assumption that you were looking for all subfolders of $folder
$gdfolders = Get-ChildItem -Path $folder -Recurse -Force | Where-Object{$_.psiscontainer}
That would return all subfolders of the paths provided. If you have PowerShell 3.0 or higher this would even be easier.
$gdfolders = Get-ChildItem -Path $folder -Recurse -Force -Directory
Update from comments
The code you have displayed is incomplete which is what lead me to the solution that you see above. If you do use the variable $fol somewhere else that you do not show lets go back to my earlier regex solution which would work better in place with what you already have.
$regex = "^($(($folder | ForEach-Object{[regex]::Escape($_)}) -join "|")).+"
....
$gdfolders = $fol | Where-Object{($_.Attributes -eq "Directory") -and ($_.FullName -match $regex)}
What this will do is build a regex compare string with what I will assume is the logic of locate folders that begin with either of paths passed.
Using your example input of "d:\folder1", "d:\folder2" the variable $regex would work out to ^(d:\\folder1|d:\\folder2). The proper characters, like \, are escaped automatically by the static method [regex]::Escape which is applied to each element. We then use -join to place a pipe which, in this regex capture group means match whats on the left OR on the right. For completeness sake we state that the match has to occur at the beginning of the path with the caret ^ although this is most likely redundant. It would match paths that start with either "d:\folder1" or "d:\folder2". At the end of the regex string we have .+ which means match 1 to more characters. This should ensure we dont match the actual folder "d:\folder1" but meerly its children
Side Note
The quotes in the line with ’Size (MB)’ are not the proper ones which are '. If you have issues around that code consider changing the quotes.