The help for Split-Path
states that the parameters -LiteralPath
and -Leaf
are incompatible:
SYNTAX
Split-Path [-Path] <System.String[]> -Leaf [-Resolve] # ...
Split-Path -LiteralPath <System.String[]> [-Resolve] # ...
This command failing confirms it:
Split-Path -LiteralPath 'C:\foo.txt' -Leaf
# Split-Path: Parameter set cannot be resolved using the specified named parameters.
# One or more parameters issued cannot be used together
And yet, piping works just fine:
Get-Item 'C:\foo.txt' | Split-Path -Leaf
Why is that?
I thought Split-Path
would receive an object from the pipeline and try to bind that object's PSPath
property to its -LiteralPath
parameter (alias: PSPath
), as explained here.
I imagine that because I supplied the -Leaf
switch, the parameter binder knew to use the parameter set containing -Leaf
, which took -LiteralPath
out of the picture. But then how does PSPath
end up bound to -Path
? Does the binder automatically call .ToString()
on the object to obtain the path as a string, which it can then bind to -Path
by value instead of by
property name? How does it know to do that?
I tried using Trace-Command, but I'm unable to understand the output.
Trace-Command ParameterBinding { Get-Item 'C:\foo.txt' | Split-Path -Leaf } -PSHost
CodePudding user response:
Following the output of Trace-Command
when using -Leaf
(trimmed down here):
# First, powershell binds any manually specified args (-Leaf):
BIND NAMED cmd line args [Split-Path]
BIND arg [True] to param [Leaf] SUCCESSFUL
# Then it checks for required positional args
BIND POSITIONAL cmd line args [Split-Path]
BIND cmd line args to DYNAMIC parameters.
MANDATORY PARAMETER CHECK on cmdlet [Split-Path]
# Because -Leaf was specified, it knows only -Path is required, and tries to bind the object first:
BIND PIPELINE object to parameters: [Split-Path]
PIPELINE object TYPE = [System.IO.FileInfo]
BIND arg [C:\foo] to parameter [Path]
BIND arg [C:\foo] to param [Path] SKIPPED ## fails
# It tries again later on by coercing to string and succeeds:
Parameter [Path] PIPELINE INPUT ValueFromPipeline WITH COERCION
BIND arg [C:\foo] to parameter [Path]
COERCE arg to [System.String[]]
BIND arg [System.String[]] to param [Path] SUCCESSFUL
Based on the trace output, powershell tries using named args
> pipeline, no coercion
> pipeline, with coercion
in order. It tries each with every possible parameter set in the order they're defined in. You can list the parameter sets in order with (Get-Command Split-Path).Definition
.
Without
-Leaf
, it's able to use the-LiteralPath
parameter set with no coercion first, so that's what runs.With
-Leaf
, it excludes the-LiteralPath
parameter set, and is finally able to use-Path
by coercing the pipeline object to string.