PowerShell How-To

Writing PowerShell Functions for the Pipeline

Make sure your code is ready for the pipeline with these tips.

One of the best things about Windows PowerShell is the pipeline. The pipeline is beneficial in so many ways, and it is in part what makes PowerShell fun to code in.  The capability of the pipeline to pass objects rather than simple strings is an extremely powerful concept.  However, if you're new to writing PowerShell functions, you may find that your function breaks when you attempt to use the pipeline.  To use the pipeline effectively, a function must be specifically coded to accept pipeline input or it won't work.

Here's a very simple example function I just created.

function Get-ComputerInfo { 
param($Computername)
Get-WmiObject -Computername $Computername -Class Win32_OperatingSystem
}

Let's say I have a text file of computer names in C:\Computers.txt that I want to pass to this function.  Naturally, I try to use the pipeline.

Get-Content C:\Computers.txt | Get-ComputerInfo 
[Click on image for larger view.]  Figure 1.

You can see that doesn't work out too well.  The Get-WmiObject cmdlet was passed a null value for ComputerName.  Why? It's because our function doesn't know what to do with pipeline input, and the $Computername variable isn't populated as you'd expect.

To get this to work, you'd have to use a foreach loop.

[Click on image for larger view.]  Figure 2.

That's not very intuitive.  How are we supposed to simply pass the output of Get-Content to Get-ComputerInfo?  The answer is to turn our simple function into an advanced function, and use the ValueFromPipeline statement to give our function the capability to recognize pipeline input.

function Get-ComputerInfo { 
[CmdletBinding()]
 param(
 [Parameter(ValueFromPipeline)]
 $Computername
 )
 process {
 Get-WmiObject -Computername $Computername -Class Win32_OperatingSystem
 }
}

Now when I attempt to directly pipe the output of Get-Content to Get-ComputerInfo, it works as I expect.

[Click on image for larger view.]  Figure 3.

Why did this work?  The reason is through something called parameter binding.  First, to get parameter binding to work from the pipeline, you must make a function "advanced."  This is a concept that makes simple functions act like fully-fledged cmdlets.  You can do this by adding the [CmdletBinding()] keyword.  Just using this simple trick makes the function "advanced."

Now that the function is advanced, you have the ability to specify the ValueFromPipeline statement against the $Computername parameter.  This tells PowerShell to keep a look out for objects that are coming in from the pipeline.  If found, bind those objects to the $Computername parameter, and use that value as the argument to $Computername.

PowerShell has two types of parameter binding: ValueFromPipeline or ValueFromPipelineByPropertyName.  In this instance, I'm using ValueFromPipeline.  When Get-Content sends output down the pipeline, it does so one string object at a time.  When Get-ComputerInfo sees this, PowerShell runs through its parameter binding process and attempts to search for a parameter to match that up against.  Because we've specified ValueFromPipeline on the $Computername parameter on Get-ComputerInfo, PowerShell sees this and automatically binds the output of Get-Content to $Computername.

After the parameter is bound, ensure you have a process block.  Advanced functions have three blocks: begin, process, and end.  We won't go over how these work in detail.  However, when working with the pipeline, you must always have the process block.  Using this block enables your function to accept every object coming in from the pipeline instead of just the first one.

The intricacies of how PowerShell accepts pipeline input may be difficult to wrap your head around.  You'll get frustrated sometimes, because PowerShell isn't binding certain parameters coming from the output into the input of the other function.  We've all been there.  Just remember to ensure your input function is advanced, has a process block, and uses either the ValueFromPipeline or ValueFromPipelineByPropertyName, if required.

About the Author

Adam Bertram is a 20-year veteran of IT. He's an automation engineer, blogger, consultant, freelance writer, Pluralsight course author and content marketing advisor to multiple technology companies. Adam also founded the popular TechSnips e-learning platform. He mainly focuses on DevOps, system management and automation technologies, as well as various cloud platforms mostly in the Microsoft space. He is a Microsoft Cloud and Datacenter Management MVP who absorbs knowledge from the IT field and explains it in an easy-to-understand fashion. Catch up on Adam's articles at adamtheautomator.com, connect on LinkedIn or follow him on Twitter at @adbertram or the TechSnips Twitter account @techsnips_io.


comments powered by Disqus
Most   Popular