PowerShell Where-Object: Filter Results Like a Pro

PowerShell Where-Object: Filter Results Like a Pro

PowerShell Tips Editor 4 min read
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 $_.Status rather than comparing it, and always evaluates to $true. Every object passes through. Use -eq for 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.

Send-Item -To