Check multiple path with Test-path - powershell

In my function i have 3 no mandatory and no positional parameters Folder1, Folder2 and Folder3, before running my function i would like to check for each selected parameter if folder exist. It must be something dynamic because user can specify randomly one or two or all tree folders.
Thanks in advance.

You can pass an array to Test-Path
Test-Path c:, d:, e:
True
True
True
EDIT
So I believe from your comment that you have something like this:
$mypaths = "c:","d:",$null
Test-Path $mypaths
Which issues the following error:
Test-Path : Cannot bind argument to parameter 'Path' because it is null.
Then you can try this:
$mypaths = "c:","d:",$null
$mypaths | Foreach { if ($_){Test-Path $_}}
Which results in only two valid paths.
So you could consider checking the returned values:
$valid = $mypaths | Foreach { if ($_){Test-Path $_}}
write-host "Variable `$mypaths contains $($mypaths.count) paths, and $($valid.count) were found valid"
Which outputs:
Variable $mypaths contains 3 paths, and 2 were found valid

Related

Why does pipeline parameter cause error when combined with PSDefaultParameterValues?

My powershell function should accept a list of valid paths of mixed files and/or directories either as a named parameter or via pipeline, filter for files that match a pattern, and return the list of files.
$Paths = 'C:\MyFolder\','C:\MyFile'
This works: Get-Files -Paths $Paths This doesn't: $Paths | Get-Files
$PSDefaultParameterValues = #{
"Get-Files:Paths" = ( Get-Location ).Path
}
[regex]$DateRegex = '(20\d{2})([0-1]\d)([0-3]\d)'
[regex]$FileNameRegex = '^term2-3\.7_' + $DateRegex + '\.csv$'
Function Get-Files {
[CmdletBinding()]
[OutputType([System.IO.FileInfo[]])]
[OutputType([System.IO.FileInfo])]
param (
[Parameter(
Mandatory = $false, # Should default to $cwd provided by $PSDefaultParameterValues
ValueFromPipeline,
HelpMessage = "Enter filesystem paths that point either to files directly or to directories holding them."
)]
[String[]]$Paths
)
begin {
[System.IO.FileInfo[]]$FileInfos = #()
[System.IO.FileInfo[]]$SelectedFileInfos = #()
}
process { foreach ($Path in $Paths) {
Switch ($Path) {
{ Test-Path -Path $Path -PathType leaf } {
$FileInfos += (Get-Item $Path)
}
{ Test-Path -Path $Path -PathType container } {
foreach ($Child in (Get-ChildItem $Path -File)) {
$FileInfos += $Child
}
}
Default {
Write-Warning -Message "Path not found: $Path"
continue
}
}
$SelectedFileInfos += $FileInfos | Where-Object { $_.Name -match $FileNameRegex }
$FileInfos.Clear()
} }
end {
Return $SelectedFileInfos | Get-Unique
}
}
I found that both versions work if I remove the default parameter value. Why?
Why does passing a parameter via the pipeline cause an error when that parameter has a default defined in PSDefaultParameterValues, and is there a way to work around this?
Mathias R. Jessen provided the crucial pointer in a comment:
A parameter that is bound via an entry in the dictionary stored in the $PSDefaultParameterValues preference variable is bound before it is potentially bound via the pipeline, just like passing a parameter value explicitly, as an argument would.
Once a given parameter is bound that way, it cannot be bound again via the pipeline, causing an error:
The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
As you can see, the specific problem at hand - a parameter already being bound - is unfortunately not covered by this message. The unspoken part is that once a given parameter has been bound by argument (possibly via $PSDefaultParameterValues), it is removed from the set of candidate pipeline-binding parameters the input could bind to, and if there are no candidates remaining, the error occurs.
The only way to override a $PSDefaultParameterValue preset value is to use an (explicit) argument.
This comment on a related GitHub issue provides details on the order of parameter binding.
A simplified way to reproduce the problem:
& {
# Preset the -Path parameter for Get-Item
# In any later Get-Item calls that do not use -Path explicitly, this
# is the same as calling Get-Item -Path /
$PSDefaultParameterValues = #{ 'Get-Item:Path' = '/' }
# Trying to bind the -Path parameter *via the pipeline* now fails,
# because it has already been bound via $PSDefaultParameterValues.
# Even without the $PSDefaultParameterValues entry in the picture,
# you'd get the same error with: '.' | Get-Item -Path /
'.' | Get-Item
# By contrast, using an *argument* allows you to override the preset.
Get-Item -Path .
}
What's happening here?!
This is a timing issue.
PowerShell attempts to bind and process parameter arguments in roughly* the following order:
Explicitly named parameter arguments are bound (eg. -Param $value)
Positional arguments are bound (abc in Write-Host abc)
Default parameter values are applied for any parameter that wasn't processed during the previous two steps - note that applicable $PSDefaultParameterValues always take precedence over defaults defined in the parameter block
Resolve parameter set, validate all mandatory parameters have values (this only fails if there are no upstream command in the pipeline)
Invoke the begin {} blocks on all commands in the pipeline
For any commands downstream in a pipeline: wait for input and then start binding it to the most appropriate parameter that hasn't been handled in previous steps, and invoke process {} blocks on all commands in the pipeline
As you can see, the value you assign to $PSDefaultParameterValues takes effect in step 3 - long before PowerShell even has a chance to start binding the piped string values to -Paths, in step 6.
*) this is a gross over-simplification, but the point remains: default parameter values must have been handled before pipeline binding starts.
How to work around it?
Given the procedure described above, we should be able to work around this behavior by explicitly naming the parameter we want to bind the pipeline input to.
But how do you combine -Paths with pipeline input?
By supplying a delay-bind script block (or a "pipeline-bound parameter expression" as they're sometimes called):
$Paths | Get-Files -Paths { $_ }
This will cause PowerShell to recognize -Paths during step 1 above - at which point the default value assignment is skipped.
Once the command starts receiving input, it transforms the input value and binds the resulting value to -Paths, by executing the { $_ } block - since we just output the item as-is, the effect is the exact same as when the pipeline input is bound implicitly.
Digging deeper
If you want to learn more about what happens "behind the curtain", the best tool available is Trace-Command:
$PSDefaultParameterValues['Get-Files:Paths'] = $PWD
Trace-Command -Expression { $Paths |Get-Files } -Name ParameterBinding -PSHost
I should mention that the ParameterBinding tracer is very verbose - which is great for surmising what's going on - but the output can be a bit overwhelming, in which case you might want to replace the -PSHost parameter with -PSPath .\path\to\output.txt to write the trace output to a file

PowerShell Test-Path is returning false always

I have two below arrays in powershell
$obj= ('Sales','Finance','config.ini')
$objPath= ('D:\Project','D:\Element','D:\Project')
now $obj can be a folder name or a file name
$objPath is the path where the respective positional $obj will reside
I want to check if that folder or file exist in the respective $objPath
my code:
foreach($element in $obj){
foreach($elementpath in $objPath){
Test-Path $element+'\'+$elementpath
}
}
But it is returning false everytime. Can anyone please suggest where I am doing wrong
I think you've written the statement backwards. When you run it'll check for paths like:
Sales\D:\Project
Sales\D:\Element
Sales\D:\Project
Finance\D:\Project
Finance\D:\Element
Finance\D:\Project
config.ini\D:\Project
config.ini\D:\Element
config.ini\D:\Project
That obviously doesn't look right. You can try a minor re-write like:
Along with reversing the variable references you may want to entertain using Join-Path (as a best practice) like below:
foreach($element in $obj){
foreach($elementpath in $objPath){
Test-Path (Join-Path $elementpath $element)
}
}
It will work with string concatenation like below:
foreach($element in $obj){
foreach($elementpath in $objPath){
Test-Path ($elementpath + '\' + $element)
}
}
Per one of the comments string expansion will also work:
foreach($element in $obj){
foreach($elementpath in $objPath){
Test-Path "$elementpath\$element"
}
}

Type of Variable not as expected in Powershell

This is a question of understanding.
I construct a Directory Structure using a string Variable $path. I append the name of the Directory I want to create, this works as expected. When the path is completed I need the complete Directory as System.IO.DirectoryInfo but assigning the Path in this way $Path = Get-Item $Path results in a string type.
$path = "c:\dir1"
If(-Not (Test-Path $path))New-Item -ItemType Directory -Path $path
$path = $path + "\dir2"
If(-Not (Test-Path $path))New-Item -ItemType Directory -Path $path
# Assigned to same variable
$path = Get-Item $path
echo $path.GetType() # = string
# assigned to different variable
$p_a_t_h = Get-Item $path
echo $p_a_t_h.GetType() # = System.IO.DirectoryInfo
# solution but not understood the behavior
[System.IO.DirectoryInfo]$path = Get-Item $path
echo $path.GetType() # = System.IO.DirectoryInfo
It took hours to find out this behavior and I couldn't find any documentation why this is - maybe because I don't know what to search for.
It is clear, that for appending something to a variable, the type of the variable is relevant, but a $path = ... is a "new" assignement and should have the type of the assigned value - at least in my eyes. In the languages I used so far a variable becomes the type of its value and is not converted to a type the variable had earlier or I define the type of a variable and get an error if assigned with wrong type.
Where is the error in my logic?
I think that somewhere in your code you did a left-side cast (on the variable, not the value) to [String], just like you did later in your sample with [System.IO.DirectoryInfo]$path.
The most common way that this happens: Parameters.
Is this taken from a function? Like:
function Invoke-MyThing {
param([String]$Path)
}
Why that matters
When you put the type on the variable, all values assigned to that variable receive that cast.
[String]$Value = 'Hello'
$Value.GetType()
$Value = 3.141
$Value.GetType()
Casting the value only affects that one value:
$V2 = 5
$V2.GetType()
$V2 = [String]9
$V2.GetType()
$V2 = 45
$V2.GetType()
So, remove your previous variable-side cast, or if it's a parameter, just use a different local variable.
Better yet, if it's a parameter, you could make it of type [System.IO.DirectoryInfo] instead.. then it would accept that directly, or even accept a string. You just have to rework your code a little bit to deal with it.

How can I determine whether a path is filesystem or registry?

I'm making a function which can take a argument which can be either filesystem or registry path. e.g.
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
'C:\ProgramData\Microsoft\Windows'
I don't want to divide them by named argument but their interfaces aren't compatible. How can I classify them?
You can use this method ($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath) to do that. It have overload, which allows you to extract PowerShell provider and PowerShell drive info from path.
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run',
'C:\ProgramData\Microsoft\Windows' |
ForEach-Object { $Provider = $null } {
[void]$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($_, [ref]$Provider, [ref]$null)
$Provider
}
These commands tell you the types:
(Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run').GetType().Name # returns RegistryKey
(Get-Item 'C:\ProgramData\Microsoft\Windows').GetType().Name # returns DirectoryInfo
...or another way of getting the same info...
$item = Get-Item 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
$item.GetType().Name # returns RegistryKey
$item = Get-Item 'C:\ProgramData\Microsoft\Windows'
$item.GetType().Name # returns DirectoryInfo

Get actual path from path with wildcard

When using Test-Path in an if statement, I am looking to get the path that the if statement succeeds with.
For example, these files exist in C:
C:\Test6_1_15.txt
C:\Test6_2_15.txt
C:\Test6_3_15.txt
C:\Test6_4_15.txt
what do I do in the "then" branch?
$Path = "C:\Test6_*_15.txt"
if (Test-Path $Path)
{
# if test passes because there are 4 files that fit the test, but I want to be
# able to output the file that made the if statement succeed.
}
Sounds like you want Resolve-Path:
if(($Paths = #(Resolve-Path "C:\Test6_*_15.txt"))){
foreach($file in $Paths){
# do stuff
}
} else {
# Resolve-Path was unable to resolve "C:\Test6_*_15.txt" to anything
}
You can do get-item $path, that will return actual file name(s) in its result.
You will not get that with Test-Path. Test-Path returns a boolean value(s) representing the presence of the path(s) passed. Looking at the description from TechNet
It returns TRUE ($true) if all elements exist and FALSE ($false) if any are missing
If you just want the actual filenames that match then use Get-Item as it supports standard wildcards. You can get information from the System.IO.FileInfo objects that Get-Item returns.