I am trying to pass two parameters to a function through the pipeline but it does not appear to work as expected and I am struggling to understand why.
function Test-Pipeline {
[CmdletBinding ()]
Param(
[Parameter(ValueFromPipeline=$true)][String]$Name,
[Parameter(ValueFromPipeline=$true)][String]$Value
)
Write-Host "Name: $Name"
Write-Host "Value: $Value"
}
"Name", "Value" | Test-Pipeline
Name: Value
Value: Value
I tried running the Trace-Command
command to see what was happening. On line 35 we can see that Value
is bound to $Parameter
.
Why is PowerShell binding the second input to both parameters? If this is expected, why does it occur only for the second parameter and not for the first?
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : BIND arg [] to parameter [Name]
DEBUG: ParameterBinding Information: 0 : Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 : result returned from DATA GENERATION:
DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.String]
DEBUG: ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
DEBUG: ParameterBinding Information: 0 : BIND arg [] to param [Name] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : BIND arg [] to parameter [Value]
DEBUG: ParameterBinding Information: 0 : Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 : result returned from DATA GENERATION:
DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.String]
DEBUG: ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
DEBUG: ParameterBinding Information: 0 : BIND arg [] to param [Value] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : PIPELINE object TYPE = [System.String]
DEBUG: ParameterBinding Information: 0 : RESTORING pipeline parameter's original values
DEBUG: ParameterBinding Information: 0 : Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 : BIND arg [Name] to parameter [Value]
DEBUG: ParameterBinding Information: 0 : Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 : result returned from DATA GENERATION: Name
DEBUG: ParameterBinding Information: 0 : BIND arg [Name] to param [Value] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : Parameter [Name] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 : BIND arg [Name] to parameter [Name]
DEBUG: ParameterBinding Information: 0 : Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 : result returned from DATA GENERATION: Name
DEBUG: ParameterBinding Information: 0 : BIND arg [Name] to param [Name] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : BIND PIPELINE object to parameters: [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : PIPELINE object TYPE = [System.String]
DEBUG: ParameterBinding Information: 0 : RESTORING pipeline parameter's original values
DEBUG: ParameterBinding Information: 0 : Parameter [Name] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 : BIND arg [Value] to parameter [Name]
DEBUG: ParameterBinding Information: 0 : Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 : result returned from DATA GENERATION: Value
DEBUG: ParameterBinding Information: 0 : BIND arg [Value] to param [Name] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : Parameter [Value] PIPELINE INPUT ValueFromPipeline NO COERCION
DEBUG: ParameterBinding Information: 0 : BIND arg [Value] to parameter [Value]
DEBUG: ParameterBinding Information: 0 : Executing DATA GENERATION metadata: [System.Management.Automation.ArgumentTypeConverterAttribute]
DEBUG: ParameterBinding Information: 0 : result returned from DATA GENERATION: Value
DEBUG: ParameterBinding Information: 0 : BIND arg [Value] to param [Value] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Test-Pipeline]
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Write-Host]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Write-Host]
DEBUG: ParameterBinding Information: 0 : BIND REMAININGARGUMENTS cmd line args to param: [Object]
DEBUG: ParameterBinding Information: 0 : BIND arg [System.Collections.Generic.List`1[System.Object]] to parameter [Object]
DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.Object]
DEBUG: ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
DEBUG: ParameterBinding Information: 0 : BIND arg [System.Collections.Generic.List`1[System.Object]] to param [Object] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Write-Host]
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
Name: Value
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
DEBUG: ParameterBinding Information: 0 : BIND NAMED cmd line args [Write-Host]
DEBUG: ParameterBinding Information: 0 : BIND POSITIONAL cmd line args [Write-Host]
DEBUG: ParameterBinding Information: 0 : BIND REMAININGARGUMENTS cmd line args to param: [Object]
DEBUG: ParameterBinding Information: 0 : BIND arg [System.Collections.Generic.List`1[System.Object]] to parameter [Object]
DEBUG: ParameterBinding Information: 0 : COERCE arg to [System.Object]
DEBUG: ParameterBinding Information: 0 : Parameter and arg types the same, no coercion is needed.
DEBUG: ParameterBinding Information: 0 : BIND arg [System.Collections.Generic.List`1[System.Object]] to param [Object] SUCCESSFUL
DEBUG: ParameterBinding Information: 0 : MANDATORY PARAMETER CHECK on cmdlet [Write-Host]
DEBUG: ParameterBinding Information: 0 : CALLING BeginProcessing
Value: Value
DEBUG: ParameterBinding Information: 0 : CALLING EndProcessing
Per the comment from Lee Dailey, you can only have one parameter accept input from the Pipeline by "Value" (for each value type, e.g string, int etc.). What your code is currently doing is sending an array of string values, which would then be processed one at a time through the pipeline.
If you want to send in multiple values together in to the Pipeline, you could do so by making those values the properties of a custom object, and then you can accept them via the pipeline by using ValueFromPipelineByPropertyName
parameters. This works by matching any properties of the input object that have the same name as input parameters:
function Test-Pipeline {
[CmdletBinding ()]
Param(
[Parameter(ValueFromPipelineByPropertyName=$true)][String]$Name,
[Parameter(ValueFromPipelineByPropertyName=$true)][String]$Value
)
Write-Host "Name: $Name"
Write-Host "Value: $Value"
}
$MyObject = [pscustomobject]@{
Name = "MyName"
Value = "MyValue"
}
$MyObject | Test-Pipeline
Result:
Name: MyName
Value: MyValue
An alternative but similar approach is to use ValueFromPipeline
to accept an input object, and then get the property values from that object:
function Test-Pipeline {
[CmdletBinding ()]
Param(
[Parameter(ValueFromPipeline=$true)][Object]$InputObject
)
$Name = $InputObject.Name
$Value = $InputObject.Value
Write-Host "Name: $Name"
Write-Host "Value: $Value"
}
$MyObject = [pscustomobject]@{
Name = "MyName"
Value = "MyValue"
}
$MyObject | Test-Pipeline
Some cmdlets will support both of these approaches, as PowerShell will try to match by object type first and then will revert to matching by property name. There's a detailed explanation of this here if you'd like to know more: https://blogs.technet.microsoft.com/heyscriptingguy/2013/03/25/learn-about-using-powershell-value-binding-by-property-name/
Note if you are going to work with values successfully via the Pipeline you also need to use a Process { }
block in your function, which results in collections of objects being processed one at a time:
function Test-Pipeline {
[CmdletBinding ()]
Param(
[Parameter(ValueFromPipeline=$true)][Object]$InputObject
)
Process {
$Name = $InputObject.Name
$Value = $InputObject.Value
Write-Host "Name: $Name"
Write-Host "Value: $Value"
}
}
$MyObject = @(
[pscustomobject]@{
Name = "MyName"
Value = "MyValue"
}
[pscustomobject]@{
Name = "MySecondName"
Value = "MySecondValue"
}
)
$MyObject | Test-Pipeline
Without this only the last value in the object collection would be handled.