Dess
Dess

Reputation: 2669

How are parameters bound to second position in pipeline?

I'm trying to understand how parameters are bound to the second position of a cmdlet, within a pipeline.

Tried with Select-String [1]. It has -Path, which accepts pipeline input at position 1 (second position). These work:

sls "the-pattern" "the-file.txt"
"the-file.txt" | sls "the-pattern" -path {$_}

But this doesn't:

"the-file.txt" | sls "the-pattern"

In this latter case, I expect "the-file.txt" to be bound as the 2nd argument to sls.

How do parameter binding work after the first position, in Powershell?

[1] https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/select-string?view=powershell-7.3#parameters

Upvotes: 1

Views: 131

Answers (1)

Santiago Squarzon
Santiago Squarzon

Reputation: 60498

Get-Help provides crucial information to answer your question:

Get-Help Select-String -Parameter LiteralPath, Path, Pattern, InputObject |
    Select-Object Name, Position, PipelineInput, Aliases

We can see the following:

name        position pipelineInput         aliases
----        -------- -------------         -------
LiteralPath named    True (ByPropertyName) PSPath, LP
Path        1        True (ByPropertyName) none
InputObject named    True (ByValue)        none
Pattern     0        False                 none

Both -LiteralPath and -Path are bound ByPropertyName whereas -InputObject is bound ByValue.

Knowing this we can assume the following, in the first example:

Select-String "the-pattern" "the-file.txt"
  • "the-pattern" is bound positionally to -Pattern.
  • "the-file.txt" is bound positionally to -Path.

In the second example:

"the-file.txt" | Select-String "the-pattern" -Path { $_ }

In third example, which works perfectly fine except it doesn't work how you expected it to work:

"the-file.txt" | Select-String "the-pattern"
  • "the-file.txt" is bound to -InputObject by ValueFromPipeline.
  • "the-pattern" is bound positionally to -Pattern.

You can see this is true by simply trying "the-file.txt" | Select-String "the-file".

As for things you didn't try, Trace-Command helps a lot understanding how parameters are bound. This particular case is hard to understand at first because Select-String will always bind -InputObject from pipeline, no matter what. This happens because the parameter type is PSObject, any object can be bound to this parameter so the rest are ignored.

Here is an example of what I mean, using a temporary file. $tmp should be bound to -LiteralPath because the object has the .PSPath property but instead it gets bound to -InputObject:

$tmp = New-TemporaryFile
$tmp = Get-Item $tmp.FullName
Trace-Command ParameterBinding { $tmp | Select-String .+ } -PSHost

Output

BIND NAMED cmd line args [Select-String]
BIND POSITIONAL cmd line args [Select-String]
    BIND arg [.+] to parameter [Pattern]        
        Binding collection parameter Pattern: argument type [String], parameter type [System.String[]], collection type Array, element type [System.String], no coerceElementType
        Creating array with element type [System.String] and 1 elements
        Argument type String is not IList, treating this as scalar
        Adding scalar element of type String to array position 0
        BIND arg [System.String[]] to param [Pattern] SUCCESSFUL
MANDATORY PARAMETER CHECK on cmdlet [Select-String]
CALLING BeginProcessing
BIND PIPELINE object to parameters: [Select-String]
    PIPELINE object TYPE = [null]
    RESTORING pipeline parameter's original values
    Parameter [InputObject] PIPELINE INPUT ValueFromPipeline NO COERCION
    BIND arg [] to parameter [InputObject]
        BIND arg [] to param [InputObject] SUCCESSFUL
MANDATORY PARAMETER CHECK on cmdlet [Select-String]
CALLING ProcessRecord
CALLING EndProcessing

In the source code we can also see it has logic for determining when the input object should be treated as a file and read from it instead.

else
{
    if (_inputObject.BaseObject is FileInfo fileInfo)
    {
        _inputObjectFileList[0] = fileInfo.FullName;
        expandedPaths = _inputObjectFileList;
    }
}

Upvotes: 2

Related Questions