Working with Objects

Reviewed & published by Brayan K

By the end of this lesson you'll be able to filter, sort, reshape, and summarise live system data using PowerShell's object pipeline — the one feature that makes it far more powerful than a traditional text-based shell.

Part of the free PowerShell course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1. The Object Pipeline

In a traditional shell the pipe | passes text, so the next command must re-parse that text every time. PowerShell's pipe passes objects — structured .NET values with named properties (like Name, CPU, Id) and methods. That single difference is the whole point of the language: you filter, sort, and select by property name instead of counting columns. Where-Object keeps or drops objects, Sort-Object orders them, and Select-Object picks the columns you care about. Read this worked example, then run it.

Order matters and reads left to right: filter first so later steps do less work, then sort, then select. The Where-Object CPU -gt 1 form (property, operator, value) is the easy way to filter on a single property — no $_ needed.

2. Discover Anything with Get-Member

You never have to guess what an object can do. Pipe any object into Get-Member (alias gm) and it lists every property and method, with types. Properties are the data (Name, CPU); methods are actions you call with () (ToUpper(), GetType()). This is the most important habit in PowerShell — when you're stuck, Get-Member tells you exactly what's available.

# Never guess an object's properties — ask it with Get-Member (alias: gm).
# Pipe ANY object in and it lists every Property and Method, with types.

Get-Process | Get-Member -MemberType Property |
    Select-Object Name, MemberType -First 5

# Now that you KNOW the property names, use them directly:
$p = Get-Process | Select-Object -First 1
"Name: $($p.Name)"          # $(...) runs code inside a string
"Id:   $($p.Id)"
"Type: $($p.GetType().Name)"  # call a METHOD with () — it's a .NET object

Notice $(...) inside the string — that's a subexpression: it runs code and drops the result into the text. Use it whenever you need a property or a method call inside "double quotes".

🎯 Your Turn: filter and sort by property

The pipeline below is almost done. Fill in the two ___ blanks with the property name to filter and sort on, then run it and check your output against the expected lines.

# 🎯 YOUR TURN — replace each ___ then run it.
# A reproducible sample set so your output matches exactly.
$files = @(
    [pscustomobject]@{ Name = 'report.docx'; SizeKB = 320 }
    [pscustomobject]@{ Name = 'photo.png';   SizeKB = 1840 }
    [pscustomobject]@{ Name = 'notes.txt';   SizeKB = 4 }
    [pscustomobject]@{ Name = 'backup.zip';  SizeKB = 9600 }
)

$files |
    Where-Object ___ -gt 300 |        # 👉 the property to filter on: SizeKB
    Sort-Object ___ -Descending |     # 👉 sort by the same property: SizeKB
    Select-Object Name, SizeKB

# ✅ Expected output:
#    Name          SizeKB
#    ----          ------
#    backup.zip      9600
#    photo.png       1840
#    report.docx      320

3. ForEach-Object and the $_ Variable

ForEach-Object (alias %) runs a script block — code in { curly braces } — once for every object in the pipeline. Inside that block, $_ (also written $PSItem) is the current object, exactly like "this item" in a loop. The same $_ appears in any Where-Object { ... } script block too. Get comfortable with it now: $_ is everywhere in PowerShell.

🎯 Your Turn: use $_ inside ForEach-Object

Fill in the one blank so each price is multiplied by 1.2 (adding 20% tax). The hint tells you what ___ should be.

# 🎯 YOUR TURN — ForEach-Object runs a script block once per item.
# Inside the block, $_ is the CURRENT object flowing through the pipe.
$prices = 10, 25, 40

$prices | ForEach-Object {
    $withTax = ___ * 1.2          # 👉 multiply the current item: $_
    "Price $($_) -> with tax $withTax"
}

# ✅ Expected output:
#    Price 10 -> with tax 12
#    Price 25 -> with tax 30
#    Price 40 -> with tax 48

4. Calculated Properties

