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?
CodePudding user response:
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:
sls "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" | sls "the-pattern" -path {$_}
"the-pattern"is bound positionally to-Pattern."the-file.txt"is bound by name to-Pathusing a delay-bind scriptblock.
In third example, which works perfectly fine except it doesn't work how you expected it to work:
"the-file.txt" | sls "the-pattern"
"the-file.txt"is bound to-InputObjectby ValueFromPipeline."the-pattern"is bound positionally to-Pattern.
You can see this is true by simply trying "the-file.txt" | sls "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) and most likely has internal logic determining when it should treat the object coming from pipeline as a file and read from it's path.
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 | sls . } -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
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 = [System.IO.FileInfo]
RESTORING pipeline parameter's original values
Parameter [InputObject] PIPELINE INPUT ValueFromPipeline NO COERCION
BIND arg [...\AppData\Local\Temp\tmpD30C.tmp] to parameter [InputObject]
BIND arg [...\AppData\Local\Temp\tmpD30C.tmp] to param [InputObject] SUCCESSFUL
MANDATORY PARAMETER CHECK on cmdlet [Select-String]
CALLING ProcessRecord
CALLING EndProcessing
