PowerShell · lesson 2 of 12
Sorting, Grouping, and Measuring Objects
Sort-Object, Group-Object, and Measure-Object turn a list of live objects into an answer — until one of them sorts numbers as text, which is the same trap grep-and-sort scripts fall into.
By SkillByExample · Published
Three cmdlets turn a list of objects into an answer: Sort-Object, Group-Object, and
Measure-Object. All three trust the type of the value they’re given — which is exactly
where the first bug in this lesson comes from.
The bug: sorting numbers as text
$numbers = "10","2","33","4"
$numbers | Sort-Object
10
2
33
4
Barely sorted at all. Every value here is a string, and Sort-Object compares strings
character by character — "1" sorts before "2" regardless of what comes after it. This is
the same failure mode as forgetting -n on sort in bash.
The fix: sort the actual numbers
$numbers = "10","2","33","4"
$numbers | ForEach-Object { [int]$_ } | Sort-Object
2
4
10
33
Casting each value to [int] before it reaches Sort-Object fixes it — now it’s comparing
integers, not characters. This never comes up with Get-Process’s own numeric properties,
because they’re typed as numbers from the moment the cmdlet creates them; it only bites
values that started life as text, like $numbers here, or anything read from a CSV file.
Sorting real typed data
Get-Process | Sort-Object -Property WorkingSet64 -Descending |
Select-Object -First 5 -Property ProcessName, @{Name="MemoryMB";Expression={[math]::Round($_.WorkingSet64/1MB,1)}}
ProcessName MemoryMB
----------- --------
node 1129.10
node 1126.10
node 1083.40
Code 508.50
mc-fw-host 384.20
WorkingSet64 is already an int64 on the object Get-Process returns, so this sorts
correctly with no casting needed — the calculated MemoryMB column exists purely to make the
output readable; the sort itself ran on the raw byte count.
Group-Object: counting without pre-sorting
Get-Process | Group-Object -Property ProcessName | Sort-Object -Property Count -Descending |
Select-Object -First 5 -Property Count, Name
Count Name
----- ----
99 svchost
36 Code
20 conhost
15 chrome
14 msedgewebview2
This is the pipeline equivalent of sort | uniq -c | sort -rn — except Group-Object scans
the whole input itself and doesn’t need it pre-sorted first. uniq -c only counts
consecutive matching lines; feeding it unsorted input silently produces wrong counts, a bug
Group-Object structurally can’t have.
Measure-Object: aggregating a property
Get-Process | Measure-Object -Property WorkingSet64 -Sum -Average |
Select-Object -Property Count, @{Name="TotalMB";Expression={[math]::Round($_.Sum/1MB,0)}}, @{Name="AverageMB";Expression={[math]::Round($_.Average/1MB,1)}}
Count TotalMB AverageMB
----- ------- ---------
427 13608.00 31.90
-Sum and -Average both aggregate the same -Property in one pass — this is the pipeline’s
answer to awk '{sum+=$1} END {print sum}', minus the manual accumulator variable.
Practice
1. Find the single heaviest process by memory, using Measure-Object -Maximum.
Get-Process | Measure-Object -Property WorkingSet64 -Maximum | Select-Object -Property Maximum
Maximum
-------
1183973376.00
-Maximum returns the raw largest value, in bytes — Select-Object here doesn’t reach into
which process it belonged to, since Measure-Object’s output is a summary object, not the
original list.
2. Sort processes by CPU time, descending, and show the top three.
Get-Process | Sort-Object CPU -Descending | Select-Object -First 3 -Property ProcessName, CPU
ProcessName CPU
----------- ---
BixbySystray 12331.78
Zoom 4686.47
Docker Desktop 4253.41
Same pattern as the memory sort — CPU is already numeric on the object, so descending sort
needs no casting, unlike the $numbers example at the top of this lesson.
3. Group a list of file extensions and find the most common one, without sorting first.
$files = "report.csv","data.json","notes.txt","summary.csv","log.txt","export.csv"
$files | ForEach-Object { [System.IO.Path]::GetExtension($_) } | Group-Object | Sort-Object Count -Descending | Select-Object -First 1
Count Name Group
----- ---- -----
3 .csv {.csv, .csv, .csv}
Group-Object’s Group property keeps every original value in the group, not just the
count — useful when the next step needs to go back and act on the actual matching items.
Next: variables, arrays, and hashtables — and the quirks in how PowerShell compares them.