In our previous post, we learned how to make use of Begin, Process and End blocks to implement proper pipeline support. Building on to that, in this blog post, we’ll learn further about when to make use of ValueFromPipeline property and when to make use of ValueFromPipelineByPropertyName and what happens behind the scenes.
Multiple parameters that accept Pipeline Input
What if there are multiple parameters that can accept pipeline input and we need to run the Process block for them. Let’s consider below code:
Function Show-ProcessID { [CmdletBinding()] Param( [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String] $Id, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] [String] $ProcessName ) Begin { Write-Verbose "In the begin block" } Process { Write-Verbose "In the process block" Write-Host "$ProcessName has process Id as $Id" } End { Write-Verbose "In the end block" } }
If we run above code, we can see output like below:
It looks like it is processing the same value for each parameter just because it accepted pipeline input. We can get around this issue by using Property as ValueFromPipelineByPropertyName
. Note that when using this, it should match the property name in incoming object and our function. Example of our new code:
Function Show-ProcessID { [CmdletBinding()] Param( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [String] $Id, [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [String] $ProcessName ) Begin { Write-Verbose "In the begin block" } Process { Write-Verbose "In the process block" Write-Host "$ProcessName has process Id as $Id" } End { Write-Verbose "In the end block" } }
If we pass the same input again, we can now see it is being parsed correctly:
Another option to achieve same kind of output would be to use ParameterSets, but that would mean that you would only have one parameter or the other to accept pipeline input and work around it.
Order of Parameter Binding Process From Pipeline
Let’s modify our code to accept input from both ValueFromPipeline=$true
and ValueFromPipelineByPropertyName=$true
:
Function Show-ProcessID { [CmdletBinding()] Param( [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [String] $Id, [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [String] $ProcessName ) Begin { Write-Verbose "In the begin block" } Process { Write-Verbose "In the process block" Write-Host "$ProcessName has process Id as $Id" } End { Write-Verbose "In the end block" } }
Now, the output will be like below:
Note that this is same as the case where we did not specified ValueFromPipelineByPropertyName=$true
. This is the due to the order of binding inputs when using both of these attributes.
Below are the rules to decide:
- Bind parameter by Value with same Type (No Coercion)
- Bind parameter by PropertyName with same Type (No Coercion)
- Bind parameter by Value with type conversion (Coercion)
- Bind parameter by PropertyName with type conversion (Coercion)
We can use Trace-Command to dig deeper and see what is happening:
$Process = Get-Process | Select -First 1 Trace-Command parameterbinding { $Process | Show-ProcessID} -PSHost
In case of ValueFromPipelineByPropertyName=$true
:
We can see that it has first started with ‘NO COERCION’ and binded it successfully. Then it tried it with ‘COERCION’ and binded it successfully. Finally it decided that since both are same, there is no need for ‘COERCION’.
If we would have used both ValueFromPipeline=$true
and ValueFromPipelineByPropertyName=$true
, the trace-command would change to like below:
Note that it first tries for PIPELINE INPUT ValueFromPipeline as ‘NO COERCION’ and then it went for ValueFromPipelineByPropertyName as ‘NO COERCION’. After this, it went for ValueFromPipeline with ‘COERCION’ (although that was not highlighted due to lazyness)
Working with the [Alias()] attribute
Let’s say that our parameter names does not match with the incoming property names successfully. Or there may be possible more than one type of incoming objects. In such a case, we can use alias to perfectly match the property from incoming objects.
Let’s modify our code to look like below:
Function Show-ProcessID { [CmdletBinding()] Param( [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Alias("Id")] [String] $ProcessID, [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)] [Alias("Name")] [String] $ProcessName ) Begin { Write-Verbose "In the begin block" } Process { Write-Verbose "In the process block" Write-Host "$ProcessName has process Id as $ProcessID" } End { Write-Verbose "In the end block" } }
With Trace-Command output as below:
which is not terribly different from the one in previous case. So when using aliases, Trace-Command is not much useful for debugging.