Reputation: 2669
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?
Upvotes: 1
Views: 131
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 { $_ }
"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" | 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