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-Path
using 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-InputObject
by 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