I have written functions that have path parameters. I'm not sure if I implemented it correctly. Is there a standardized or better way of doing this in Powershell?
function Get-PathExample {
param(
[Parameter(Position=0,mandatory=$true,HelpMessage="Profilepath e.g. C:\Users or \\computername\c$\users\")]
[string]$ProfilePath,
[Parameter(Position=1,mandatory=$true,HelpMessage="SubPath e.g. AppData\Roaming\")]
[string]$SubPath
)
<#
code...
#>
}
As mentioned in the comments, your basic approach is correct - accept path stems as string arguments, then resolve and validate inside the function.
You can add the most basic level of input validation to the param
block itself - like validating that the $ProfilePath
only resolves to directories for example:
param(
[Parameter(...)]
# Any path that doesn't exclusively resolve to 1 or more
# directories will now cause a parameter validation error
[ValidateScript({ Test-Path -Path $_ -PathType Container })]
[string]$ProfilePath,
...
)
Then inside the function you can perform more domain-specific validation - like testing that the resolved paths are indeed filesystem paths:
foreach ($resolvedPath in Resolve-Path -Path $ProfilePath) {
if ($resolvedPath.Provider.Name -ne 'FileSystem') {
Write-Warning "Resolved non-filesystem item at $($resolvedPath.Path), skipping entry"
continue
}
# work with $resolvedPath.Path here (or store it for later)
}
In the most basic scenarios - where you don't need to care about the paths themselves, but just want to resolve 1 or more provider items from a caller-supplied path - a better option is to mimic the parameter surface of the corresponding provider cmdlet (like Get-Item
) and then just offload all the heavy lifting to that command instead.
To do that, use the following to generate the source code for a new parameter blocks:
# locate the provider cmdlet we want to mimic
$targetCommand = Get-Command Get-Item
# create CommandMetadata object from command info
$commandMetadata = [System.Management.Automation.CommandMetadata]::new($targetCommand)
# generate new proxy param block from Get-Item
$paramBlock = [System.Management.Automation.ProxyCommand]::GetParamBlock($commandMetadata)
On Windows you can copy the resulting code to your clipboard with $paramBlock |Set-ClipBoard
The result will look like this:
[Parameter(ParameterSetName='Path', Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${Path},
[Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${LiteralPath},
[string]
${Filter},
[string[]]
${Include},
[string[]]
${Exclude},
[switch]
${Force},
[Parameter(ValueFromPipelineByPropertyName=$true)]
[pscredential]
[System.Management.Automation.CredentialAttribute()]
${Credential}
Now manually remove the parameter definitions related to features you don't need, and you might end up with something like:
[Parameter(ParameterSetName='Path', Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${Path},
[Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${LiteralPath},
[switch]
${Force}
Replace the contents of your param block with the above and add a [CmdletBinding()]
decorator to set the default parameter set to the $Path
one:
[CmdletBinding(DefaultParameterSetName = 'Path')]
param(
<# generated parameter definitions from above go here #>
)
... at which point you can just pass the caller's parameter arguments off to Get-Item
as-is:
foreach ($item in Get-Item @PSBoundParameters) {
# work with $item
}
Now the caller can supply either wildcard paths or exact paths as they see fit, and Get-Item
takes care of the rest