Skip to content
6 min read·Lesson 2 of 8

Objects and the Pipeline

What makes PowerShell PowerShell — every cmdlet emits and consumes .NET objects, not text. The pipeline carries them.

If Bash pipes are streams of text — line-oriented, parsed with grep, awk, sed — PowerShell pipes are streams of .NET objects. Get-Process emits Process objects with properties like Name, Id, CPU, WorkingSet. The next cmdlet in the pipeline receives those objects intact, no parsing required.

This is the single biggest mental shift coming from Bash, and the one that makes PowerShell genuinely productive once it clicks.

The Pipeline Mental Model

Get-Process | Where-Object CPU -gt 100 | Sort-Object CPU -Descending | Select-Object -First 5

# Read: get all processes → keep ones with CPU > 100 → sort by CPU descending → take first 5

Each | passes objects to the next cmdlet. No string parsing. The properties (CPU, Name, etc.) are first-class — no fragile column-indexing as you would do with awk '{print $3}'.

Get-Member: Your Anchor

Get-Process | Get-Member

# Output (abridged):
#   TypeName: System.Diagnostics.Process
#   Name              MemberType
#   ----              ----------
#   Handles           AliasProperty
#   Id                Property
#   CPU               ScriptProperty
#   WorkingSet64      Property
#   Kill              Method
#   WaitForExit       Method

Once you know an object has CPU, WorkingSet64, and Id properties, you can filter, sort, and select on them. Always pipe to Get-Member when you encounter a new cmdlet.

Filtering: Where-Object

# Simple syntax (PowerShell 3+)
Get-Service | Where-Object Status -eq Running
Get-Process | Where-Object WorkingSet -gt 100MB

# Script-block syntax — required for complex expressions
Get-ChildItem | Where-Object { $_.Length -gt 1MB -and $_.Extension -eq ".log" }

# The $_ variable is the current pipeline item.

The alias is ?: Get-Service | ? Status -eq Running.

Projecting: Select-Object

# Pick a subset of properties
Get-Process | Select-Object Name, Id, CPU

# Pick first or last N
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10

# Calculated property
Get-Process | Select-Object Name, @{ Name = "MemoryMB"; Expression = { $_.WorkingSet64 / 1MB } }

# Unique values
Get-EventLog -LogName System | Select-Object Source -Unique

The hashtable form (@{ Name = ...; Expression = { ... } }) is one of the most useful patterns in PowerShell — define new computed columns inline.

Iterating: ForEach-Object

# Run a script block per item
1..10 | ForEach-Object { $_ * $_ }                    # 1,4,9,...,100

Get-ChildItem *.log | ForEach-Object {
    "$($_.Name): $($_.Length) bytes"
}

# Parallel (PowerShell 7+) — major productivity boost for network and IO
$urls | ForEach-Object -Parallel {
    Invoke-WebRequest -Uri $_ -Method HEAD
} -ThrottleLimit 10

Alias is %. -Parallel in pwsh 7 is genuinely useful — common pattern for fan-out HTTP calls or large-file operations.

Sorting and Grouping

Get-Process | Sort-Object CPU -Descending
Get-Service | Group-Object Status
Get-ChildItem | Measure-Object Length -Sum -Average -Maximum

Group-Object is the SQL GROUP BY. Measure-Object is the aggregator.

Building Custom Objects

Sometimes you want to emit your own structured objects from a script:

[pscustomobject]@{
    Name      = "kube-controller"
    Status    = "Running"
    Replicas  = 3
    Image     = "registry.io/kube/controller:v1.27"
}

That object now flows through any subsequent Where-Object, Select-Object, Format-Table, ConvertTo-Json. Building cmdlets that emit custom objects is the path to composable scripts.

Formatting vs Selecting (the Trap)

Newcomers often pipe to Format-Table or Format-List too early:

# WRONG — the formatting cmdlet emits format objects, not the original data.
# Anything piped after this can't see the real properties.
Get-Process | Format-Table Name, Id | Where-Object Id -gt 1000     # broken

# RIGHT — select first, format only at the end (or not at all).
Get-Process | Where-Object Id -gt 1000 | Select-Object Name, Id

Rule: Format-* only at the very end of the pipeline, and only for human display. For programmatic output use Select-Object, ConvertTo-Json, ConvertTo-Csv.

Data In, Data Out

# Read JSON / CSV
$config = Get-Content config.json | ConvertFrom-Json
$users  = Import-Csv users.csv

# Write JSON / CSV
$obj | ConvertTo-Json -Depth 5 | Set-Content out.json
$rows | Export-Csv -NoTypeInformation users.csv

# Read / write files
Get-Content access.log | Select-String "ERROR"
"new line" | Add-Content app.log

# HTTP
$data = Invoke-RestMethod https://api.example.com/users -Headers @{ Authorization = "Bearer $token" }

The bidirectional symmetry — Get-/Set-Content, Import-/Export-Csv, ConvertFrom-/ConvertTo-Json — is intentional and convenient.

A Realistic Pipeline

"Find the top 5 log files in /var/log by size and email me the result":

Get-ChildItem /var/log -Recurse -File -ErrorAction SilentlyContinue |
    Sort-Object Length -Descending |
    Select-Object -First 5 |
    Select-Object FullName, @{ Name='SizeMB'; Expression={ [math]::Round($_.Length / 1MB, 2) } } |
    ConvertTo-Json |
    Out-File top-logs.json

Send-MailMessage -To "ops@example.com" -From "reports@example.com" -Subject "Top logs" -Body (Get-Content top-logs.json -Raw) -SmtpServer smtp.example.com

Notice: no temp files mid-pipeline, no awk, no parsing. Each cmdlet's output is structured input to the next.

The PowerShell Mindset

  • If your script has grep, awk, or string parsing, you are probably ignoring the object that already has the data as a property.
  • Pipe to Get-Member first. Discover the shape. Then write the rest.
  • Resist Format-Table until the last step.
  • Emit [pscustomobject] from your own scripts — composability follows.

With pipelines and objects internalised, the next step is making your own reusable building blocks — functions and modules.

Key Takeaways

  • PowerShell pipelines carry objects, not text — Where-Object and Select-Object filter and project by property.
  • Get-Member shows the shape of any object on the pipeline.
  • ForEach-Object applies a script block to each item.
  • Sort-Object, Group-Object, Measure-Object are the workhorses of analysis.
  • Format-Table / Format-List / ConvertTo-Json shape output for display or further processing.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →