PowerShell pipeline variables come in two kinds: the automatic $_ (also called $PSItem), which holds the current object, and the -PipelineVariable common parameter, which saves an object under your own name for later pipeline steps:
Import-Csv .\orders.csv -PipelineVariable order |
Where-Object { [decimal]$_.Total -gt 200 } |
ForEach-Object { "Order $($order.Id) ships to $($PSItem.City)" }
Output:
Order 1001 ships to Austin
Order 1003 ships to Seattle
Here $order still points at the CSV row two commands later, while $PSItem is whatever reached the current command. The sample file is a small orders.csv with five orders from Austin, Denver and Seattle.
I ran every example in PowerShell 7.6 and Windows PowerShell 5.1. Where the two shells disagree, you’ll see both outputs and why.
What are pipeline variables in PowerShell?
A pipeline passes objects from one command to the next, one at a time. Pipeline variables are how the code inside each step gets at those objects.
| Variable | What it holds | Where you use it |
|---|---|---|
| $_ / $PSItem | The current object in this step | ForEach-Object, Where-Object, script blocks, process blocks |
| -PipelineVariable (-pv) | The latest object a chosen command sent on | Any later command in the same pipeline |
| $input | An enumerator over the input | Functions, script blocks and .ps1 files |
| -OutVariable (-ov) | Every output of a command, collected | During and after the pipeline |
Most scripts only need $_. The others solve specific problems, such as reaching back to an earlier object or reading all input at once. If you’re new to the cmdlets themselves, start with my ForEach-Object tutorial.
How do $_ and $PSItem work?
$_ and $PSItem are the same automatic variable with two names. PowerShell sets it to the current object each time a script block runs for a new item.
This filters orders over $100, then builds a line for each one. I mixed both names on purpose:
$orders = Import-Csv .\orders.csv
$orders | Where-Object { [decimal]$_.Total -gt 100 } |
ForEach-Object { "Order $($_.Id) from $($PSItem.City): `$$($_.Total)" }
Output:
Order 1001 from Austin: $245.50
Order 1003 from Seattle: $412.00
Order 1005 from Seattle: $130.75
Where-Object sees each row as $_, and ForEach-Object sees only the rows that passed the filter. The Where-Object filtering guide covers more filter patterns.
Microsoft’s docs use $PSItem in their examples but say $_ is the preferred form, and that’s what you’ll see in most scripts. Pick one and stay consistent.
Inside the Begin and End script blocks of ForEach-Object, $_ is $null, because those blocks don’t run per object.
Where else can you use $_ in PowerShell?
$_ isn’t limited to the two pipeline cmdlets. It also works in the .Where() and .ForEach() array methods, switch conditions, catch blocks, and delay-bind script blocks:
$cities = 'austin', 'denver', 'seattle'
$cities.Where({ $_ -like 's*' }).ForEach({ $_.ToUpper() })
switch ($cities) { { $_.Length -gt 6 } { "$_ has a long name" } }
try { Get-Item .\missing-report.csv -ErrorAction Stop }
catch { "Failed. $($_.Exception.GetType().Name)" }
Get-ChildItem .\orders.csv |
Rename-Item -NewName { "2026-$($_.Name)" } -WhatIf
Output:
SEATTLE
seattle has a long name
Failed. ItemNotFoundException
What if: Performing the operation "Rename File" on target "Item: C:\psfaqs\orders.csv Destination: C:\psfaqs\2026-orders.csv".
The .Where() and .ForEach() methods treat $_ as the current array item. In a switch statement, $_ is the value being tested, as my PowerShell switch statement guide explains.
In a catch block, $_ is the ErrorRecord, not a pipeline object. The post on PowerShell er