Put your hands up, have you ever bumped into the -PipelineVariable parameter in PowerShell cmdlets and wondered what it stood for? I have. But you use tab-completion when you are feeling lazy or want to get something done in a blink. Both these situations are not conductive to experimentation.

What is a pipeline

Think of cmdlets as little machine parts. Each takes an input, processes it and gives an output. You start with something raw, place it in the first machine, which processes it and gives you the output. The second machine is capable of taking the output of the first machine as the input, process it and give an output. The third machine takes this as the input, and so on.

Here is an example:

Get-ChildItem -File | Where-Object Length -gt 4096 | Select-Object Name, LastWriteTime, Length

You can also write it like this, by the way:

Get-ChildItem -File |
Where-Object Length -gt 4096 |
Select-Object Name, LastWriteTime, Length

The first command here is Get-ChildItem -File, which lists out all the files in the specified directory (the present working directory in this case). The next command is Where-Object Length -gt 4096, which shows files greater than 4 kb in size. The third command is Select-Object, which picks the three properties we care about.

Leveraging the pipeline for more power

Back in the initial years of PowerShell, cmdlets like Where-Object would not let you use the property name directly. Instead, you had to use the automatic variable $_, which would take the “current object in the pipeline” and filter it. This meant that we had to write the same command chain as:

Get-ChildItem -File | Where-Object { $_.Length -gt 4096 } | Select-Object Name, LastWriteTime, Length

To explain this a little further, imagine that I had ten files in the present working directory, which I listed using Get-ChildItem -File. When using the filter script with Where-Object, $_ would pick one of the ten objects as the output and check whether the condition holds true. After processing it, it would pick the next, process it, pick the next, and so on, until the last object.

Using the PipelineVariable parameter

With later versions of PowerShell, $PSItem replaced $_ in convention (though $_ has not gone away).

But … what if you had to use more than one instance of $_?

In general, it should not matter. You could still do something like:

Get-ChildItem -File |
Where-Object { $PsItem.Length -gt 4096 } |
Select-Object Name, @{ 
    Name = 'Modified'
    Expression = { $PsItem.LastWriteTime }
}, Length

PowerShell should handle it without issues in most cases. But what about readability? You have $PSItem in two places. Why keep track of when what becomes what? This is where -PipelineVariable comes into the picture. And this is how you use it:

Get-ChildItem -File -PipelineVariable 'File' |
Where-Object { $File.Length -gt 4096 } -PipelineVariable 'FilteredFile' |
Select-Object Name, @{ Name = 'Modified'; Expression = { $FilteredFile.LastWriteTime } }, Length

This makes your script much more descriptive. Of course, not everyone is a fan. But this avoids confusion and prevents mix-up. Every time.

Also, no more doing this when wanting to avoid confusions:

$File = Get-ChildItem -File
$FilteredFile = $File | Where-Object { $PSItem.Length -gt 4096 }
$FilteredFile | Select-Object Name, @{ Name = 'Modified'; Expression = { $PSItem.LastWriteTime } }, Length

A note on convention

I thought I will add a short not about the convention in using the functionality. Make the pipeline variable names singular. Treat this like $PSItem; not $PSItems.