One of the most useful features of the PowerShell language is the pipeline. To take advantage of this PowerShell feature you have to build PowerShell functions that can accept pipeline input from other functions.

The pipeline is the ability to send objects (not just simple strings) from one command to another. The pipeline is one of those features that make PowerShell such a natural language. However, all functions are not created equal in PowerShell, and not all kinds of functions will work with the pipeline. Functions that need to be aware of the pipeline must be built with pipeline support.

To understand "pipelinable" functions, let's first build a build a function that has no clue that the pipeline exists and see what happens. To do this, I'll create functions called Get-Thing and Set-Thing. Since the noun is the same, I would expect to be able to pipe the results of Get-Thing to Set-Thing and do whatever manipulation to Thing is necessary.

Get-Thing | Set-Thing

I'll first build some basic functions. Get-Thing will output two objects, and Set-Thing will accept the same object type as Get-Thing returns.

function Get-Thing {
     param()
     [pscustomobject]@{
         Name = 'thing1'
         Description = 'thing1 description'
     }
     [pscustomobject]@{
         Name = 'thing2'
         Description = 'thing2 description'
     }
}
function Set-Thing {
     param(
         [pscustomobject]$Thing
     )
     ## Just output $Thing
     $Thing
}

If I run Get-Thing, it will return two objects as expected.

Get Thing output

Get Thing output

If I try to pipe this result directly to Set-Thing, you might expect to get the same result since Set-Thing is just accepting that output and returning it right back. If so, you'd be wrong. In fact, nothing is returned. Why? Because Set-Thing is not "advanced" and is not set up to accept pipeline input correctly.

The first task in making the Set-Thing function "pipeline-aware" is to make it advanced. This is the easy part. To do this, just add the [CmdletBinding()] reference to the function or use the [Parameter()] reference to at least one parameter. I'll do both below.

function Set-Thing {
     [CmdletBinding()]
     param(
         [Parameter()]
         [pscustomobject]$Thing
     )
     ## Just output $Thing
     $Thing
}

Now when Get-Thing is piped to Set-Thing, I get some output back, but it's a bunch of red text!

Set-Thing Errors

This means Set-Thing can now at least detect that something is amiss. We're making progress! The next step is to modify the $Thing parameter in Set-Thing to go from understanding a simple function call...

Set-Thing -Thing [pscustomobject]@{Name = 'foo'; Description = 'foo description'}

...to getting the value of $Thing from the pipeline instead. This step requires adding a parameter attribute to the Thing parameter called ValueFromPipeline. By defining this parameter attribute, this parameter now knows how to bind the object coming in from the pipeline to the Thing parameter.

The Thing parameter will now look like this:

[Parameter(ValueFromPipeline)]
[pscustomobject]$Thing

Piping Get-Thing to Set-Thing now returns an object.

Set Thing pipeline

Set Thing pipeline

But wait, that's only a single object. Get-Thing outputs two objects. Where did the other one go? The other one was discarded because we didn't have the final piece to perfecting pipeline functions: the process block.  The process block is necessary for Set-Thing because this is the block that processes each object, not just the last one.

I'll now add a process block to the Set-Thing function.

function Set-Thing {
     [CmdletBinding()]
     param(
         [Parameter(ValueFromPipeline)]
         [pscustomobject]$Thing
     )
     process {
         ## Just output $Thing
         $Thing
     }
}

Now I'll run Get-Thing | Set-Thing again and see what happens.

Successful pipeline output

Successful pipeline output

Success! Set-Thing is now returning what Get-Thing has sent to it over the pipeline. This is known as passing objects over the pipeline, but we have one more option which allows us to pass only property names instead. To do that, I'll also add the ValueFromPipelineByPropertyName attribute to the Thing parameter.

[Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]

If I attempt to pipe Get-Thing to Set-Thing again, it will return the same result. No change. However, I now have the option to pass specific properties of that object. For instance, maybe I just want to accept the Name property from Get-Thing's output. To do that, I'll add a Name property to Set-Thing and set the ValueFromPipelineByPropertyName parameter attribute to it.

param(
     [Parameter(ValueFromPipelineByPropertyName)]
     [string]$Name
)

You can see my parameter type is now a string rather than a pscustomobject. This is because I'm only accepting a single property of that object, not the entire object itself.

I'll now attempt to pipe Get-Thing to Set-Thing and see what happens.

Get-Thing | Set-Thing

You would see that this just returns the values of the Name property for each of the objects. This is binding by property name rather than by object.

Subscribe to 4sysops newsletter!

Those are the basics of building functions for pipeline input. If you'd like more information about this topic, I encourage you to take a look at my Pluralsight course Building Advanced PowerShell Functions and Modules. This course goes into great detail on more advanced topics like parameter binding, type casting, and so on.

avatar
1 Comment
  1. Excellent one Adam!

    There is just a typo error in the 7th sample of code with tags.

Leave a reply

Please enclose code in pre tags

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

*

© 4sysops 2006 - 2022

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