Undoubtedly, the most unique feature of PowerShell is its pipeline and it implements parameter binding. PowerShell allows us to pass objects (not just strings) across the pipeline. It must have a way to “map” various attributes from the command that’s outputting information to the command that needs that information as input. The method that does this is called parameter binding.
Avatar

To demonstrate parameter binding, let's create our own simple example. We'll create a Get function and a Remove function. We will craft the Get function to return a specific object and its properties and then create the Remove function to understand these (bindings).

Demonstrating parameter binding

function Get-Widget {
    [CmdletBinding()]
    param()
    [pscustomobject]@{
        FileName = 'foo.txt'
        Path = 'C:\foo.txt'
    }
}

function Remove-Widget {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        [pscustomobject]$Widget
    )

    Write-Host "Remove-Widget: I see a `$Widget property FileName [$($Widget.FileName)] and property Path is [$($Widget.Path)]"
}

Notice in the example above that the Get-Widget function returns a type of pscustomobject with two properties: FileName and Path.

The Remove-Widget function has a single parameter called Widget that accepts an object type of pscustomobject.

When you run Get-Widget | Remove-Widget (which forces Remove-Widget to ingest whatever Get-Widget returns), you'll get this:

Remove-Widget: I see a $Widget property FileName [foo.txt] and property Path is [C:\foo.txt]

You can see Remove-Widget "sees" both properties because the entire object that Get-Widget returned was bound to $Widget. This type of binding is "binding by value." The entire object is bound to a parameter in the receiving command.

This doesn't just happen by itself though. This worked because of the ValueFromPipeline parameter attribute I've defined. This tells PowerShell to attempt to bind any appropriate object coming in from the pipeline to this parameter.

But what if you don't need the entire object to "map" to a parameter? Instead, perhaps you'd rather have only a single property represent a parameter on the receiving command. In such a case, we can set up binding by property. By setting a parameter to accept pipeline binding by property name, we can tell PowerShell to map only a single property of an object coming from the sending command to a parameter on the receiving command.

Using the same code for Get-Widget but changing the code for Remove-Widget a bit, we can change PowerShell's binding behavior. Notice below I've changed the $Widget parameter name to $Path in Remove-Widget, the type to string, and instead of using the parameter attribute ValueFromPipeline, I'm using ValueFromPipelineByPropertyName.

Parameter binding by property

function Get-Widget {
    [CmdletBinding()]
    param()
    [pscustomobject]@{
        FileName = 'foo.txt'
        Path = 'C:\foo.txt'
    }
}

function Remove-Widget {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName)]
        [pscustomobject]$Widget
    )

    Write-Host "Remove-Widget: I see a `$Widget property FileName [$($Widget.FileName)] and property Path is [$($Widget.Path)]"
}

If I've enabled strict mode by running Set-StrictMode -Version Latest, when I run this as is, it throws an error: The property 'FileName' cannot be found on this object. Verify that the property exists.

Unknown parameter binding behavior

Unknown parameter binding behavior

It's throwing this error because PowerShell didn't bind the entire object this time. Instead, it just bound the property name Path on Get-Widget's object. Under the hood, PowerShell is only attaching the value of Path to the $Path parameter.

I'll need to change up my Write-Host reference a bit now to use the Path variable (since it is the only thing bound) and run it again.

function Remove-Widget {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Path
    )

    Write-Host "Remove-Widget: I see the `$Path parameter value is now $Path."
}¬
Write-Host "Remove-Widget: I see the `$Path parameter value is now $Path."

After running Get-Widget | Remove-Widget again, I now get no errors with the output Remove-Widget: I see the $Path parameter value is now C:\foo.txt. $Path's value now is the value of the Path property on the object that Get-Widget returned.

Subscribe to 4sysops newsletter!

Conclusion

Parameter binding is a fascinating concept if you're a geek like me. If you're interested in learning more, check out exactly how both of these methods work. For further explanation on how the pipeline interacts with the begin, process, and end blocks, I highly recommend Boe Prox's blog article entitled Tips on Implementing Pipeline Support.

avatar
1 Comment
  1. Avatar
    Adam L 2 years ago

    Hey sorry I know this article is three years old, but I don’t think the second code example has been modified as you say it have. It sure looks like the remove-widget function is still taking in the whole pscustomobject, and not just the $Path attribute like it is in the final code block. Am I reading this wrong?

Leave a reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account