Effective PowerShell Item 12: Understanding ByValue Pipeline Bound Parameters

In item 11, I covered ByPropertyName pipeline bound parameters.  In this post, I’ll cover the other variety of pipeline binding – ByValue.  ByValue binding takes the input object itself and attempts to bind it by type using type coercion if possible to parameters decorated as ByValue.  For example, most of the *-Object utility cmdlets operate on whatever object is presented to them.  The help on Where-Object shows this:

-inputObject <psobject>
    Specifies the objects to be filtered. If you save the output of a command in a variable,
    you can use InputObject to pass the variable to Where-Object. However, typically, the
    InputObject parameter is not typed in the command. Instead, when you pass an object
    through the pipeline, Windows PowerShell associates the passed object with the
    InputObject parameter.

    Required?                    false
    Position?                    named
    Default value
    Accept pipeline input?       true (ByValue)
    Accept wildcard characters?  false

It turns out that ByValue isn’t nearly as popular as ByPropertyValue.  How can I make such a statement you ask?  Well this is one of the things that I love about PowerShell.  It provides so much metadata about itself.  It is very "self describing".  You can easily walk every parameter on every cmdlet that is currently loaded into PowerShell.  First let’s see what information is available for a parameter:

PS> Get-Command -CommandType cmdlet | Select -Expand ParameterSets | 
>> Select -Expand Parameters -First 1 | Get-Member
>>
TypeName: System.Management.Automation.CommandParameterInfo Name MemberType Definition ---- ---------- ----------
... Aliases Property System.Collections.ObjectModel.ReadOnlyCollection`1[[...
Attributes Property System.Collections.ObjectModel.ReadOnlyCollection`1[[...
HelpMessage Property System.String HelpMessage {get;}
IsDynamic Property System.Boolean IsDynamic {get;} IsMandatory Property System.Boolean IsMandatory {get;}
Name Property System.String Name {get;}
ParameterType Property System.Type ParameterType {get;} Position Property System.Int32 Position {get;} ValueFromPipeline Property System.Boolean ValueFromPipeline {get;}
ValueFromPipelineByPropertyName Property System.Boolean ValueFromPipelineByPropertyName {get;}
ValueFromRemainingArguments Property System.Boolean ValueFromRemainingArguments {get;}

The interesting properties for us here are the Name and ValueFromPipeline* properties.  Given this information it is easy to figure out how many of each type there are:

PS> (Get-Command -CommandType cmdlet | Select -Expand ParameterSets | Select -Expand Parameters |
>> Where {$_.ValueFromPipeline -and !$_.ValueFromPipelineByPropertyName} | Measure-Object).Count
>>
55 PS> (Get-Command -CommandType cmdlet | Select -Expand ParameterSets | Select -Expand Parameters |
>> Where {!$_.ValueFromPipeline -and $_.ValueFromPipelineByPropertyName} | Measure-Object).Count
>>
196 PS> (Get-Command -CommandType cmdlet | Select -Expand ParameterSets | Select -Expand Parameters |
>> Where {$_.ValueFromPipeline -and $_.ValueFromPipelineByPropertyName} | Measure-Object).Count
>>
66

So from here we can see the following:

Type of Pipeline Binding

Count

ValueFromPipeline

55

ValueFromPipelineByPropertyName

196

Both

66

So indeed binding by property name is much more common.  Binding by value from the pipeline is primarily for cmdlets that manipulate objects.  In the query below we can see that the InputObject parameter is by far the most common "ByValue" pipeline bound parameter:

PS> Get-Command -CommandType cmdlet | Select -Expand ParameterSets | Select -Expand Parameters |
>> Where {$_.ValueFromPipeline -and !$_.ValueFromPipelineByPropertyName} |
>> Group Name -NoElement | Sort Count -Desc >> Count Name ----- ---- 40 InputObject 4 Message 3 String 2 SecureString 1 ExecutionPolicy 1 Object 1 AclObject 1 DifferenceObject 1 Id 1 Command

A little further digging reveals the cmdlets that use the ByValue bound InputObject parameters as shown below.  Note that a single parameter can appear in more than one parameter set on a cmdlet, which explains why there are only 36 cmdlets that account for the 40 instances of InputObject.

PS> $CmdletName = @{Name='CmdletName';Expression={$_.Name}}
PS> Get-Command -CommandType cmdlet | Select $CmdletName -Expand ParameterSets |
>> Select CmdletName -Expand Parameters |
>> Where {$_.ValueFromPipeline -and !$_.ValueFromPipelineByPropertyName} | >> Group Name | Sort Count -Desc | Select -First 1 | Foreach {$_.Group} | >> Sort CmdletName -Unique | Format-Wide CmdletName -AutoSize >> Add-History Add-Member ConvertTo-Html Export-Clixml Export-Csv ForEach-Object
Format-Custom Format-List Format-Table Format-Wide Get-Member Get-Process
Get-Service Get-Unique Group-Object Measure-Command Measure-Object Out-Default
Out-File Out-Host Out-Null Out-Printer Out-String Restart-Service
Resume-Service Select-Object Select-String Sort-Object Start-Service Stop-Process
Stop-Service Suspend-Service Tee-Object Trace-Command Where-Object Write-Output

As you can see most of these cmdlets are designed to deal with objects in general.  Note to cmdlet developers – pipeline bound parameters is how your cmdlets receive pipeline objects.  When writing a cmdlet there is no $_.  If your cmdlet wants to "participate" in the pipeline it must set the ParameterAttribute property ValueFromPipeline and/or ValueFromPipelineByPropertyName to true on at least one of its parameters. 

As mentioned above most ByValue parameters are of the InputObject (type psobject or psobject[]) variety so they pretty much accept anything.  However not all cmdlets work that way.  The -Id parameter (type [long[]]) on Get-History is pipeline bound ByValue.  The follow Trace-Command output shows how PowerShell works hard when necessary to convert the input object’s type to the expected type. In this case a scalar string value of ‘1’ to an array of Int64:

PS> Trace-Command -Name ParameterBinding -PSHost -Expression {'1' | get-history}
BIND NAMED cmd line args [Get-History] BIND POSITIONAL cmd line args [Get-History] MANDATORY PARAMETER CHECK on cmdlet [Get-History] CALLING BeginProcessing BIND PIPELINE object to parameters: [Get-History] PIPELINE object TYPE = [System.String] RESTORING pipeline parameter's original values Parameter [Id] PIPELINE INPUT ValueFromPipeline NO COERCION BIND arg [1] to parameter [Id] Binding collection parameter Id: argument type [String], parameter type
[System.Int64[]], collection type Array, element type [System.Int64],
no coerceElementType
Creating array with element type [System.Int64] and 1 elements Argument type String is not IList, treating this as scalar BIND arg [1] to param [Id] SKIPPED Parameter [Id] PIPELINE INPUT ValueFromPipeline WITH COERCION BIND arg [1] to parameter [Id] COERCE arg type [System.Management.Automation.PSObject] to [System.Int64[]]
ENCODING arg into collection
Binding collection parameter Id: argument type [PSObject], parameter type
[System.Int64[]], collection type Array, element type [System.Int64],
coerceElementType
Creating array with element type [System.Int64] and 1 elements Argument type PSObject is not IList, treating this as scalar COERCE arg type [System.Management.Automation.PSObject] to [System.Int64]
CONVERT arg type to param type using LanguagePrimitives.ConvertTo
CONVERT SUCCESSFUL using LanguagePrimitives.ConvertTo: [1]
Adding scalar element of type Int64 to array position 0 Executing VALIDATION metadata: [System.Management.Automation.ValidateRangeAttribute]
BIND arg [System.Int64[]] to param [Id] SUCCESSFUL
MANDATORY PARAMETER CHECK on cmdlet [Get-History] CALLING ProcessRecord CALLING EndProcessing

Note that on the first attempt, PowerShell tries to convert the string to an array of Int64 and fails.  Then it tries again by treating the input as psobject.  It throws that psobject at an internal help class method LanguagePrimitives.ConvertTo() that successfully converts the string ‘1’ to an Int64[] containing the value 1.

When a parameter is both ByValue and ByPropertyName bound, PowerShell attempts to bind in this order:

  1. Bind ByValue with no type conversion
  2. Bind ByPropertyName with no type conversion
  3. Bind ByValue with type conversion
  4. Bind ByPropertyName with type conversion

There is more to the parameter binding algorithm like finding the best match amongst different parameter sets.  BTW one last tidbit related to parameters.  The PowerShell help topics aren’t completely automatically generated and as a result they aren’t always correct.  For instance, look up the parameters on Get-Content and see if you find a -Wait parameter – you won’t.  🙂  However the metadata is always complete and correct e.g.:

PS> Get-Command Get-Content -Syntax
Get-Content [-Path] <String[]> [-ReadCount <Int64>] [-TotalCount <Int64>] [-Filter <String>] 
[-Include <String[]>] [-Exclude <String[]>] [-Force] [-Credential <PSCredential>] [-Verbose]
[-Debug] [-ErrorAction <ActionPreference>] [-ErrorVariable <String>] [-OutVariable <String>]
[-OutBuffer <Int32>] [-Delimiter <String>] [-Wait] [-Encoding <FileSystemCmdletProviderEncoding>]
Get-Content [-LiteralPath] <String[]> [-ReadCount <Int64>] [-TotalCount <Int64>] [-Filter <String>]
[-Include <String[]>] [-Exclude <String[]>] [-Force] [-Credential <PSCredential>] [-Verbose]
[-Debug] [-ErrorAction <ActionPreference>] [-ErrorVariable <String>] [-OutVariable <String>]
[-OutBuffer <Int32>] [-Delimiter <String>] [-Wait] [-Encoding <FileSystemCmdletProviderEncoding>]

Hopefully this post has given you more knowledge about ByValue parameters and how to explore and get more information on cmdlet parameters in general.  In summary, there actually isn’t much you need to know about ByValue pipeline bound parameters because in most cases they just work intuitively.  Just be sure to keep your eye out for those parameters that bind ByPropertyName. They are the ones whose pipeline bound usage isn’t as obvious.

This entry was posted in Effective PowerShell. Bookmark the permalink.

6 Responses to Effective PowerShell Item 12: Understanding ByValue Pipeline Bound Parameters

  1. Tomas Soucek says:

    Great and concise.
    In the penultimate paragraph you mentioned: “There is more to the parameter binding algorithm like finding the best match amongst different parameter sets.” Could you point me to a place dealing with this topic?
    Thanks a lot

    • rkeithhill says:

      The only place I’m aware that has parameter binding information is Bruce Payette’s Windows PowerShell in Action 2nd Edition. He covers the parameter binder in chapters two and three.

  2. ghostsquad2013 says:

    Your code is white foreground on a light grey background. It’s impossible to read.

Leave a comment