PowerShell Pipeline

Exploring the PipelineVariable Common Parameter in PowerShell

Here's a breakdown of what it can do and how you can use it in conjunction with other custom functions.

Starting in PowerShell V4, a new common parameter was introduced called PipelineVariable which provides a new way to handle the output of data that is being sent out from one command to another via the pipeline. As the name implies, it saves the data to a variable of your choice that you can use later on in the string of commands. There is also an alias for this parameter, pv, that you can use. It is important that you do not actually specify a variable with this parameter, but instead only specify just the name so that way the output from the commands will be saved to that name as a variable. While it is a string name that you supply to the parameter, the data being saved to it can be of any object type.

The example below is rather simple but will show you how versatile this variable is in nested ForEach loops.

Get-Process -Name n* -PipelineVariable  Proc |  ForEach {
Get-WmiObject -Class Win32_Service -ErrorAction SilentlyContinue -Filter "ProcessID='$($Proc.Id)'" -PipelineVariable Service | ForEach {
[pscustomobject]@{
ProcessName = $Proc.Name
PID = $Proc.Id
ServiceName = $Service.Name
ServiceDisplayName = $Service.DisplayName
StartName = $Service.StartName
}
}
} | Format-Table -AutoSize
[Click on image for larger view.] Figure 1. A look at some of the services.

What you see here are all of the services associated with processes starting with the letter N. We use not only one, but two variables thanks to the pipelineVariable parameter to determine the process associated with the service. This works because of the streaming of data that using the PowerShell pipeline which allows all of the data to flow from the first command down to the last command.

Being that all of this is happening because of the streaming approach with PowerShell, throwing a command that stops the streaming for aggregating output, such as Sort-Object, will cause the output to drastically change. The example below will use Get-Service to get a list of all of the services on my computer and then sort that list by the State property. What we will see this time will not be an accurate representation the output that we were expecting.

Get-Service -PipelineVariable  Service |  Sort-Object -Property  State |  ForEach {
[pscustomobject]@{
Name = $Service.Name
DisplayName = $_.DisplayName
}
}
[Click on image for larger view.] Figure 2. Sorting the data while using the pipelinevariable can lead to undesirable effects.

This is just a handful of the services, but you can tell pretty quickly that something isn't quite right here. The last item in the pipeline is saved to the pipeline variable due to the aggregation that is occurring with Sort-Object. This is happening because all of the data is being held up by Get-Service before being sent to Sort-Object to be sorted based on the given sorting parameters. Because of this, the pipelinevariable no longer shows all of the output and isn't a good source of data to be used later on in other commands in the pipeline.

Building a function that allows you to make use of this parameter is easier than you think! The key component when you build your function or script is to include the cmdletbinding attribute that defines your function as an advanced function and opens up this parameter (as well as other common parameters). You can quickly verify this by looking at the command syntax like in the images below:

Without cmdletbinding:

Function Test-Function  {
Param (
[string[]]$Data
)
}

Get-Command Test-Function -Syntax 

Figure 3. Function that doesn't support common parameters.

 

With cmdletbinding:

Function Test-Function  {
[cmdletbinding()]
Param (
[string[]]$Data
)
}

Get-Command Test-Function -Syntax

 

[Click on image for larger view.] Figure 4. Function that does support common parameters.

You can see that the CommandParameter label is used on the function that includes the cmdletbinding attribute which means that we have our PipelineVariable enabled for use.

Once you have done this then you can begin building out your function. The process to do this is a little quirky, for a lack of better words. I found that supporting the pipeline in the function using more traditional means like the Process block only seems to send the first item in the pipeline to the variable as shown below.

 

Function Test-Function  {
[cmdletbinding()]
Param (
[parameter(ValueFromPipeline=$True)]
[object[]]$Data
)
Process {
ForEach ($Item in $Data) {
$Item
}
}
}

1..5|Test-Function -PipelineVariable t|ForEach{$t}

[Click on image for larger view.] Figure 5. Function where the pipelinevariable is not working as expected while support pipeline input.

This does work as expected when using the named parameter.

Test-Function -Data (1..5) -PipelineVariable  t | ForEach {$t} 
[Click on image for larger view.] Figure 6. Example showing the use of pipelinevariable in a custom function.

So how in the world can we make this support pipline input? Well, we can make use of an automatic variable called $Input which takes in all of the pipeline input and treats it like a collection. It can even be placed in the Begin block and it will have all of the pipeline data! This variable does have a different meaning when you are not supporting pipeline input and another catch is that you cannot have a Process block with your pipeline support because it then becomes the single item in the Process block (like $_ and $PSItem).

 

Function Test-Function  {
[cmdletbinding()]
Param (
[parameter(ValueFromPipeline=$True)]
[object[]]$Data
)
$input
}

1..5|Test-Function -PipelineVariable t|ForEach{$t}

[Click on image for larger view.] Figure 7. Example that supports input via the pipeline.

As you can see, by using the $Input automatic variable, we can provide some much needed pipeline support that also works with the PipelineVariable parameter. Of course, this was a rather simple function for demo purposes but you can get the idea on what needs to be done.

That is all for looking at the PipelineVariable and seeing not only how to use, but how we can incorporate it within our custom functions. While this may not be for everyone, it can certainly have its uses when being used within the pipeline.

About the Author

Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.

comments powered by Disqus
Most   Popular