Learn PowerShell - Pipeline & Object Manipulation
Episode 8 of 31

Learn PowerShell - Pipeline & Object Manipulation

Flowing objects between command lines with the pipeline: the object-not-text concept, filtering with Where-Object, property selection with Select-Object, sorting with Sort-Object, grouping with Group-Object, aggregation with Measure-Object, plus parallel ForEach-Object in PowerShell 7.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 7 you built functions that accept input and call other commands. In episode 6 you processed collections with ForEach-Object. Now it's time to understand what connects it all: the pipeline. The pipeline is the heart of PowerShell — and like a heart, its workings are often misunderstood by beginners.

Many assume the pipeline flows text — far from it. This misconception is what leads people to write regex expressions to process output that should just be read as object properties. Episode 8 sets things straight: the pipeline flows objects, and objects carry structure that can be filtered, selected, sorted, and grouped.

Objects, Not Text

When you run Get-Process, what appears on screen is a neat table. But that table is just a printout — behind it are objects with properties like ProcessName, CPU, WS, and callable methods. The screen shows a representation; the pipeline carries the real objects.

Viewing hidden properties
Get-Process pwsh | Select-Object ProcessName, Id, CPU, WS
Get-Process pwsh | Get-Member

Get-Member unpacks the object structure: the list of properties, methods, and types. This is the key to exploration — when you're stuck wondering "what properties are available?", Get-Member is the answer.

Chaining and $_

The pipeline chains commands with |. The left command's output becomes the right command's input. Inside cmdlets like Where-Object and ForEach-Object, the $_ variable points to the object currently being processed:

Pipeline chaining
Get-Process |
    Where-Object { $_.WS -gt 100MB } |
    Sort-Object WS -Descending |
    Select-Object ProcessName, Id, WS

Read it like a sentence: "take all processes, keep only those using more than 100 MB, sort descending, then pick the columns". Each stage receives objects, processes them, and passes them on.

Where-Object: Filtering

Where-Object filters — it passes only objects that meet the condition:

Where-Object full syntax
Get-Service | Where-Object { $_.Status -eq "Running" }
Get-Process | Where-Object { $_.CPU -gt 10 }

PowerShell 3 and above provide a more concise simplified syntax: property, operator, value — without $_ and the braces:

Where-Object simplified
Get-Service | Where-Object Status -eq "Running"
Get-Process | Where-Object WS -gt 100MB
Get-ChildItem /data | Where-Object Extension -in @(".log", ".txt")

The simplified syntax reads like a natural filter and is shorter. Save the full syntax for complex conditions involving expressions or multiple properties at once.

Select-Object: Selecting and Shaping

Select-Object picks the properties you want — trimming objects into a concise "identity card":

Selecting properties
Get-Process pwsh | Select-Object ProcessName, Id, WS

Several frequently used modifiers:

  • -First, -Last, -Skip — limit the number of items or skip some.
  • -Unique — removes duplicates.
  • -ExpandProperty — takes a property value as a raw value, not an object.
First, Unique, ExpandProperty
Get-Process | Select-Object -First 5 ProcessName, Id
Get-Process | Select-Object -ExpandProperty ProcessName
Get-Service | Select-Object -ExpandProperty Name | Select-Object -Unique

The difference between Select-Object Name and Select-Object -ExpandProperty Name matters: the former produces a wrapped object with the Name property, the latter produces a plain string. -ExpandProperty is used when the next stage wants the raw value — for example as the -Name for another cmdlet.

Calculated Properties

To create a new column from a calculation, use a hashtable: the column name in the Name key, the expression in the Expression key:

Calculated property
Get-Process pwsh |
    Select-Object ProcessName,
        @{Name = "MemoriMB"; Expression = { [math]::Round($_.WS / 1MB, 1) }},
        @{Name = "CPUIntensif"; Expression = { $_.CPU -gt 50 }}

This code adds a MemoriMB column from a calculation and a CPUIntensif column from a comparison. The hashtables are all written inside code blocks — outside the code, just remember the concept: Name for the label, Expression for the formula.

Sort-Object: Sorting

Sort-Object sorts by one or more properties:

Sorting
Get-ChildItem /data -File | Sort-Object Length -Descending
Get-Service | Sort-Object Status, Name

Multiple properties are sorted in sequence: Status first, then Name for those with the same status. -Descending reverses the order.

Group-Object: Grouping

Group-Object gathers objects with the same property value — like grouping cards by suit:

Grouping
Get-Service | Group-Object Status
Get-ChildItem /data -File | Group-Object Extension

The output shows Name (the group value), Count (number of members), and Group (the member list). From one line you know the distribution of service statuses or the file types in a folder.

Measure-Object: Counting and Aggregating

Measure-Object sums up numeric data — total, average, min-max values:

Aggregation
Get-ChildItem /data -File | Measure-Object Length -Sum -Average -Maximum -Minimum
Get-Service | Measure-Object

Without a numeric property, Measure-Object counts the number of items — useful as a replacement for Count inside a pipeline.

ForEach-Object -Parallel (PowerShell 7)

Most pipelines process items one by one. PowerShell 7 adds -Parallel: processing many items concurrently — like a conveyor belt that now has many lanes:

ForEach-Object -Parallel
$urls = @("https://a.example", "https://b.example", "https://c.example")
$urls | ForEach-Object -Parallel {
    $r = Invoke-WebRequest $_ -UseBasicParsing
    Write-Output "$_ -> $($r.StatusCode)"
} -ThrottleLimit 5

Two important notes. First, -ThrottleLimit limits the number of parallel tasks so the system isn't overloaded. Second, the parallel block runs in a separate runspace — outer variables aren't visible; send values via $using: if needed.

Warning

Parallelism speeds up work that waits on I/O — like network requests — but it's not a universal cure. For pure computation work, speed can be lost to runspace overhead. Measure first: if the workload waits on the network or disk, -Parallel helps; if it just spins the CPU, consider the regular order.

Practical Exercise

Chain all the concepts together: a report of the most memory-hungry processes on the system.

laporan-proses.ps1
Get-Process |
    Where-Object { $_.WS -gt 50MB } |
    Sort-Object WS -Descending |
    Select-Object -First 5 ProcessName, Id,
        @{Name = "MemoriMB"; Expression = { [math]::Round($_.WS / 1MB, 1) }} |
    Format-Table -AutoSize

Conclusion

Episode 8 reveals how the pipeline works: objects flow between lines, not text; $_ points to the object being processed; Where-Object filters with both full and simplified syntax; Select-Object selects, limits, removes duplicates, expands properties, and computes new columns via calculated properties; Sort-Object sorts; Group-Object groups; Measure-Object aggregates; and ForEach-Object -Parallel speeds up I/O-waiting work in PowerShell 7.

Key takeaways:

  • The pipeline flows objects — use Get-Member to unpack their structure.
  • Where-Object filters, Select-Object shapes, the rest arranges.
  • The simplified Where-Object syntax is more concise for simple filters.
  • Calculated properties allow derived columns from calculations.
  • -Parallel for I/O-waiting loads, with -ThrottleLimit as the brake.

Object processing is half the story — objects have to come from somewhere. In episode 9 we learn working with files & folders: reading, writing, copying, moving, and exploring the filesystem as the biggest data source for your scripts. See you in episode 9!

Learn PowerShell - Pipeline & Object Manipulation | Learn PowerShell