PowerShell Pipeline Explained: Chain Commands Together

The pipe character | exists in almost every shell, but what PowerShell does with it is fundamentally different — and more powerful — than anything CMD or Bash can match. In CMD, | passes text from one command to another. In PowerShell, the pipeline passes full .NET objects with all their properties and methods intact. That distinction is why a single PowerShell pipeline can filter, reshape, sort, and export data in one readable line of code. This guide explains exactly how it works and shows the patterns you’ll use every day.
What the Pipeline Does
When you use | in PowerShell, the output of the left-hand command becomes the input to the right-hand command. Because that output is an object (not text), the receiving cmdlet can access any property of the object directly:
# Without pipeline — two separate operations
$processes = Get-Process
$sorted = $processes | Sort-Object WorkingSet -Descending
# With pipeline — chained in one line
Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 5 Name, WorkingSet
Name WorkingSet
---- ----------
chrome 523452416
outlook 189267968
vscode 157286400
powershell 87654321
explorer 45678901
Each cmdlet in the chain receives the objects produced by the previous cmdlet. The pipeline is evaluated left to right, one object at a time — PowerShell doesn’t wait for the left side to finish before starting the right side.
Passing Objects vs Passing Text
This is the critical difference between PowerShell and text-based shells:
# CMD equivalent (text-only):
# dir | findstr ".txt"
# Result: lines of text you have to parse manually
# PowerShell: objects flow through with all properties
Get-ChildItem C:\Users\Public\Documents | Where-Object { $_.Extension -eq '.txt' }
# You can access properties directly — no string parsing needed
Get-Service | Where-Object { $_.Status -eq 'Running' } | ForEach-Object {
"Service: $($_.Name), Started: $($_.StartType)"
}
Name StartType
---- ---------
AppInfo Manual
AudioSrv Automatic
Because objects flow through, you never need to parse the output of one command to feed another. The properties are already there as typed values.
Filtering with Where-Object
Where-Object (alias: ?) filters the pipeline, passing through only objects that match the condition:
# Classic script block syntax
Get-Process | Where-Object { $_.CPU -gt 10 }
# Simplified comparison syntax (PS3+)
Get-Service | Where-Object Status -eq 'Stopped'
# Multiple conditions
Get-ChildItem C:\Logs -Recurse | Where-Object {
$_.Extension -eq '.log' -and $_.Length -gt 1MB
}
Name CPU
---- ---
chrome 125.3
vscode 18.7
Selecting Properties with Select-Object
Select-Object reduces the number of properties on each object to just what you need:
# Pick specific properties
Get-Process | Select-Object Name, Id, CPU
# Limit result count
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
# Add a calculated property
Get-ChildItem C:\Logs | Select-Object Name, @{Name='SizeMB'; Expression={ [math]::Round($_.Length / 1MB, 2) }}
Name SizeMB
---- ------
app.log 2.45
error.log 0.87
debug.log 5.12
Sorting with Sort-Object
Sort-Object orders pipeline objects by any property:
# Sort ascending (default)
Get-ChildItem C:\Logs | Sort-Object Length
# Sort descending
Get-Process | Sort-Object WorkingSet -Descending
# Sort by multiple properties
Get-Service | Sort-Object Status, Name
Ending the Pipeline: Export, Display, Save
The last cmdlet in a pipeline determines what happens to the output:
# Display in a formatted table
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 | Format-Table
# Save to CSV
Get-Service | Export-Csv -Path 'C:\Reports\services.csv' -NoTypeInformation
# Convert to JSON
Get-Process | Select-Object Name, CPU | ConvertTo-Json
# Count results
(Get-ChildItem C:\Logs -Filter '*.log').Count
# Pipe to a text file
Get-ChildItem C:\Logs | Out-File -FilePath 'C:\Reports\filelist.txt'
# services.csv now contains all service data
# filelist.txt contains the directory listing
Common Errors and Fixes
-
Piping text to object-aware cmdlets breaks silently: If you convert an object to text (using
Out-StringorFormat-Table) and then pipe it toWhere-Object, you’re filtering a text string — not the original object properties. Keep objects as objects until the very end of the pipeline. Only convert to text at the final step. -
Forgetting pipeline output is an array not a single object: When a pipeline produces multiple objects, the result is an array. If you assign pipeline output to a variable and expect a single object, wrap it with
Select-Object -First 1or check the count before accessing properties.
Related Cmdlets / See Also
Wrapping Up
The PowerShell pipeline passes full .NET objects between cmdlets — not text. This is the foundation of everything powerful in PowerShell: filtering with Where-Object, shaping with Select-Object, ordering with Sort-Object, and exporting with Export-Csv. Keep objects as objects until the final step. Your next step: build a one-liner that lists the top 5 processes by memory and exports them to a CSV.


