Closed chadbaldwin closed 2 months ago
I agree, it's really not obvious at all from the documentation that many PowerShell cmdlets will fail to properly parse file paths containing special characters, unless the developer explicitly adds the -LiteralPath
parameter. In fact, I recently submitted a PR to fix an issue in Microsoft/Analysis-Services where a function was invoking Set-Content
with an implicit -Path
parameter rather than an explicit -LiteralPath
, and failing on file paths containing square brackets as a result. I think this default behavior, and all of its potential downsides, should be plainly and precisely documented in the PowerShell docs.
It is already documented by the fact that -Path
can accept pipeline input ByPropertyName or ByValue, and -LiteralPath
can accept pipeline input ByPropertyName only. Pipeline Parameter Binding resolves which parameter that the input is bound to. When you pipe a string, it can only be bound to -Path
. When you pipe a FileInfo
object, it binds to -LiteralPath
ByPropertyName.
The point is that this isn't undocumented handling of FileInfo
objects, it is standard parameter binding.
You can see this using Trace-Command
. This example shows binding of the string input to -Path
.
PS> Trace-Command -Name ParameterBinding -Expression {$filename | Get-Item } -PSHost
DEBUG: 2024-05-15 08:58:01.9189 ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-Item]
DEBUG: 2024-05-15 08:58:01.9191 ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Get-Item]
DEBUG: 2024-05-15 08:58:01.9193 ParameterBinding Information: 0 : BIND cmd line args to DYNAMIC parameters.
DEBUG: 2024-05-15 08:58:01.9194 ParameterBinding Information: 0 : DYNAMIC parameter object: [Microsoft.PowerShell.Commands.FileSystemProviderGetItemDynamicParameters]
DEBUG: 2024-05-15 08:58:01.9196 ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-Item]
DEBUG: 2024-05-15 08:58:01.9197 ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: 2024-05-15 08:58:01.9199 ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Get-Item]
DEBUG: 2024-05-15 08:58:01.9201 ParameterBinding Information: 0 : PIPELINE object TYPE = [System.String]
DEBUG: 2024-05-15 08:58:01.9202 ParameterBinding Information: 0 : RESTORING pipeline parameter's original values
DEBUG: 2024-05-15 08:58:01.9203 ParameterBinding Information: 0 : Parameter [Path] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: 2024-05-15 08:58:01.9205 ParameterBinding Information: 0 : BIND arg [[foobar].xyz] to parameter [Path]
DEBUG: 2024-05-15 08:58:01.9206 ParameterBinding Information: 0 : Binding collection parameter Path: argument type [String], parameter type [System.String[]], collection type Array, element type [System.String], no coerceElementType
DEBUG: 2024-05-15 08:58:01.9207 ParameterBinding Information: 0 : Creating array with element type [System.String] and 1 elements
DEBUG: 2024-05-15 08:58:01.9208 ParameterBinding Information: 0 : Argument type String is not IList, treating this as scalar
DEBUG: 2024-05-15 08:58:01.9210 ParameterBinding Information: 0 : Adding scalar element of type String to array position 0
DEBUG: 2024-05-15 08:58:01.9212 ParameterBinding Information: 0 : BIND arg [System.String[]] to param [Path] SUCCESSFUL
DEBUG: 2024-05-15 08:58:01.9213 ParameterBinding Information: 0 : Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2024-05-15 08:58:01.9215 ParameterBinding Information: 0 : Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2024-05-15 08:58:01.9216 ParameterBinding Information: 0 : Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2024-05-15 08:58:01.9217 ParameterBinding Information: 0 : Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2024-05-15 08:58:01.9219 ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-Item]
DEBUG: 2024-05-15 08:58:01.9220 ParameterBinding Information: 0 : CALLING ProcessRecord
DEBUG: 2024-05-15 08:58:01.9228 ParameterBinding Information: 0 : CALLING EndProcessing
This example shows binding by property name. In this case, PSPath
is an alias for LiteralPath
, so the PSPath property is binding to the -LiteralPath
parameter.
PS> Trace-Command -Name ParameterBinding -Expression {$fileobj | Get-Item } -PSHost
DEBUG: 2024-05-15 08:59:18.2210 ParameterBinding Information: 0 : BIND NAMED cmd line args [Get-Item]
DEBUG: 2024-05-15 08:59:18.2212 ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Get-Item]
DEBUG: 2024-05-15 08:59:18.2214 ParameterBinding Information: 0 : BIND cmd line args to DYNAMIC parameters.
DEBUG: 2024-05-15 08:59:18.2216 ParameterBinding Information: 0 : DYNAMIC parameter object: [Microsoft.PowerShell.Commands.FileSystemProviderGetItemDynamicParameters]
DEBUG: 2024-05-15 08:59:18.2217 ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-Item]
DEBUG: 2024-05-15 08:59:18.2219 ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: 2024-05-15 08:59:18.2221 ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Get-Item]
DEBUG: 2024-05-15 08:59:18.2222 ParameterBinding Information: 0 : PIPELINE object TYPE = [System.IO.FileInfo]
DEBUG: 2024-05-15 08:59:18.2223 ParameterBinding Information: 0 : RESTORING pipeline parameter's original values
DEBUG: 2024-05-15 08:59:18.2225 ParameterBinding Information: 0 : Parameter [Path] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: 2024-05-15 08:59:18.2227 ParameterBinding Information: 0 : BIND arg [D:\temp\test\[foobar].xyz] to parameter [Path]
DEBUG: 2024-05-15 08:59:18.2229 ParameterBinding Information: 0 : Binding collection parameter Path: argument type [FileInfo], parameter type [System.String[]], collection type Array, element type [System.String], no coerceElementType
DEBUG: 2024-05-15 08:59:18.2230 ParameterBinding Information: 0 : Creating array with element type [System.String] and 1 elements
DEBUG: 2024-05-15 08:59:18.2231 ParameterBinding Information: 0 : Argument type FileInfo is not IList, treating this as scalar
DEBUG: 2024-05-15 08:59:18.2233 ParameterBinding Information: 0 : BIND arg [D:\temp\test\[foobar].xyz] to param [Path] SKIPPED
DEBUG: 2024-05-15 08:59:18.2234 ParameterBinding Information: 0 : Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2024-05-15 08:59:18.2236 ParameterBinding Information: 0 : Parameter [Path] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2024-05-15 08:59:18.2237 ParameterBinding Information: 0 : Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2024-05-15 08:59:18.2238 ParameterBinding Information: 0 : Parameter [LiteralPath] PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
DEBUG: 2024-05-15 08:59:18.2240 ParameterBinding Information: 0 : BIND arg [Microsoft.PowerShell.Core\FileSystem::D:\temp\test\[foobar].xyz] to parameter [LiteralPath]
DEBUG: 2024-05-15 08:59:18.2241 ParameterBinding Information: 0 : Binding collection parameter LiteralPath: argument type [String], parameter type [System.String[]], collection type Array, element type [System.String], no coerceElementType
DEBUG: 2024-05-15 08:59:18.2243 ParameterBinding Information: 0 : Creating array with element type [System.String] and 1 elements
DEBUG: 2024-05-15 08:59:18.2244 ParameterBinding Information: 0 : Argument type String is not IList, treating this as scalar
DEBUG: 2024-05-15 08:59:18.2245 ParameterBinding Information: 0 : Adding scalar element of type String to array position 0
DEBUG: 2024-05-15 08:59:18.2246 ParameterBinding Information: 0 : BIND arg [System.String[]] to param [LiteralPath] SUCCESSFUL
DEBUG: 2024-05-15 08:59:18.2248 ParameterBinding Information: 0 : Parameter [Credential] PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
DEBUG: 2024-05-15 08:59:18.2250 ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Get-Item]
DEBUG: 2024-05-15 08:59:18.2251 ParameterBinding Information: 0 : CALLING ProcessRecord
DEBUG: 2024-05-15 08:59:18.2254 ParameterBinding Information: 0 : CALLING EndProcessing
Directory: D:\temp\test
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 5/15/2024 8:47 AM 0 [foobar].xyz
You could also do it this way:
Get-Item (foobar.exe) | Remove-Item
You can also use a delay-bind script block:
foobar.exe | Remove-Item -LiteralPath { $_ }
Prerequisites
Get-Foo
cmdlet" instead of "Typo."Links
Summary
I don't personally feel this is a bug and more so feels like an undocumented behavior. Basically, depending on whether you are passing a
string
vs aIO.FileSystemInfo
object via pipeline to certain cmdlets (e.g.Get-Item
,Remove-Item
, etc), their behavior is different and unexpected.If
-Path
is the default pipeline parameter, then the behavior of passing an item via pipeline vs passing it directly to that parameter should result in the same behavior regardless of the items type, but in this case it does not.The reason this came up for me is because I had written a script to essentially do this:
The script was not doing anything because the file paths returned by
foobar.exe
contained glob characters. (e.g.[foobar].xyz
)So I fixed the script to run as:
And then I started wondering why I had to make that fix for
Get-Item
but notRemove-Item
...then I realized one was being passed a list of strings and the other a list ofIO.FileSystemInfo
objects. This led me to check the documentation to see if this was mentioned in there.Details
Code example:
Suggested Fix
There should be some sort of note indicating that for these cmdlets, if a
IO.FileSystemInfo
object is passed in via the pipeline, then it is handled directly, rather than via the-Path
parameter; Whereas if a string is passed in via the pipeline, is is interpreted the same as-Path
.