If I were writing "idiomatic" PowerShell, what parameter types would I use for functions that work with files or directories?
For example, I made a module that contains this function:
$bookmarks = @{}
$bookmarkKeys = @{}
function Set-Bookmark {
param(
[Parameter(Mandatory = $true)]
[string] $Id,
[System.Management.Automation.DirectoryInfo] $Path = (Get-Location))
$script:bookmarks[$Id] = $Path
$script:bookmarkKeys[$Path.Path] = $Id
}
The trouble is, the following doesn't work:
PS>Set-Bookmark -Id Code -Path C:\Code
Set-Bookmark : Cannot process argument transformation on parameter 'Path'.
Cannot convert the "C:\Code" value of type "System.String" to type
"System.Management.Automation.PathInfo".
At line:1 char:29
+ Set-Bookmark -Id Code -Path C:\Code
+ ~~~~~~~
+ CategoryInfo : InvalidData: (:) [Set-Bookmark], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Set-Bookmark
Weirdly, neither does this (but for a slightly different reason):
PS>Set-Bookmark -Id Code -Path (gi C:\Code)
Set-Bookmark : Cannot process argument transformation on parameter 'Path'.
Cannot convert the "C:\Code" value of type "System.IO.DirectoryInfo" to type
"System.Management.Automation.PathInfo".
At line:1 char:29
+ Set-Bookmark -Id Code -Path (gi C:\Code)
+ ~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Set-Bookmark], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Set-Bookmark
So should I actually define my parameters as strings (i.e. the lowest common denominator) and then try to cast/parse them into the more meaningful types? This doesn't sound right, because it's a shame if I'm working with the results of things like Get-Item
and they get brought down to string
for passing-in, only for that function to parse back into the higher-level type again.
I think you are wanting to use the type [IO.DirectoryInfo]
. This works fine for me:
function t {
param(
[IO.DirectoryInfo] $d
)
"You passed $d"
}
t (Get-Item "C:\Windows")
For files, you can use [IO.FileInfo]
, although this won't support wildcards.
From a usability perspective, I have always found it easier to declare file and directory parameters as type string
. What makes PowerShell so flexible is the ability to combine functionality from vastly different sources. I don't know who your intended audience is, but suppose a user wanted to call Set-Bookmark
for entries in a text file. It would be much simpler to call the function if it accepted a string:
$id = 0
Get-Content 'bookmarks.txt' | For-EachObject { Set-Bookmark -id $i++ -Path $_ }
There's also the fact that you might not always be using the file provider, so assuming that your Path always represents a filepath will limit the usefulness of this function. If, say, you were using the Microsoft SQL provider, you might want your function to provide bookmarks to paths in the SQL provider. (Your second solution using Test-Path
would work.) Again, I don't know your target audience, but it's worth considering.
You used the type System.Management.Automation.PathInfo
in your actual code, not System.Management.Automation.DirectoryInfo
, otherwise you'd be getting a TypeNotFound
exception. There is no DirectoryInfo
class in the System.Management.Automation
namespace. However, as @Bill_Stewart already mentioned, folder objects are of the type System.IO.DirectoryInfo
anyway. The PathInfo
class is for PowerShell paths, which aren't necessarily filesystem paths (think for instance cert or registry provider).
If you want a parameter that can take both files (System.IO.FileInfo
) and folders (System.IO.DirectoryInfo
), you need to make the parameter type either a common base class of both types (e.g. System.IO.FileSystemInfo
):
function Set-Bookmark {
Param(
[Parameter(Mandatory=$true)]
[string] $Id,
[Parameter(Mandatory=$false)]
[IO.FileSystemInfo]$Path = (Get-Location)
)
...
}
or System.String
(because both file and folder objects can be cast to string and vice versa):
function Set-Bookmark {
Param(
[Parameter(Mandatory=$true)]
[string] $Id,
[Parameter(Mandatory=$false)]
[ValidateScript({Test-Path -LiteralPath $_})]
[string]$Path = (Get-Location)
)
...
}
Note that when using String
as the type of the -Path
parameter you need to validate the parameter yourself to prevent passing arguments that aren't valid paths. Also note that the parameter won't accept path strings when using IO.FileSystemInfo
as the parameter type (i.e. Set-Bookmark -Id 42 -Path "C:\some\path"
would fail).
Bottom line:
The idiomatic parameter type depends on what input you want the function to accept. If it's file and folder objects use System.IO.FileSystemInfo
. If it's paths use use System.String
, validate the input, and convert the path string (back) to a file/folder object as required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With