1

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
MarredCheese
  • 17,541
  • 8
  • 92
  • 91
  • 1
    The `Trace-Command` output tells us that the parameter binder initially skips binding the value to `Path` because of type mismatch, but then it tries again _with coercion_ (eg. "convert types if necessary") because there are no other parameters it can bind to given the resolved parameter set, and succeeds. – Mathias R. Jessen Oct 11 '21 at 15:24

1 Answers1

1

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.

Cpt.Whale
  • 4,784
  • 1
  • 10
  • 16
  • Thanks. And do you happen to know if it coerces by calling the file object's `ToString()` method? – MarredCheese Oct 12 '21 at 03:14
  • 1
    @MarredCheese Powershell coerces by calling `[string]($object)`, or casting-as-type. Most of the time, this behaves exactly like the `.ToString()` method, but there are exceptions, e.g. https://stackoverflow.com/a/30542422/7411885. – Cpt.Whale Oct 12 '21 at 13:03