A mental model: the pipeline passes objects
In many traditional command prompts, a pipeline passes text: one command prints characters, and the next command tries to interpret those characters. In PowerShell, the pipeline passes .NET objects: structured data with a type, properties (data fields), and methods (actions). This difference is why PowerShell pipelines can be more reliable and readable: you can filter and sort by real properties instead of parsing text.
Think of each pipeline stage as a worker on an assembly line
- Stage 1 produces objects (for example, process objects).
- Stage 2 receives those objects and can filter, reshape, or enrich them.
- Stage 3 receives the updated objects, and so on.
Only at the very end does PowerShell typically format objects into text for display. That formatting is for your screen; it is not what flows through the pipeline.
Side-by-side demo: text output vs object properties
Let’s compare a text-oriented approach with an object-oriented approach. The goal: find processes using more than a certain amount of CPU time.
Text-style thinking (what you might do elsewhere)
In a text pipeline world, you might list processes and then try to match lines with a text tool. In PowerShell, you can convert to text and do string matching, but it is fragile.
# This turns objects into formatted text, then filters by text (fragile pattern) Get-Process | Out-String -Stream | Select-String -Pattern "chrome"This “works” for some cases, but it depends on how the output is formatted and what columns happen to be displayed. It also loses the original object structure.
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
Object-style thinking (PowerShell’s strength)
Instead, filter by properties on the process objects:
# Filter by the Name property (robust) Get-Process | Where-Object { $_.Name -like "*chrome*" }Here, $_ represents the current object flowing through the pipeline. $_.Name is a real property, not a substring in a line of text.
Seeing the difference: formatted view vs real data
When you run Get-Process, you see a table-like display. That display is a formatted view, not the full object. To prove it, select properties that are not shown by default:
# Show properties you choose, even if they aren't in the default table Get-Process | Select-Object -First 5 -Property Name, Id, CPU, StartTimeIf a property isn’t available for a particular process (for example, StartTime may require permissions or may not be accessible), PowerShell will show blanks or errors depending on the situation. The key point: you are working with structured fields.
Inspecting pipeline objects with Get-Member
To use objects effectively, you need to know what type you’re receiving and what properties/methods it exposes. Get-Member (alias: gm) is your microscope.
What type is flowing through the pipeline?
Get-Process | Get-MemberYou’ll see output indicating the object type (commonly System.Diagnostics.Process) and a list of members.
Focus on properties only
Get-Process | Get-Member -MemberType PropertyThis helps you discover what you can filter, sort, and select.
Focus on methods (actions you can call)
Get-Process | Get-Member -MemberType MethodMethods are actions on the object (for example, process-related methods). In pipelines, you’ll more commonly use properties for filtering/sorting and occasionally use methods when you intentionally want to perform an action.
Mini-check: inspect a single object
Sometimes it’s easier to inspect one item rather than the whole set:
$p = Get-Process | Select-Object -First 1 $p | Get-MemberThis reinforces the idea that the pipeline is passing objects you can store, inspect, and reuse.
Core pipeline patterns you’ll use every day
Most practical pipelines are built from a few repeatable patterns. You’ll often combine them in this order:
Get-Something | Where-Object ... | Sort-Object ... | Select-Object ...Grouping is a special case that often comes after filtering and before final formatting.
Filtering with Where-Object
Where-Object keeps only objects that match a condition.
# Keep processes with CPU time greater than 100 seconds Get-Process | Where-Object { $_.CPU -gt 100 }Common comparison operators you’ll see:
-eqequals-nenot equals-gtgreater than-gegreater than or equal-ltless than-leless than or equal-likewildcard match (use*)-matchregex match
Selecting with Select-Object
Select-Object chooses which properties to output (and can also limit the number of objects).
# Show only a few useful fields Get-Process | Select-Object -Property Name, Id, CPU# Take only the first 10 objects Get-Process | Select-Object -First 10You can also create calculated properties to make pipelines more readable:
# Add a calculated property in MB Get-Process | Select-Object Name, Id, @{Name='WorkingSetMB'; Expression = { [math]::Round($_.WorkingSet64 / 1MB, 1) } }Sorting with Sort-Object
Sort-Object orders objects by one or more properties.
# Sort by CPU descending Get-Process | Sort-Object -Property CPU -Descending# Sort by Name, then by Id Get-Process | Sort-Object -Property Name, IdGrouping with Group-Object
Group-Object clusters objects that share the same value for a property.
# Group processes by name (how many instances of each process) Get-Process | Group-Object -Property NameThe output is itself objects (group objects) with useful properties like Name and Count. You can keep working with them:
# Show the most common process names Get-Process | Group-Object Name | Sort-Object Count -Descending | Select-Object -First 10 Name, CountGuided mini-lab 1: from broad to targeted (Get-Process)
Goal: build a readable pipeline that finds the top CPU-consuming processes and shows a clean summary.
Step 1: Start broad
Get-ProcessNotice you get many objects. The screen shows a table, but remember: objects are flowing.
Step 2: Inspect what you can use
Get-Process | Get-Member -MemberType PropertyLook for properties like Name, Id, CPU, WorkingSet64.
Step 3: Filter to “interesting” processes
For example, keep only processes that have recorded CPU time:
Get-Process | Where-Object { $_.CPU -ne $null }Or filter by a name pattern:
Get-Process | Where-Object { $_.Name -like "*svchost*" }Step 4: Sort to find the top consumers
Get-Process | Where-Object { $_.CPU -ne $null } | Sort-Object CPU -DescendingStep 5: Select a small, readable set of fields
Get-Process | Where-Object { $_.CPU -ne $null } | Sort-Object CPU -Descending | Select-Object -First 10 -Property Name, Id, CPUStep 6: Add a calculated property for memory in MB
Get-Process | Where-Object { $_.CPU -ne $null } | Sort-Object CPU -Descending | Select-Object -First 10 Name, Id, CPU, @{Name='WorkingSetMB'; Expression={ [math]::Round($_.WorkingSet64/1MB,1) }}What you built is a classic object pipeline: produce objects → filter → sort → shape the output.
Guided mini-lab 2: grouping and then refining
Goal: identify which process names have the most running instances, then drill into one of them.
Step 1: Group by process name
Get-Process | Group-Object NameStep 2: Sort groups by count
Get-Process | Group-Object Name | Sort-Object Count -DescendingStep 3: Select the top results
Get-Process | Group-Object Name | Sort-Object Count -Descending | Select-Object -First 10 Name, CountStep 4: Drill into a specific name
Pick one of the names you saw (example: svchost) and list its instances with useful properties:
Get-Process | Where-Object { $_.Name -eq 'svchost' } | Sort-Object Id | Select-Object Name, Id, CPU, @{Name='WorkingSetMB'; Expression={ [math]::Round($_.WorkingSet64/1MB,1) }}Notice how grouping gave you a summary, and then you returned to the original objects to investigate details.
Common pipeline “gotchas” that objects help you avoid
1) Formatting too early breaks object pipelines
Cmdlets like Format-Table and Format-List are meant for display. If you use them in the middle of a pipeline, you usually convert rich objects into formatting instructions, making later filtering/sorting unreliable.
# Avoid this pattern (formatting too early) Get-Process | Format-Table Name, CPU | Where-Object { $_.CPU -gt 100 }Instead, filter/sort/select first, and only format at the end if you need custom display.
2) Some properties may be missing or inaccessible
Because you’re dealing with real system objects, some properties can be $null or throw access-related errors. A simple defensive filter can help:
# Keep only processes where StartTime is available Get-Process | Where-Object { $_.StartTime -ne $null } | Select-Object Name, Id, StartTimeQuick reference: a pipeline “recipe card”
| Task | Pattern | Example |
|---|---|---|
| Filter objects | ... | Where-Object { condition } | Get-Process | Where-Object { $_.Name -like '*sql*' } |
| Pick properties | ... | Select-Object Prop1,Prop2 | Get-Process | Select-Object Name,Id,CPU |
| Limit results | ... | Select-Object -First N | ... | Sort-Object CPU -Desc | Select-Object -First 5 |
| Sort results | ... | Sort-Object Prop [-Descending] | Get-Process | Sort-Object WorkingSet64 -Descending |
| Group results | ... | Group-Object Prop | Get-Process | Group-Object Name |