PowerShell Where-Object: Filter Results Like a Pro

Every time you run a cmdlet that returns more data than you need, Where-Object is the tool to trim it down. If Get-Process returns 80 processes and you only care about the ones using more than 100MB of memory, one PowerShell Where-Object expression filters the entire result in a single line. This guide covers the full syntax, simplified comparison style, string and numeric filtering, multiple conditions, and when to use the faster .Where() method instead.
Quick Answer / TL;DR
# Classic script block
Get-Service | Where-Object { $_.Status -eq 'Running' }
# Simplified syntax (PS3+)
Get-Service | Where-Object Status -eq 'Running'
Basic Where-Object Syntax
Where-Object evaluates a script block for each pipeline object and passes through only those for which the block returns $true. The current object is always $_ inside the block:
# Filter running services
Get-Service | Where-Object { $_.Status -eq 'Running' }
# Filter files larger than 10MB
Get-ChildItem C:\Logs -Recurse | Where-Object { $_.Length -gt 10MB }
# Filter processes by name
Get-Process | Where-Object { $_.Name -like 'chrome*' }
Status Name DisplayName
------ ---- -----------
Running Audiosrv Windows Audio
Running BFE Base Filtering Engine
Running BrokerInfrastructure Connected Devices Platform Service
The curly braces { } are required around the script block. Inside, reference the current pipeline object with $_ and its properties with $_.PropertyName.
Simplified Comparison Syntax
PowerShell 3+ supports a more readable syntax for simple comparisons — no script block required:
# Old style (always works)
Get-Service | Where-Object { $_.Status -eq 'Stopped' }
# Simplified style (PS3+): Where-Object Property Operator Value
Get-Service | Where-Object Status -eq 'Stopped'
# More examples
Get-Process | Where-Object CPU -gt 10
Get-ChildItem C:\Logs | Where-Object Length -gt 1MB
Get-Module -ListAvailable | Where-Object Version -ge 2.0
The simplified syntax works for any single comparison using standard PowerShell operators. It’s more readable for simple filters. Use the script block form for complex expressions with multiple conditions or method calls.
Filtering Strings with -like and -match
# -like uses wildcards: * (any characters) and ? (single character)
Get-Service | Where-Object { $_.Name -like 'W*' }
# -match uses regex (case-insensitive by default)
Get-Process | Where-Object { $_.Name -match '^(chrome|firefox|msedge)$' }
# -notlike and -notmatch for negation
Get-ChildItem C:\Users\Public\Documents | Where-Object { $_.Name -notlike '~$*' }
# String contains — use -like with wildcards
Get-EventLog -LogName System -Newest 100 |
Where-Object { $_.Message -like '*failed*' }
Status Name DisplayName
------ ---- -----------
Stopped WinDefend Windows Defender Antivirus Service
Running WSearch Windows Search
Running WWAN AutoConfig WWAN AutoConfig
Filtering Numbers with -gt, -lt
# Processes using more than 200MB memory
Get-Process | Where-Object { $_.WorkingSet -gt 200MB } |
Select-Object Name, @{ Name='MemMB'; Expression={ [math]::Round($_.WorkingSet/1MB,1) } }
# Files modified in the last 24 hours
$cutoff = (Get-Date).AddHours(-24)
Get-ChildItem C:\Logs | Where-Object { $_.LastWriteTime -gt $cutoff }
# Event log errors (EventID between 1000 and 2000)
Get-EventLog -LogName Application -Newest 500 |
Where-Object { $_.EventID -ge 1000 -and $_.EventID -le 2000 }
Name MemMB
---- -----
chrome 523.2
outlook 187.4
Multiple Conditions with -and/-or
Combine conditions inside the script block using -and and -or:
# Both conditions must be true
Get-Process | Where-Object { $_.CPU -gt 5 -and $_.WorkingSet -gt 100MB }
# Either condition triggers inclusion
Get-Service | Where-Object { $_.Status -eq 'Stopped' -or $_.StartType -eq 'Disabled' }
# Negate with -not
Get-ChildItem C:\Logs -Recurse | Where-Object { -not $_.PSIsContainer } # Files only
Where-Object vs .Where() Method
For in-memory collections (already fully loaded), the .Where() method is significantly faster than piping through Where-Object:
$processes = Get-Process # Load all into memory first
# Pipeline version — slightly slower due to pipeline overhead
$processes | Where-Object { $_.CPU -gt 10 }
# .Where() method — faster for in-memory collections
$processes.Where({ $_.CPU -gt 10 })
# .Where() with mode parameter (PS5+)
$processes.Where({ $_.CPU -gt 10 }, 'First', 3) # First 3 matches
Use Where-Object when streaming from cmdlets. Use .Where() when filtering an already-loaded array for better performance.
Common Errors and Fixes
-
Using = instead of -eq inside script block:
Where-Object { $_.Status = 'Running' }assigns the string to$_.Statusrather than comparing it, and always evaluates to$true. Every object passes through. Use-eqfor equality comparison inside script blocks. -
Forgetting curly braces around the script block:
Where-Object $_.Status -eq 'Running'is a parse error or produces unexpected results. The script block form requires{ }. The simplified form does not use curly braces:Where-Object Status -eq 'Running'.
Related Cmdlets / See Also
Wrapping Up
Where-Object is the essential pipeline filter: it keeps only the objects you want and discards the rest. Use the simplified syntax for single-property comparisons, the script block form for complex logic. For maximum performance on in-memory collections, prefer the .Where() method. Your next step: combine Where-Object with Sort-Object and Export-Csv to build a targeted system report.


