Lesson 4 of 5  ·  Beginner

The Pipeline & Filtering

⏱ ~20 minutes Beginner

What is the Pipeline?

The pipeline is PowerShell's most powerful feature. It lets you chain commands together so the output of one command becomes the input of the next — using the pipe character |.

Instead of processing text like traditional shells, PowerShell passes objects through the pipeline. This means you can access properties and methods at every step.

Your First Pipeline
# Without pipeline — two separate steps
$processes = Get-Process
$sorted = $processes | Sort-Object CPU -Descending

# With pipeline — one fluid expression
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5

NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
112 245.2 312.5 8823.4 1234 1 chrome
56 88.1 104.2 1240.1 5678 1 Code
...
💡
Reading a pipeline: Read it left to right. "Get all processes, then sort them by CPU descending, then take only the first 5." Each | means "and then send that to..."

Where-Object — Filtering

Where-Object (alias: ? or where) filters a collection to only include objects that match a condition. Think of it as a "keep only if" filter.

Where-Object — Two Syntaxes
# SIMPLIFIED syntax (PS 3+) — great for single comparisons
Get-Service | Where-Object Status -eq 'Running'

# SCRIPTBLOCK syntax — needed for complex conditions
# $_ refers to the CURRENT object being evaluated
Get-Service | Where-Object { $_.Status -eq 'Running' }

# Multiple conditions with -and / -or
Get-Process | Where-Object {
    $_.CPU -gt 10 -and $_.WorkingSet -gt 50MB
}

Comparison Operators

PowerShell uses letter-based operators instead of symbols (this avoids confusion with XML/HTML):

OperatorMeaningExample
-eqEqual to$x -eq 5
-neNot equal to$x -ne 5
-gtGreater than$x -gt 10
-geGreater than or equal$x -ge 10
-ltLess than$x -lt 10
-leLess than or equal$x -le 10
-likeWildcard match (* and ?)$name -like "A*"
-notlikeWildcard no match$name -notlike "B*"
-matchRegex match$str -match "^\d+"
-containsArray contains value$arr -contains "x"
-inValue is in array"x" -in $arr
-not / !Logical NOT-not $active
-andLogical AND$a -gt 5 -and $b -lt 10
-orLogical OR$a -eq 1 -or $a -eq 2
Case sensitivity: By default all comparison operators are case-insensitive. For case-sensitive matching, add 'c' prefix: -ceq, -clike, -cmatch.

Select-Object — Choosing Properties

Select-Object lets you pick which properties to keep, limit how many objects you get, or create calculated (custom) properties:

Select-Object
# Keep only specific properties
Get-Process | Select-Object Name, Id, CPU

# Get only first / last N items
Get-ChildItem | Select-Object -First 5
Get-ChildItem | Select-Object -Last 3

# Skip the first N items
Get-Process | Select-Object -Skip 10 -First 5

# Unique values only
Get-Process | Select-Object -ExpandProperty Company -Unique

# Calculated (custom) property
Get-Process | Select-Object Name, @{
    Name = 'Memory (MB)'
    Expression = { [math]::Round($_.WorkingSet / 1MB, 1) }
} | Sort-Object 'Memory (MB)' -Descending

Sort-Object — Sorting Results

Sort-Object
# Sort ascending (default)
Get-Service | Sort-Object Name

# Sort descending
Get-Process | Sort-Object CPU -Descending

# Sort by multiple properties
Get-Service | Sort-Object Status, Name

# Get top 10 memory users
Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 10 | Format-Table Name, Id

ForEach-Object — Processing Each Item

ForEach-Object (alias %) runs a script block for each object in the pipeline. Inside the block, $_ is the current object:

ForEach-Object
# Double each number in a range
1..5 | ForEach-Object { $_ * 2 }
2 4 6 8 10

# Show info about each running service
Get-Service | Where-Object Status -eq Running | ForEach-Object {
    Write-Host "Service: $($_.DisplayName)" -ForegroundColor Green
}

# Use $() for expressions inside strings
Get-ChildItem *.txt | ForEach-Object {
    Write-Host "$($_.Name)$($_.Length) bytes"
}

Group-Object and Measure-Object

Group and Measure
# Count services by status
Get-Service | Group-Object Status
Count Name Group
----- ---- -----
178 Stopped {AarSvc, AJRouter...}
92 Running {AdobeARMservice, Appinfo...}

# Calculate statistics on a property
Get-Process | Measure-Object WorkingSet -Sum -Average -Maximum
Count : 180
Average : 35821234
Sum : 6447822120
Maximum : 652812288

# Count lines in a file
(Get-Content myfile.txt | Measure-Object -Line).Lines

Putting It All Together

Here are some real-world one-liners that combine multiple pipeline stages:

Real-World Pipeline Examples
# Top 5 largest files in a folder
Get-ChildItem C:\Windows -File -Recurse -ErrorAction SilentlyContinue
| Sort-Object Length -Descending
| Select-Object Name, @{N='Size(MB)';E={[math]::Round($_.Length/1MB,2)}}
| Select-Object -First 5
| Format-Table -AutoSize

# Running services sorted by name
Get-Service | Where-Object Status -eq Running | Sort-Object Name | Select-Object Name, DisplayName

# Total disk usage of a folder in MB
Get-ChildItem C:\Logs -Recurse -File
| Measure-Object Length -Sum
| Select-Object @{N='TotalMB';E={[math]::Round($_.Sum/1MB,2)}}

🧪 Try It Yourself

  1. Run Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 | Format-Table Name, Id, CPU -AutoSize
  2. List all Running services: Get-Service | Where-Object Status -eq 'Running'
  3. Count files by extension: Get-ChildItem C:\Windows -File | Group-Object Extension | Sort-Object Count -Descending | Select-Object -First 5
  4. Build your own pipeline — get processes, filter ones using more than 50MB RAM, sort by memory, show Name and memory as MB

Key Takeaways

  • The pipeline (|) passes objects from one command to the next
  • Where-Object filters the collection — use $_ for the current object
  • Comparison operators: -eq -ne -gt -lt -like -match -contains
  • Select-Object picks properties, limits count, creates custom properties
  • Sort-Object orders results; add -Descending to reverse
  • ForEach-Object runs code for each item; $_ is the current item
  • Combine pipeline stages to build powerful one-liner data transformations