This example illustrates what I mean:
Function test{
param(
[parameter(Position = 0, ValueFromPipeline)]
[string]$Param0,
[parameter(Position = 1)]
[ArgumentCompletions("Param1_Opt1", "Param1_Opt2")]
[Array]$Param1 = ('Param1_Opt3', 'Param1_Opt4'),
[switch]$NoHeader
)
"This is $Param0"
"Header :$Param1"
}
For a long time I have been relying on parameter positions in all my functions today when writing a function, suddenly its stopped working the way I have been using them. The above test
function demonstrates this issue.
If a parameter has a parameter Position = 0
property and also has the ValueFromPipeline
property. When its used in pipeline. The next parameter that has a Position
property takes its position. This also means this next parameters ArgumenCompletions
, such as "Param1_Opt1"
/"Param1_Opt2"
get suggested.
But I am not getting this behaviour at all.
Test "This is For Parameter Zero" "This is For Parameter One"
---- Ouput -----
This is For Parameter Zero
This is For Parameter One
The above works as expected, The first string is correctly assigned to Param0
and the second to Param1
,further more Param1
argument suggestions works, but the following fails with an error and the pipeline string is assigned to Param1
. Also Param1
argument completions don't work:
"This is For Parameter Zero" | Test "This is For Parameter One"
---- Error ----
test: The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
--- OutPut ----
This is For Parameter One
Header :Param1_Opt0 Param1_Opt0
Test "This is For Parameter Zero" "This is For Parameter One"
---- Ouput -----
This is For Parameter Zero
This is For Parameter One
The above is what I am expecting in normal usage and the following for when the pipeline is used and for Param1
argument suggestions to work:
"This is For Parameter Zero" | Test "This is For Parameter One"
---- Ouput -----
This is For Parameter Zero
This is For Parameter One
Santiago has provided the crucial pointer in a comment, but let me spell it out:
In your pipeline-based invocation, you'll need to use a named argument to bind to -Param1
:
# Note the required use of -Param1
"This is For Parameter Zero" | Test -Param1 "This is For Parameter One"
The reason this is necessary is the order of parameter binding:
Input provided by arguments is bound first, before execution of the command starts.
Only later, when pipeline input processing begins, is parameter binding performed on each pipeline input object.
As for why PowerShell must bind parameters this way:
Arguments passed to a command are by design evaluated up front, and the result of the evaluation is bound to the target parameters once, before the command even starts to execute.
Pipeline input is generally not known in advance, given that it is only produced when the input-providing command executes.
Additionally, the data types of the objects emitted by the input-providing command is generally not known, and commands are free to emit objects of differing types, so each emitted object may bind differently to the receiving command's parameters.
In short: There's no way to statically, up front determine how pipeline input will bind to parameters, so it must be deferred until runtime, and must be performed individually for each and every input object.
Therefore:
"This is For Parameter Zero" | Test "This is For Parameter One"
binds argument "This is For Parameter One"
first, in isolation, without considering potential pipeline input, and, given that it is the first positional argument, it binds to parameter -Param0
Then when pipeline-input processing starts, PowerShell tries to bind the pipeline object "This is For Parameter Zero"
to a not-yet-bound-by-arguments parameter, and since your function's only pipeline-binding parameter, -Param0
, is already bound, parameter binding fails, resulting in the error you saw.
-Param1
avoids this problem.