Object Manipulation
Note
Master PowerShell object manipulation with Select-Object, Where-Object, ForEach-Object, calculated properties, and more.
Overview
PowerShell works with objects, not text. Every cmdlet returns objects with properties and methods. Mastering object manipulation is the key to unlocking PowerShell's full potential. This is what makes PowerShell different from traditional shells - you don't parse text, you work with structured data.
Understanding PowerShell Objects
What is an Object?
An object is a structured piece of data with:
- Properties: Data about the object (like Name, Size, Status)
- Methods: Actions you can perform on the object (like Kill, Start, Stop)
# Get a process object
$proc = Get-Process | Select-Object -First 1
# Properties - data about the object
$proc.Name # The process name
$proc.CPU # CPU time used
$proc.WS # Working set (memory)
# Methods - actions on the object
$proc | Get-Member -MemberType Method
$proc.Kill() # Kill the process
Exploring Objects
# See all properties and methods
Get-Process | Get-Member
# Just properties
Get-Process | Get-Member -MemberType Property
# Just methods
Get-Process | Get-Member -MemberType Method
# Get type name
(Get-Process).GetType().FullName # System.Diagnostics.Process
Select-Object (Choose Properties)
Select which properties to display and work with:
Basic Selection
# Select specific properties
Get-Process | Select-Object Name, CPU, WS
# Select first N items
Get-Process | Select-Object -First 10
# Select last N items
Get-ChildItem | Select-Object -Last 5
# Skip and select
Get-Process | Select-Object -Skip 10 -First 5 # Skip first 10, get next 5
# Get unique values
1,2,2,3,3,3,4 | Select-Object -Unique # 1,2,3,4
Expand Property
# Get just the values (not an object with that property)
Get-Process | Select-Object -ExpandProperty Name
# Returns: "chrome", "powershell", "code", etc.
# vs regular select (returns objects)
Get-Process | Select-Object Name
# Returns: @{Name="chrome"}, @{Name="powershell"}, etc.
# Useful for getting a simple list
$processNames = Get-Process | Select-Object -ExpandProperty Name
# $processNames is now a simple string array
Calculated Properties
Create new properties on-the-fly:
# Basic calculated property
Get-Process | Select-Object Name, @{Name="MemoryMB";Expression={$_.WS/1MB}}
# Multiple calculated properties
Get-ChildItem | Select-Object Name,
@{Name="SizeMB";Expression={[math]::Round($_.Length/1MB, 2)}},
@{Name="Modified";Expression={$_.LastWriteTime.ToString("yyyy-MM-dd")}}
# Shorter syntax (Label or L instead of Name, E instead of Expression)
Get-Process | Select-Object Name,
@{L="MemoryMB";E={[math]::Round($_.WS/1MB, 2)}},
@{L="CPUTime";E={[math]::Round($_.CPU, 2)}}
Where-Object (Filter Objects)
Filter objects based on conditions:
Basic Filtering
# Long syntax (explicit)
Get-Process | Where-Object {$_.CPU -gt 100}
Get-Service | Where-Object {$_.Status -eq "Running"}
Get-ChildItem | Where-Object {$_.Length -gt 1MB}
# Short syntax (PowerShell 3.0+)
Get-Process | Where-Object CPU -GT 100
Get-Service | Where-Object Status -EQ "Running"
Get-ChildItem | Where-Object Length -GT 1MB
Complex Filtering
# Multiple conditions with -and
Get-Process | Where-Object {($_.CPU -gt 50) -and ($_.WS -gt 100MB)}
# Multiple conditions with -or
Get-Service | Where-Object {($_.Status -eq "Running") -or ($_.StartType -eq "Automatic")}
# Negation with -not
Get-ChildItem | Where-Object {-not ($_.PSIsContainer)} # Files only
# String matching
Get-Process | Where-Object {$_.Name -like "chrome*"}
Get-Service | Where-Object {$_.DisplayName -match "Windows"}
Common Filter Patterns
# Not null
Get-Process | Where-Object {$null -ne $_.Company}
# Exists in array
$allowedUsers = "admin", "user1", "user2"
Get-LocalUser | Where-Object {$_.Name -in $allowedUsers}
# Property exists and has value
Get-Process | Where-Object {$_.Description -and $_.Description.Length -gt 0}
# Multiple property checks
Get-ChildItem | Where-Object {
($_.Extension -in @(".txt", ".log")) -and
($_.LastWriteTime -lt (Get-Date).AddDays(-30))
}
ForEach-Object (Process Each Item)
Perform actions on each object in the pipeline:
Basic Usage
# Long syntax
Get-Process | ForEach-Object {
Write-Output "$($_.Name) is using $($_.WS) bytes"
}
# Short syntax with member access
Get-Process | ForEach-Object Name # Just get the Name property
Get-Process | ForEach-Object {$_.Name} # Same thing
Performing Actions
# Modify each object
"file1.txt", "file2.txt" | ForEach-Object {
New-Item -Path $_ -ItemType File
Write-Output "Created $_"
}
# Call methods
Get-Service | Where-Object {$_.Name -like "test*"} | ForEach-Object {
$_.Stop()
Write-Output "Stopped $($_.Name)"
}
# Complex processing
Get-ChildItem -Filter "*.log" | ForEach-Object {
$content = Get-Content -Path $_.FullName
$errorCount = ($content | Select-String "ERROR").Count
[PSCustomObject]@{
File = $_.Name
Errors = $errorCount
Size = $_.Length
}
}
Begin, Process, End Blocks
# Advanced ForEach-Object with three sections
Get-Process | ForEach-Object -Begin {
# Runs once before processing
$total = 0
Write-Output "Starting..."
} -Process {
# Runs for each item
$total += $_.WS
} -End {
# Runs once after all items
Write-Output "Total memory: $($total/1GB) GB"
}
Group-Object (Group by Property)
Group objects by a property value:
# Basic grouping
Get-Service | Group-Object -Property Status
# Output shows:
# Count Name Group
# ----- ---- -----
# 150 Running {System.ServiceProcess.ServiceController, ...}
# 50 Stopped {System.ServiceProcess.ServiceController, ...}
# Group by multiple properties
Get-Process | Group-Object -Property Company, Name
# Group files by extension
Get-ChildItem | Group-Object -Property Extension |
Select-Object Name, Count |
Sort-Object -Property Count -Descending
Working with Groups
# Get the groups
$grouped = Get-Process | Group-Object -Property Company
# Access individual groups
$grouped | ForEach-Object {
Write-Output "$($_.Name): $($_.Count) processes"
}
# Get items in a specific group
$msProcesses = ($grouped | Where-Object {$_.Name -eq "Microsoft Corporation"}).Group
Measure-Object (Calculate Statistics)
Calculate statistics on object properties:
# Count items
Get-ChildItem | Measure-Object
# Returns Count
# Sum a property
Get-ChildItem | Measure-Object -Property Length -Sum
# Returns Count and Sum
# Get statistics
1..100 | Measure-Object -Average -Sum -Maximum -Minimum
# Returns Count, Average, Sum, Maximum, Minimum
# Measure strings
Get-Content file.txt | Measure-Object -Line -Word -Character
Sort-Object (Sort by Property)
Sort objects by one or more properties:
# Basic sorting (ascending)
Get-Process | Sort-Object -Property CPU
Get-ChildItem | Sort-Object -Property Length
# Descending
Get-Process | Sort-Object -Property CPU -Descending
# Multiple properties
Get-Process | Sort-Object -Property Company, Name
Get-ChildItem | Sort-Object -Property Extension, Name
# Custom sort logic
Get-Process | Sort-Object -Property @{Expression={$_.WS}; Descending=$true}, Name
Compare-Object (Compare Two Sets)
Compare two collections of objects:
# Basic comparison
$before = Get-Process
Start-Process notepad
Start-Sleep -Seconds 2
$after = Get-Process
Compare-Object -ReferenceObject $before -DifferenceObject $after -Property Name
# Find differences in arrays
$list1 = "apple", "banana", "cherry"
$list2 = "banana", "cherry", "date"
Compare-Object $list1 $list2
# Output:
# InputObject SideIndicator
# ----------- -------------
# apple <= (only in list1)
# date => (only in list2)
# Find only items in second list
Compare-Object $list1 $list2 | Where-Object {$_.SideIndicator -eq "=>"}
Real-World Examples
Example: Process Report with Calculated Properties
Scenario: Generate a detailed process report with custom formatting
Get-Process | Where-Object {$_.CPU -gt 0} |
Sort-Object -Property CPU -Descending |
Select-Object -First 20 Name,
@{L="CPU(s)";E={[math]::Round($_.CPU, 2)}},
@{L="Memory(MB)";E={[math]::Round($_.WS/1MB, 2)}},
@{L="Handles";E={$_.Handles}},
@{L="StartTime";E={$_.StartTime.ToString("yyyy-MM-dd HH:mm")}},
@{L="Runtime";E={
if ($_.StartTime) {
$runtime = (Get-Date) - $_.StartTime
"$($runtime.Days)d $($runtime.Hours)h $($runtime.Minutes)m"
} else { "N/A" }
}} |
Format-Table -AutoSize
Example: File System Analysis
Scenario: Analyze file system usage by type
# Get all files recursively
$files = Get-ChildItem -Path C:\Projects -Recurse -File
# Group by extension and calculate totals
$analysis = $files | Group-Object -Property Extension | ForEach-Object {
$totalSize = ($_.Group | Measure-Object -Property Length -Sum).Sum
[PSCustomObject]@{
Extension = if ($_.Name) { $_.Name } else { "(no extension)" }
FileCount = $_.Count
TotalSizeMB = [math]::Round($totalSize/1MB, 2)
AverageSizeMB = [math]::Round(($totalSize/$_.Count)/1MB, 2)
LargestFile = ($_.Group | Sort-Object Length -Descending | Select-Object -First 1).Name
}
}
# Display sorted by total size
$analysis | Sort-Object -Property TotalSizeMB -Descending | Format-Table -AutoSize
Example: Service Status Dashboard
Scenario: Create a service status summary with grouping
# Get services grouped by status
$serviceStats = Get-Service | Group-Object -Property Status | ForEach-Object {
[PSCustomObject]@{
Status = $_.Name
Count = $_.Count
Services = $_.Group.Name -join ", "
}
}
# Find automatic services that are stopped
$stoppedAutomatic = Get-Service |
Where-Object {($_.StartType -eq "Automatic") -and ($_.Status -eq "Stopped")} |
Select-Object Name, DisplayName, Status, StartType
# Display results
Write-Output "`n===== Service Summary ====="
$serviceStats | Format-Table -AutoSize
if ($stoppedAutomatic) {
Write-Warning "`nAutomatic services that are stopped:"
$stoppedAutomatic | Format-Table -AutoSize
}
Example: Log File Error Analysis
Scenario: Parse log files and count errors by type
# Get log files
$logFiles = Get-ChildItem -Path C:\Logs -Filter "*.log"
# Process each log and extract errors
$errors = $logFiles | ForEach-Object {
$content = Get-Content -Path $_.FullName
$errorLines = $content | Select-String "ERROR"
$errorLines | ForEach-Object {
# Extract error type (assume format: "ERROR: [Type] Message")
if ($_.Line -match "ERROR:\s*\[(.+?)\]") {
[PSCustomObject]@{
File = $_.Filename
Line = $_.LineNumber
ErrorType = $matches[1]
Message = $_.Line
}
}
}
}
# Group errors by type
$errorSummary = $errors | Group-Object -Property ErrorType |
Select-Object @{L="ErrorType";E={$_.Name}}, Count |
Sort-Object -Property Count -Descending
Write-Output "`n===== Error Summary ====="
$errorSummary | Format-Table -AutoSize
Write-Output "`n===== Top 10 Most Recent Errors ====="
$errors | Select-Object -Last 10 | Format-Table File, ErrorType, Line
Combining Commands
Common Pipeline Patterns
# Filter → Sort → Select → Format
Get-Process |
Where-Object {$_.CPU -gt 10} |
Sort-Object -Property CPU -Descending |
Select-Object -First 10 Name, CPU, WS |
Format-Table -AutoSize
# Get → Group → Measure → Select
Get-ChildItem -Recurse |
Group-Object -Property Extension |
ForEach-Object {
[PSCustomObject]@{
Extension = $_.Name
Count = $_.Count
TotalSize = ($_.Group | Measure-Object Length -Sum).Sum
}
} |
Sort-Object -Property TotalSize -Descending
# Filter → ForEach → Create Objects
Get-Service |
Where-Object {$_.Status -ne "Running"} |
ForEach-Object {
[PSCustomObject]@{
Service = $_.Name
Status = $_.Status
StartType = $_.StartType
Action = if ($_.StartType -eq "Automatic") { "Should be running!" } else { "OK" }
}
}
Tips & Tricks
Use -ExpandProperty for Simple Lists
# Returns objects with Name property
Get-Process | Select-Object Name
# Returns simple string array
Get-Process | Select-Object -ExpandProperty Name
# Use -ExpandProperty when you need a simple list for other commands
$names = Get-Process | Select-Object -ExpandProperty Name
$names -contains "chrome" # Works with simple array
Calculated Properties for Formatting
Group-Object with Hash Tables
Where-Object vs. Where() Method
Select-Object Doesn't Change Original
Calculated Properties Performance
# Don't calculate same thing repeatedly
# BAD:
Get-ChildItem | Select-Object Name,
@{L="SizeMB";E={$_.Length/1MB}},
@{L="SizeKB";E={$_.Length/1KB}} # Accesses Length twice
# Better: Calculate once if needed in script
$files = Get-ChildItem
$files | ForEach-Object {
$sizeBytes = $_.Length
[PSCustomObject]@{
Name = $_.Name
SizeMB = $sizeBytes/1MB
SizeKB = $sizeBytes/1KB
}
}
Related Topics
- The Pipeline - Understanding how objects flow
- Arrays & Collections - Working with collections
- Functions - Creating pipeline-aware functions
- Formatting Output - Displaying object data