PowerShell Where-Object: Complete Filtering Guide [2024]
!PowerShell Where-Object Filtering
The Where-Object cmdlet is PowerShellβs most powerful filtering tool, allowing you to select objects from a pipeline based on property values, conditions, and complex logic. Understanding Where-Object is essential for effective data manipulation and automation.
Whether youβre filtering services, files, Active Directory users, or any other objects, Where-Object provides flexible, powerful filtering capabilities. This comprehensive guide covers everything from basic filtering to advanced techniques with real-world examples.
Table of Contents
- What is Where-Object?
- Why Use Where-Object?
- Basic Syntax
- Comparison Operators
- Filter by Value
- Multiple Conditions
- Pattern Matching
- Filtering Arrays
- Filtering by Date
- Filtering Objects
- Contains Operator
- In and NotIn Operators
- Match Operator (Regex)
- Filtering Null Values
- Where Method vs Where-Object
- Performance Optimization
- Real-World Use Cases
- Common Mistakes
- Best Practices
- FAQs
What is Where-Object? {#what-is-where-object}
Where-Object is a cmdlet that filters objects in the PowerShell pipeline based on property values and conditions. It evaluates each object and returns only those that match your specified criteria.
Key Characteristics:
- Pipeline-based filtering
- Works with any object type
- Supports complex logic
- Multiple syntax options
- Can access object properties and methods
# Basic structure
Get-Command | Where-Object { $_.Name -like "Get-*" }
# Simplified syntax (PowerShell 3.0+)
Get-Service | Where-Object Status -eq 'Running'
Aliases:
where(short alias)?(shortest alias)
# All equivalent
Get-Service | Where-Object { $_.Status -eq 'Running' }
Get-Service | where { $_.Status -eq 'Running' }
Get-Service | ? { $_.Status -eq 'Running' }
For more PowerShell fundamentals, see our Complete PowerShell Guide.
Why Use Where-Object? {#why-use}
Where-Object is essential for:
1. Data Filtering Select only relevant objects from large datasets:
# Filter running services
Get-Service | Where-Object Status -eq 'Running'
# Filter large files
Get-ChildItem | Where-Object Length -gt 100MB
2. Complex Conditions Combine multiple criteria with logic:
# Stopped services that should be running
Get-Service | Where-Object {
$_.Status -eq 'Stopped' -and $_.StartType -eq 'Automatic'
}
3. Pattern Matching Find objects matching specific patterns:
# Services starting with "Win"
Get-Service | Where-Object Name -like "Win*"
4. Property Validation Check object properties meet requirements:
# Users with email addresses
Get-ADUser -Filter * | Where-Object { $_.EmailAddress -ne $null }
5. Performance Filtering Remove unwanted objects early in pipeline:
# Only process files, not directories
Get-ChildItem -Recurse | Where-Object { -not $_.PSIsContainer } |
ForEach-Object { # expensive operation }
Basic Syntax {#syntax}
Where-Object has two syntax forms:
Script Block Syntax (Flexible)
# Full syntax
Get-Service | Where-Object { $_.Status -eq 'Running' }
# Using automatic variable $_
Get-Process | Where-Object { $_.CPU -gt 100 }
# Multiple statements
Get-ChildItem | Where-Object {
$_.Length -gt 1MB -and
$_.Extension -eq '.log'
}
Simplified Syntax (PowerShell 3.0+)
# Comparison mode
Get-Service | Where-Object Status -eq 'Running'
Get-Process | Where-Object CPU -gt 100
# Equivalent to script block but cleaner for simple comparisons
When to use each:
- Script Block: Complex logic, multiple conditions, method calls
- Simplified: Single property comparisons, cleaner code
Comparison Operators {#operators}
PowerShell provides rich comparison operators for Where-Object:
Equality Operators
| Operator | Description | Example |
|---|---|---|
-eq | Equal to (case-insensitive) | $_.Status -eq 'Running' |
-ne | Not equal to | $_.Status -ne 'Stopped' |
-ceq | Equal to (case-sensitive) | $_.Name -ceq 'PowerShell' |
-cne | Not equal (case-sensitive) | $_.Name -cne 'powershell' |
# Case-insensitive equality
Get-Service | Where-Object Status -eq 'running' # Matches 'Running'
# Case-sensitive equality
Get-Process | Where-Object { $_.Name -ceq 'PowerShell' } # Exact match
Comparison Operators
| Operator | Description | Example |
|---|---|---|
-gt | Greater than | $_.Length -gt 1MB |
-ge | Greater than or equal | $_.CPU -ge 50 |
-lt | Less than | $_.Length -lt 1KB |
-le | Less than or equal | $_.Priority -le 5 |
# Numeric comparisons
Get-Process | Where-Object CPU -gt 100
Get-ChildItem | Where-Object Length -lt 1KB
# Date comparisons
Get-ChildItem | Where-Object LastWriteTime -gt (Get-Date).AddDays(-7)
Pattern Matching Operators
| Operator | Description | Example |
|---|---|---|
-like | Wildcard match | $_.Name -like "Win*" |
-notlike | Not wildcard match | $_.Name -notlike "test*" |
-match | Regex match | $_.Name -match "^\d+" |
-notmatch | Not regex match | $_.Name -notmatch "temp" |
# Wildcard patterns
Get-Service | Where-Object Name -like "Win*"
# Regex patterns
Get-Process | Where-Object { $_.Name -match "^[A-Z]" }
Collection Operators
| Operator | Description | Example |
|---|---|---|
-contains | Array contains value | $array -contains $value |
-notcontains | Array not contains | $array -notcontains $value |
-in | Value in array | $_.Status -in @('Running','Stopped') |
-notin | Value not in array | $_.Status -notin @('Disabled') |
Filter by Value {#filter-value}
Basic filtering by property values:
Filter Services by Status
# Get stopped services
Get-Service | Where-Object Status -eq 'Stopped'
# Get running services
Get-Service | Where-Object { $_.Status -eq 'Running' }
# Script block syntax for multiple checks
$services = Get-Service | Where-Object {
$_.Status -eq 'Stopped' -and
$_.StartType -eq 'Automatic'
}
Output:
Status Name DisplayName
------ ---- -----------
Stopped wuauserv Windows Update
Stopped BITS Background Intelligent Transfer Ser...
Filter Processes by CPU
# High CPU processes
Get-Process | Where-Object CPU -gt 100
# Processes using memory > 100MB
Get-Process | Where-Object { $_.WorkingSet -gt 100MB }
# Sort by CPU descending
Get-Process |
Where-Object CPU -gt 10 |
Sort-Object CPU -Descending |
Select-Object -First 10 Name, CPU
Filter Files by Size
# Large files (>100MB)
Get-ChildItem -Path "C:\Logs" -Recurse |
Where-Object Length -gt 100MB |
Select-Object FullName, @{Name="SizeMB";Expression={[math]::Round($_.Length/1MB,2)}}
# Small files (<1KB)
Get-ChildItem | Where-Object { $_.Length -lt 1KB }
# Files in size range
Get-ChildItem | Where-Object {
$_.Length -ge 1MB -and $_.Length -le 10MB
}
Multiple Conditions {#multiple-conditions}
Combine conditions using logical operators:
AND Operator
# Stopped automatic services
Get-Service | Where-Object {
$_.Status -eq 'Stopped' -and
$_.StartType -eq 'Automatic'
}
# Large log files
Get-ChildItem -Path "C:\Logs" -Recurse | Where-Object {
$_.Extension -eq '.log' -and
$_.Length -gt 10MB
}
# High CPU and memory processes
Get-Process | Where-Object {
$_.CPU -gt 100 -and
$_.WorkingSet -gt 500MB
}
OR Operator
# Running or stopped services (not disabled)
Get-Service | Where-Object {
$_.Status -eq 'Running' -or
$_.Status -eq 'Stopped'
}
# Log or text files
Get-ChildItem | Where-Object {
$_.Extension -eq '.log' -or
$_.Extension -eq '.txt'
}
# Multiple service names
Get-Service | Where-Object {
$_.Name -eq 'wuauserv' -or
$_.Name -eq 'BITS' -or
$_.Name -eq 'Spooler'
}
NOT Operator
# Services not running
Get-Service | Where-Object { -not ($_.Status -eq 'Running') }
# Equivalent to
Get-Service | Where-Object Status -ne 'Running'
# Files that are not readonly
Get-ChildItem | Where-Object { -not $_.IsReadOnly }
Complex Logic
# Parentheses for precedence
Get-Service | Where-Object {
($_.Status -eq 'Running' -and $_.StartType -eq 'Automatic') -or
($_.Status -eq 'Stopped' -and $_.StartType -eq 'Manual')
}
# Multiple AND/OR conditions
Get-ChildItem -Recurse | Where-Object {
(
$_.Extension -eq '.log' -or
$_.Extension -eq '.txt'
) -and
$_.LastWriteTime -gt (Get-Date).AddDays(-30) -and
$_.Length -gt 1MB
}
Pattern Matching {#pattern-matching}
Use wildcards and regex for flexible matching:
Like Operator (Wildcards)
# Services starting with "Win"
Get-Service | Where-Object Name -like "Win*"
# Services containing "Net"
Get-Service | Where-Object { $_.Name -like "*Net*" }
# Files ending with specific pattern
Get-ChildItem | Where-Object Name -like "*_backup_*.zip"
# Multiple wildcard patterns
Get-Service | Where-Object {
$_.Name -like "Win*" -or
$_.Name -like "Net*"
}
Wildcard Characters:
*= Zero or more characters?= Exactly one character
# Single character wildcard
Get-ChildItem | Where-Object Name -like "file?.txt" # file1.txt, fileA.txt
# Combined wildcards
Get-ChildItem | Where-Object Name -like "log_????_*.txt" # log_2024_01.txt
Match Operator (Regex)
# Services starting with capital letter
Get-Service | Where-Object { $_.Name -match "^[A-Z]" }
# Files with date pattern
Get-ChildItem | Where-Object { $_.Name -match "\d{4}-\d{2}-\d{2}" }
# Email pattern in text
Get-Content "users.txt" | Where-Object { $_ -match "\w+@\w+\.\w+" }
# Extract matches
Get-ChildItem | Where-Object { $_.Name -match "(\d+)" } | ForEach-Object {
[PSCustomObject]@{
FileName = $_.Name
Number = $matches[1]
}
}
Filtering Arrays {#filter-arrays}
Filter array elements:
# Filter numeric array
$numbers = 1..100
$even = $numbers | Where-Object { $_ % 2 -eq 0 }
# Filter string array
$names = "John", "Jane", "Bob", "Alice"
$names | Where-Object { $_.Length -gt 3 }
# Filter object array
$users = @(
[PSCustomObject]@{Name="John"; Age=30}
[PSCustomObject]@{Name="Jane"; Age=25}
[PSCustomObject]@{Name="Bob"; Age=35}
)
$users | Where-Object Age -gt 28
Filtering by Date {#filter-date}
Date-based filtering:
Recent Files
# Files modified in last 7 days
$since = (Get-Date).AddDays(-7)
Get-ChildItem | Where-Object LastWriteTime -gt $since
# Files from today
$today = (Get-Date).Date
Get-ChildItem | Where-Object {
$_.LastWriteTime -ge $today
}
# Files between dates
$start = Get-Date "2024-01-01"
$end = Get-Date "2024-12-31"
Get-ChildItem | Where-Object {
$_.CreationTime -ge $start -and
$_.CreationTime -le $end
}
Event Log Filtering
# Events from last hour
$lastHour = (Get-Date).AddHours(-1)
Get-WinEvent -LogName System | Where-Object TimeCreated -gt $lastHour
# Events from specific date
Get-WinEvent -LogName System | Where-Object {
$_.TimeCreated -gt "2024-02-01" -and
$_.TimeCreated -lt "2024-02-02"
}
Contains Operator {#contains}
Check if collection contains a value:
# Check if service name is in list
$criticalServices = @('wuauserv', 'BITS', 'Spooler')
Get-Service | Where-Object { $criticalServices -contains $_.Name }
# Files with specific extensions
$extensions = @('.log', '.txt', '.csv')
Get-ChildItem | Where-Object { $extensions -contains $_.Extension }
# Processes in allowed list
$allowedProcesses = @('explorer', 'powershell', 'chrome')
Get-Process | Where-Object { $allowedProcesses -contains $_.Name.ToLower() }
In and NotIn Operators {#in-notin}
Check if value is in collection:
# Services with specific statuses
Get-Service | Where-Object Status -in @('Running', 'Stopped')
# Exclude specific services
Get-Service | Where-Object Name -notin @('wuauserv', 'BITS')
# Files with allowed extensions
Get-ChildItem | Where-Object {
$_.Extension -in @('.ps1', '.psm1', '.psd1')
}
# Processes not in exclusion list
$exclude = @('System', 'svchost', 'dwm')
Get-Process | Where-Object Name -notin $exclude
Match Operator (Regex) {#match-regex}
Regular expression matching:
Basic Regex Patterns
# Services starting with digits
Get-Service | Where-Object { $_.Name -match "^\d" }
# Files with version numbers
Get-ChildItem | Where-Object { $_.Name -match "v\d+\.\d+" }
# Email addresses
$emails = @("user@example.com", "invalid.email", "admin@test.com")
$emails | Where-Object { $_ -match "^[\w\.-]+@[\w\.-]+\.\w+$" }
Advanced Regex
# IP address pattern
Get-Content "config.txt" | Where-Object {
$_ -match "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"
}
# Date pattern (YYYY-MM-DD)
Get-ChildItem | Where-Object {
$_.Name -match "\d{4}-\d{2}-\d{2}"
}
# Phone numbers
$contacts = Get-Content "contacts.txt"
$contacts | Where-Object { $_ -match "\d{3}-\d{3}-\d{4}" }
Filtering Null Values {#null-values}
Handle null or empty properties:
# Objects with non-null property
Get-ADUser -Filter * | Where-Object EmailAddress -ne $null
# Objects with empty strings
Get-ChildItem | Where-Object { [string]::IsNullOrEmpty($_.Extension) }
# Objects with null or whitespace
$data | Where-Object { -not [string]::IsNullOrWhiteSpace($_.Description) }
# Filter out nulls from array
$array = 1, 2, $null, 4, $null, 6
$array | Where-Object { $null -ne $_ }
Where Method vs Where-Object {#where-method}
PowerShell 4.0+ adds .Where() method:
Comparison
# Where-Object cmdlet (slower)
Get-Process | Where-Object CPU -gt 100
# .Where() method (faster)
(Get-Process).Where({ $_.CPU -gt 100 })
Where() Options
# Default mode
$processes = Get-Process
$processes.Where({ $_.CPU -gt 100 })
# First N matches
$processes.Where({ $_.CPU -gt 10 }, 'First', 5)
# Last N matches
$processes.Where({ $_.CPU -gt 10 }, 'Last', 5)
# Until condition false
$processes.Where({ $_.CPU -gt 10 }, 'Until')
# Skip until condition true
$processes.Where({ $_.CPU -gt 10 }, 'SkipUntil')
# Split on condition
$processes.Where({ $_.CPU -gt 100 }, 'Split')
Performance Comparison
# Benchmark
$data = 1..10000
# Where-Object
Measure-Command {
$data | Where-Object { $_ % 2 -eq 0 }
}
# ~180ms
# .Where() method
Measure-Command {
$data.Where({ $_ % 2 -eq 0 })
}
# ~45ms
π‘ Pro Tip: Use .Where() method for large datasets (>1000 items) for 3-4x performance improvement.
Performance Optimization {#performance}
Early Filtering
# Slow - filter late
Get-ChildItem -Path "C:\Logs" -Recurse |
Where-Object Extension -eq '.log' |
Get-Content
# Fast - filter early with -Filter
Get-ChildItem -Path "C:\Logs" -Filter "*.log" -Recurse |
Get-Content
Simplified Syntax
# Slower - script block
Get-Service | Where-Object { $_.Status -eq 'Running' }
# Faster - simplified syntax
Get-Service | Where-Object Status -eq 'Running'
Pre-compute Comparisons
# Inefficient - recomputes date each iteration
Get-ChildItem | Where-Object {
$_.LastWriteTime -gt (Get-Date).AddDays(-7)
}
# Efficient - compute once
$cutoff = (Get-Date).AddDays(-7)
Get-ChildItem | Where-Object LastWriteTime -gt $cutoff
Real-World Use Cases {#use-cases}
Use Case 1: Find Stale User Accounts
# AD users inactive for 90+ days
$staleDate = (Get-Date).AddDays(-90)
Get-ADUser -Filter * -Properties LastLogonDate |
Where-Object {
$_.LastLogonDate -lt $staleDate -and
$_.Enabled -eq $true
} |
Select-Object Name, LastLogonDate, SamAccountName
Use Case 2: Clean Old Log Files
# Delete logs older than 30 days
$cutoff = (Get-Date).AddDays(-30)
Get-ChildItem -Path "C:\Logs" -Filter "*.log" -Recurse |
Where-Object {
$_.LastWriteTime -lt $cutoff -and
$_.Length -gt 0
} |
Remove-Item -Force -WhatIf
Use Case 3: Monitor High-Resource Processes
# Alert on high CPU/memory processes
$cpuThreshold = 80
$memThreshold = 500MB
Get-Process | Where-Object {
$_.CPU -gt $cpuThreshold -or
$_.WorkingSet -gt $memThreshold
} | ForEach-Object {
Write-Warning "$($_.Name): CPU=$($_.CPU), Memory=$([math]::Round($_.WorkingSet/1MB))MB"
}
Use Case 4: Filter Security Events
# Failed login attempts
Get-WinEvent -FilterHashtable @{
LogName='Security'
ID=4625
} | Where-Object {
$_.TimeCreated -gt (Get-Date).AddHours(-24)
} | Select-Object TimeCreated, Message -First 50
Use Case 5: Find Duplicate Files
# Files with same name in different folders
$files = Get-ChildItem -Path "C:\Data" -Recurse -File
$files | Group-Object Name |
Where-Object Count -gt 1 |
ForEach-Object {
[PSCustomObject]@{
FileName = $_.Name
Count = $_.Count
Locations = ($_.Group.DirectoryName -join '; ')
}
}
Common Mistakes {#mistakes}
β Mistake 1: Using -eq with Arrays
# Wrong - returns filtered array, not boolean
$services = Get-Service
if ($services -eq 'Running') { # This filters, doesn't compare
# ...
}
# Correct - check property
if ($services[0].Status -eq 'Running') {
# ...
}
β Mistake 2: Forgetting Script Block
# Wrong - syntax error
Get-Service | Where-Object $_.Status -eq 'Running'
# Correct
Get-Service | Where-Object { $_.Status -eq 'Running' }
# Or
Get-Service | Where-Object Status -eq 'Running'
β Mistake 3: Case-Sensitive When Not Needed
# Inefficient - forces case-sensitive
Get-Service | Where-Object { $_.Name -ceq 'wuauserv' }
# Better - case-insensitive (default)
Get-Service | Where-Object Name -eq 'wuauserv'
β Mistake 4: Recomputing in Loop
# Slow - recomputes each iteration
Get-ChildItem | Where-Object {
$_.LastWriteTime -gt (Get-Date).AddDays(-7)
}
# Fast - compute once
$week = (Get-Date).AddDays(-7)
Get-ChildItem | Where-Object LastWriteTime -gt $week
β Mistake 5: Using Where-Object When Not Needed
# Unnecessary - Get-ChildItem has -Filter
Get-ChildItem | Where-Object Name -like "*.log"
# Better - use cmdlet filtering
Get-ChildItem -Filter "*.log"
Best Practices {#best-practices}
β Use Simplified Syntax for Simple Comparisons
# Good - clean and readable
Get-Service | Where-Object Status -eq 'Running'
β Pre-compute Values Outside Filter
# Good - compute once
$threshold = (Get-Date).AddDays(-30)
Get-ChildItem | Where-Object LastWriteTime -gt $threshold
β Filter Early in Pipeline
# Good - filter before expensive operations
Get-ChildItem -Filter "*.log" |
Where-Object Length -gt 10MB |
Get-Content -Tail 100
β Use .Where() for Large Datasets
# Good for 1000+ items
$largeArray.Where({ $_ -gt 100 })
β Prefer Cmdlet Filtering Over Where-Object
# Better - use native filtering
Get-ADUser -Filter "Enabled -eq `$true"
# Instead of
Get-ADUser -Filter * | Where-Object Enabled -eq $true
Frequently Asked Questions {#faqs}
Q: Whatβs the difference between Where-Object and Where()?
A: Where-Object is a cmdlet (slower). .Where() is a method (3-4x faster for large datasets).
# Cmdlet (universal, slower)
Get-Process | Where-Object CPU -gt 100
# Method (PowerShell 4.0+, faster)
(Get-Process).Where({ $_.CPU -gt 100 })
Q: Can I use Where-Object with custom objects?
A: Yes, any object with properties:
$custom = [PSCustomObject]@{Name="John"; Age=30}
@($custom) | Where-Object Age -gt 25
Q: How do I filter by multiple values?
A: Use -in operator:
Get-Service | Where-Object Status -in @('Running','Stopped')
Q: Can Where-Object modify objects?
A: No, it only filters. Use ForEach-Object to modify:
# Filter then modify
Get-Service |
Where-Object Status -eq 'Stopped' |
ForEach-Object { $_.Start() }
Q: How do I filter by nested properties?
A: Access nested properties in script block:
Get-Process | Where-Object { $_.MainModule.FileVersionInfo.ProductVersion -like "10.*" }
Q: Whatβs faster: -Filter or Where-Object?
A: Cmdlet -Filter parameter is always faster:
# Fastest
Get-ADUser -Filter "Enabled -eq `$true"
# Slower
Get-ADUser -Filter * | Where-Object Enabled -eq $true
Q: Can I use Where-Object on $null?
A: Check for null first:
if ($null -ne $data) {
$filtered = $data | Where-Object Status -eq 'Active'
}
Q: How do I filter empty arrays?
A: Check .Count property:
$arrays | Where-Object { $_.Count -gt 0 }
Q: Can I filter by calculated properties?
A: Yes, use script block:
Get-ChildItem | Where-Object {
($_.Length / 1MB) -gt 100
}
Q: How do I negate a condition?
A: Use -not or -ne:
# Method 1
Get-Service | Where-Object { -not ($_.Status -eq 'Running') }
# Method 2 (cleaner)
Get-Service | Where-Object Status -ne 'Running'
Conclusion
Mastering Where-Object is essential for PowerShell efficiency. Key takeaways:
β Use simplified syntax for single comparisons β Pre-compute values outside filter blocks β Filter early in pipeline for performance β Use .Where() method for large datasets (3-4x faster) β Prefer cmdlet filtering over Where-Object when available β Combine conditions with -and, -or, -not operators
Related Articles
Core Filtering
- PowerShell Select-Object - Property selection after filtering
- PowerShell Arrays - Array filtering techniques
- PowerShell Get-ChildItem Filter - File system filtering
- PowerShell Get-Content - Read and filter content
Data Structures
- PowerShell Arrays - Working with filtered arrays
- PowerShell Hashtables - Filter hashtable contents
- PowerShell Strings - String filtering and pattern matching
Iteration & Processing
- PowerShell ForEach-Object - Process filtered data
- PowerShell For Loops - Loop through filtered results
- PowerShell While Loops - Conditional filtering loops
Control Flow & Logic
- PowerShell If-Else Statement - Conditional logic with filtering
- PowerShell Switch Statement - Switch-based filtering
- PowerShell Try-Catch - Error handling during filtering
File Operations
- PowerShell Delete Files - Filter and delete files
- PowerShell Rename Files - Filter files to rename
- PowerShell List Files - Filter and list files
- PowerShell Copy-Item - Filter and copy files
Date & Pattern Matching
- PowerShell DateTime Format - Filter by date values
- PowerShell Replace Strings - Text pattern replacement
- PowerShell Strings - Regex and string pattern matching
Variables & Functions
- PowerShell Variables - Store filter criteria
- PowerShell Functions - Create filtering functions
- PowerShell Hashtables - Use hashtables for filter parameters
System Administration
- PowerShell Get-Process - Filter processes
- PowerShell Get CPU Usage - Filter by resource usage
- PowerShell Get Memory Usage - Filter by memory
- PowerShell Get File Properties - Filter by file attributes
Active Directory & Security
- PowerShell Active Directory Guide - Filter AD objects
- PowerShell AD Security Guide - Filter security events
- DSACLS Permission Management - Filter permissions
- PowerShell Get-ADUser - Filter AD users
Output & Export
- PowerShell Output to File - Output filtered results
- PowerShell Export CSV - Export filtered data
- PowerShell Import CSV - Filter imported data
- PowerShell Format Table - Format filtered output
Performance & Optimization
- PowerShell Performance Tuning - Optimize filter performance
- PowerShell Benchmarking - Compare filtering methods
Comprehensive Guides
- Complete PowerShell Guide - Full PowerShell with filtering
- Complete PowerShell Tutorial - Comprehensive course
- PowerShell Tutorial Complete - Full tutorial
You can find more topics about PowerShell automation on the ActiveDirectoryTools home page.