PowerShell Pipeline Explained: Chain Commands Together

PowerShell Pipeline Explained: Chain Commands Together

PowerShell Tips Editor 4 min read
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-String or Format-Table) and then pipe it to Where-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 1 or 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.

Send-Item -To