PowerShell: Practical Use of PipelineVariable Common Parameter

Introduction

The PipelineVariable Common Parameter is one of those PowerShell's "hidden gems" that are often overlooked, and not widely used. When specified, the PipelineVariable parameter stores the objects from the pipeline in a variable named after the assigned parameter value. For instance:

Get-Process -PipelineVariable Process |
Select-Object @{N='Name'; E={$Process.ProcessName}}

Here we assign the value 'Process' to the PipelineVariable parameter, so as long as the pipeline is active $Process will refer to the objects that result from the execution of Get-Process. Once the objects are piped to Select-Object, we can use the Process variable as replacement for $_ or $PSItem.

Note: The Tee-Object cmdlet provides similar functionality than PipelineVariable. Although there are important differences between the two, the most obvious one is that one is a command and the other is a parameter. Tee-Object  would probably make a good topic for a blog post some other time.

PipelineVariable Example #1

That was a straightforward but not that useful, we are simply replacing the object in the pipeline (or '$_') with the Process variable. Let's now take a look at the following example:

PS C:\> Get-Process -PipelineVariable Process | 
<#1#>Select-Object -First 1 |
<#2#>Select-Object -ExpandProperty Modules | 
<#3#>Select-Object ModuleName, Size, @{N='ProcessName'; E={$Process.ProcessName}}

ModuleName   Size ProcessName
----------   ---- -----------
AcroRd32.exe 2536 AcroRd32   
ntdll.dll    1984 AcroRd32   
wow64.dll     340 AcroRd32   
wow64win.dll  500 AcroRd32   
wow64cpu.dll   36 AcroRd32  

In this command, PipelineVariable still refers to the objects produced by Get-Process, but there are now three consecutive Select-Object cmdlets that modify the original Process objects, for clarity each one is numbered with comments.

  1. The first occurrence of Select-Object gets rid of all process objects except for the first one. It is basically the same as applying a filter.
  2. The next Select-Object cmdlet in the pipeline expands the Modules property. At this point, all other properties are removed since only Modules was selected (and expanded). After expanding the Modules property we end up with objects of type System.Diagnostics.ProcessModule, as opposed to the original System.Diagnostics.Process objects. Tip: The Get-Member cmdlet returns the objects' type.
  3. The third and last Select-Object combines properties of the original Process objects with the new ProcessModule objects. The output includes the ProcessName -which is obtained by adding the Process pipeline variable in a custom property-  and the ModuleName and Size properties that are directly collected from the pipeline. This may not seem like a big deal but it wouldn't have been possible without PipelineVariable.

PipelineVariable Example #2

This is another command that takes advantage of PipelineVariable. It is a little lengthier and more complex than the previous one but probably more useful as well.

<#First command#> Get-ADUser -Filter {Department -eq 'Technical Support'} -Properties EmailAddress -PipelineVariable User |
<#Second command#>Get-ADPrincipalGroupMembership |
<#Third command#> ForEach-Object -Process {
    $ADObj = [PSCustomObject] @{'LastName' = $User.Surname
                                'FirstName' = $User.GivenName
                                'SamAccountName' = $User.SamAccountName
                                'ADUserSID' = $User.SID
                                'EmailAddress' = $User.EmailAddress
                                'GroupName' = $_.Name
                                'GroupCategory' = $_.GroupCategory
                                'ADGroupSID' = $_.SID}

    Export-Csv -InputObject $ADObj -Path ~\Documents\ADUser_Group_Details.csv -Append -NoTypeInformation
   
}

This pseudo script is in fact a single pipeline structure, it uses the Active Directory module to find the groups that each user from the Technical Support Department is member of. It looks simple enough but here is the catch: Get-ADPrincipalGroupMembership returns ADGroup objects, once those objects are in the pipeline, how can we retrieve the user properties from the first command via pipeline? The answer is, as expected, with PipelineVariable. Let's analyze each command.

  1. First command: Finds all Active Directory users with Technical Support as value for the Department property, adds the EmailAddress property to the ADUser object and assigns the value "User" to the PipelineVariable parameter. In all subsequent commands we will be able to reference the objects produced by this command with the variable $User.
  2. Second command: Pipeline parameter binding takes place and each ADUser object is passed to the Get-ADPrincipalGroupMembership cmdlet, that finds all the groups that each user belongs to and then passes the ADGroup objects down the pipeline, one at a time.
  3. Third command: Executes the script block for each object generated by the previous command. Within the script block, the PSCustomObject type accelerator builds a new object. Note that  the key value pairs inside the hash table make use of both the $_ ($PSItem) and the $User variable, the pipeline variable from the first command. This clearly shows that the resulting object is a combination of properties of ADUser and ADGroup objects, which is possible thanks to the PipelineVariable parameter.

The output of the command above, is a csv file with the following headers:

  • LastName - User property, comes from $User variable
  • FirstName - User property, comes from $User variable
  • SamAccountName - User property, comes from $User variable
  • ADUserSID - User property, comes from $User variable
  • EmailAddress - User property, comes from $User variable
  • GroupName - Group property, comes from $PSItem
  • GroupCategory - Group property, comes from $PSItem
  • ADGroupSID - Group property, comes from $PSItem

Conclusion

The main takeaway is that with PipelineVariable it is possible to "save" the output from one command and use it later even though those objects are no longer in the pipeline or were transformed by other commands. Scripts with several lines of code can be replaced with a simple set of commands combined with PipelineVariable. The use cases are typically very specific but there is no doubt that PipelineVariable is a very useful PowerShell resource that is worth looking at.

Feel free to leave comments and questions below and thanks for reading this far.

The full and commented source code for this post is available in my GitHub repository.