Select-Object doesn't only pick existing columns — it can build new ones. A calculated property is a small hashtable with two keys: Name (the column label) and Expression (a script block that computes the value, using $_ for the current object). This is how you turn raw bytes into megabytes, or a date into "days ago", right in the pipeline.

# Select-Object can CREATE new properties, not just pick existing ones.
# A calculated property is a hashtable: @{ Name='Label'; Expression={ ... } }.
$files = @(
    [pscustomobject]@{ Name = 'photo.png';  Bytes = 1572864 }
    [pscustomobject]@{ Name = 'backup.zip'; Bytes = 9830400 }
)

$files | Select-Object Name,
    @{ Name = 'SizeMB'; Expression = { [math]::Round($_.Bytes / 1MB, 2) } }

# $_ is the current object; 1MB is a built-in PowerShell constant (1048576).

5. Group-Object & Measure-Object

Because the pipeline carries real data, you can do analysis without exporting to a spreadsheet. Group-Object buckets objects by a property (how many per region?). Measure-Object computes statistics in a single pass: -Sum, -Average, -Maximum, -Minimum, and a plain count. Together they answer real questions straight from the shell.

# Group-Object buckets objects by a property; Measure-Object does stats.
$sales = @(
    [pscustomobject]@{ Region = 'North'; Amount = 100 }
    [pscustomobject]@{ Region = 'South'; Amount = 250 }
    [pscustomobject]@{ Region = 'North'; Amount = 400 }
    [pscustomobject]@{ Region = 'South'; Amount = 150 }
)

# 1) Count how many sales per region:
$sales | Group-Object Region | Select-Object Name, Count

# 2) Total, average, and max across ALL sales — one pass, no loop:
$sales | Measure-Object Amount -Sum -Average -Maximum |
    Select-Object Count, Sum, Average, Maximum

Why This Beats Text Parsing

Here's the same job done the bash way (slicing text with awk) versus the PowerShell way (naming a property). The bash version breaks the moment a column shifts or a name contains a space; the PowerShell version can't, because it never looks at text positions at all.

# THE CONTRAST — same task, two worlds.

# Bash: everything is text, so you slice columns by position and hope.
#   ps aux | awk '{print $11, $3}' | sort -k2 -rn | head -3
#   Break if a column shifts, a name has a space, or the header moves.

# PowerShell: everything is an object, so you name what you want.
Get-Process |
    Sort-Object CPU -Descending |
    Select-Object -First 3 Name, CPU

# No awk, no column counting, no fragile text parsing. You asked for the
# 'CPU' property by NAME — PowerShell knows its type and sorts it numerically.

Common Errors (and the fix)

📋 Quick Reference

CmdletAliasWhat it does
Get-MembergmList an object's properties & methods
Where-Objectwhere / ?Filter — keep objects matching a condition
Select-ObjectselectPick/compute columns, or take -First N
Sort-ObjectsortOrder by a property (-Descending)
ForEach-Objectforeach / %Run a script block per item ($_)
Group-ObjectgroupBucket objects by a property
Measure-ObjectmeasureCount, sum, average, min, max

Mini-Challenge: Biggest CPU Users Report

No blanks this time — just a brief and an outline. Build the full pipeline yourself: filter, sort, then select with a calculated Status column. Run it and check your output against the expected lines in the comments.

# 🎯 MINI-CHALLENGE: Biggest CPU users report
# Use this sample data (don't change it):
$procs = @(
    [pscustomobject]@{ Name = 'chrome'; CPU = 45.2; Threads = 60 }
    [pscustomobject]@{ Name = 'code';   CPU = 23.1; Threads = 30 }
    [pscustomobject]@{ Name = 'idle';   CPU = 0.4;  Threads = 4 }
    [pscustomobject]@{ Name = 'node';   CPU = 12.8; Threads = 18 }
)
#
# 1. Keep only processes with CPU greater than 1   (Where-Object)
# 2. Sort them by CPU, highest first               (Sort-Object -Descending)
# 3. Show Name, CPU, and a calculated 'Status'     (Select-Object)
#    where Status is 'HOT' if CPU -gt 20, else 'ok'
#       @{ Name='Status'; Expression={ if ($_.CPU -gt 20) {'HOT'} else {'ok'} } }
#
# ✅ Expected output:
#    Name    CPU Status
#    ----    --- ------
#    chrome 45.2 HOT
#    code   23.1 HOT
#    node   12.8 ok

