PowerShell Pipeline Variables: $_, $PSItem and -PipelineVariable

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.

VariableWhat it holdsWhere you use it
$_ / $PSItemThe current object in this stepForEach-Object, Where-Object, script blocks, process blocks
-PipelineVariable (-pv)The latest object a chosen command sent onAny later command in the same pipeline
$inputAn enumerator over the inputFunctions, script blocks and .ps1 files
-OutVariable (-ov)Every output of a command, collectedDuring 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