# your pipeline here

🎉 Lesson Complete!

Practice quiz

What does the PowerShell pipeline pass between cmdlets?

  • Structured .NET objects
  • Plain text strings
  • File handles
  • JSON only

Answer: Structured .NET objects. PowerShell pipes real objects with named properties, not re-parsed text.

Which cmdlet keeps only objects that match a condition?

  • Select-Object
  • Where-Object
  • Sort-Object
  • Group-Object

Answer: Where-Object. Where-Object filters rows, keeping objects that match the condition.

Which cmdlet picks or computes which columns to keep?

  • Where-Object
  • Measure-Object
  • Select-Object
  • Format-Table

Answer: Select-Object. Select-Object chooses properties and can create calculated ones.

Inside a script block, what does $_ represent?

  • The last error
  • The script name
  • An empty value
  • The current pipeline object

Answer: The current pipeline object. $_ (also $PSItem) is the current object flowing through the pipeline.

Which cmdlet lists an object's properties and methods?

  • Get-Member
  • Get-Property
  • Get-Command
  • Get-Help

Answer: Get-Member. Get-Member (alias gm) reveals every property and method of an object.

How is a calculated property defined in Select-Object?

  • A simple string label
  • A hashtable with Name and Expression keys
  • An array of values
  • A switch parameter

Answer: A hashtable with Name and Expression keys. Use @{ Name='Label'; Expression={ ... } } to compute a new column.

Which cmdlet buckets objects by a property value?

  • Sort-Object
  • Group-Object
  • Where-Object
  • Measure-Object

Answer: Group-Object. Group-Object buckets objects by a shared property, like Region.

Which cmdlet computes Sum, Average, and Maximum in one pass?

  • Count-Object
  • Stat-Object
  • Measure-Object
  • Total-Object

Answer: Measure-Object. Measure-Object with -Sum -Average -Maximum gives stats in one pass.

Where should Format-Table go in a pipeline?

  • First
  • In the middle
  • It can go anywhere
  • Last, because it emits display objects

Answer: Last, because it emits display objects. Format-* cmdlets emit display objects, so they must come last.

How do you sort objects by CPU with the largest first?

  • Sort-Object CPU -Descending
  • Order CPU -Down
  • Sort-Object -Reverse CPU
  • Get-Process -Top CPU

Answer: Sort-Object CPU -Descending. Sort-Object CPU -Descending orders by the CPU property, highest first.

Continue this course

Frequently asked questions

What does it mean that PowerShell passes objects, not text?

When you pipe one command into another, bash hands over a plain string and the next command has to re-parse it. PowerShell hands over the actual .NET object with its named properties and methods intact, so you filter, sort, and select by property name instead of slicing text by column position.

When do I need $_ and when can I leave it out?

Use $_ (the current pipeline object) inside a script block — anything in { curly braces }, like Where-Object { $_.CPU -gt 1 } or ForEach-Object { $_.Name }. The simpler comparison form, Where-Object CPU -gt 1, has no script block, so there is no $_ to write.

What's the difference between Select-Object and Where-Object?

Where-Object filters rows: it keeps or drops whole objects based on a condition. Select-Object chooses columns: it picks which properties to keep (or creates new calculated ones) and can limit how many objects come through with -First. You usually filter first, then select.

How do I discover an object's property names?

Pipe it into Get-Member (alias gm): Get-Process | Get-Member. It lists every property and method with its type. This is the single most useful command for learning what you can do with any object in the pipeline.

Why shouldn't I pipe Format-Table into another command?

Format-* cmdlets emit display objects meant for the screen, not the original data. Anything downstream sees formatting instructions instead of real properties and breaks. Always put Format-Table, Format-List, or Out-* last in the pipeline.