Loops
Note
Learn how to repeat actions using different types of loops in PowerShell.
Overview
Loops let you repeat code multiple times without writing it over and over. You use loops when you need to process each item in a collection, repeat an action until a condition is met, or perform an operation a specific number of times. PowerShell has several types of loops, each suited for different situations.
Loop Types Overview
foreach # Best for: Iterating through a collection (most common)
ForEach-Object # Best for: Pipeline operations
for # Best for: Counting with specific start/end/increment
while # Best for: Loop while condition is true (check first)
do-while # Best for: Loop at least once, then while condition is true
do-until # Best for: Loop at least once, then until condition is true
Key Points
foreachis best for collections (arrays, file lists, etc.)ForEach-Objectworks in the pipeline with|foris best when you need precise control over iteration- Use
breakto exit a loop early - Use
continueto skip to the next iteration
ForEach Loop (Most Common)
Basic Syntax
Common Use Cases
Iterate through an array:
$colors = "Red", "Green", "Blue"
foreach ($color in $colors) {
Write-Output "Current color: $color"
}
Output:
Loop through a range of numbers:
# Use range operator (..) with foreach
foreach ($i in 1..10) {
Write-Output $i
}
# Works with letters too
foreach ($letter in 'a'..'e') {
Write-Output $letter
}
Output:
Process files in a directory:
$files = Get-ChildItem -Path C:\Temp -Filter *.txt
foreach ($file in $files) {
Write-Output "File: $($file.Name) - Size: $($file.Length) bytes"
# Do something with each file
$content = Get-Content -Path $file.FullName
Write-Output "Lines: $($content.Count)"
}
Loop through hash table:
$servers = @{
"SQL01" = "192.168.1.10"
"WEB01" = "192.168.1.20"
"FILE01" = "192.168.1.30"
}
foreach ($server in $servers.GetEnumerator()) {
Write-Output "Server: $($server.Key) - IP: $($server.Value)"
Test-Connection -ComputerName $server.Value -Count 1 -Quiet
}
ForEach-Object (Pipeline)
Basic Syntax
$collection | ForEach-Object {
# $_ or $PSItem contains the current item
}
# Shorter alias version (common but not recommended for scripts)
$collection | % { }
When to Use
Use ForEach-Object when you're already in a pipeline:
# Process each process returned by Get-Process
Get-Process | ForEach-Object {
Write-Output "$($_.Name) is using $($_.WS / 1MB) MB"
}
# Convert file sizes to MB
Get-ChildItem C:\Temp | ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
SizeMB = [math]::Round($_.Length / 1MB, 2)
}
}
# Restart multiple services
"Spooler", "W32Time" | ForEach-Object {
Restart-Service -Name $_ -Force
}
For Loop (Counter-Based)
Basic Syntax
Common Use Cases
Count from 1 to 10:
Count backwards:
Skip by 2 (even numbers):
Access array by index:
$fruits = "Apple", "Banana", "Cherry", "Date"
for ($i = 0; $i -lt $fruits.Count; $i++) {
Write-Output "Item $i : $($fruits[$i])"
}
Output:
While Loop
Basic Syntax
Common Use Cases
Wait for a condition:
$service = Get-Service -Name "Spooler"
while ($service.Status -ne "Running") {
Write-Output "Waiting for service to start..."
Start-Sleep -Seconds 2
$service.Refresh() # Update service status
}
Write-Output "Service is now running!"
Process until variable changes:
$count = 0
$maxAttempts = 5
while ($count -lt $maxAttempts) {
Write-Output "Attempt $($count + 1) of $maxAttempts"
# Try something
$success = Test-Connection -ComputerName "Server01" -Count 1 -Quiet
if ($success) {
Write-Output "Connection successful!"
break # Exit loop early
}
$count++
Start-Sleep -Seconds 5
}
Do-While Loop
Basic Syntax
When to Use
Use when you need to run code at least once before checking the condition:
# Menu system that runs at least once
do {
Write-Output "`n===== Menu ====="
Write-Output "1. Option One"
Write-Output "2. Option Two"
Write-Output "3. Exit"
$choice = Read-Host "Enter choice"
switch ($choice) {
"1" { Write-Output "You chose option 1" }
"2" { Write-Output "You chose option 2" }
"3" { Write-Output "Exiting..."; break }
default { Write-Output "Invalid choice" }
}
} while ($choice -ne "3")
Do-Until Loop
Basic Syntax
Example
$attempts = 0
do {
$attempts++
Write-Output "Attempt #$attempts"
$result = Test-Connection -ComputerName "Server01" -Count 1 -Quiet
if (-not $result) {
Start-Sleep -Seconds 5
}
} until ($result -eq $true -or $attempts -ge 5)
if ($result) {
Write-Output "Connected successfully!"
} else {
Write-Output "Failed after $attempts attempts"
}
Loop Control
Break (Exit Loop)
# Exit loop completely when condition is met
foreach ($number in 1..100) {
if ($number -eq 10) {
Write-Output "Found 10, stopping!"
break # Exits the loop
}
Write-Output $number
}
# Output: 1, 2, 3, 4, 5, 6, 7, 8, 9, Found 10, stopping!
Continue (Skip to Next Iteration)
# Skip certain items
foreach ($number in 1..10) {
if ($number % 2 -eq 0) {
continue # Skip even numbers
}
Write-Output $number
}
# Output: 1, 3, 5, 7, 9 (odd numbers only)
Nested Loops with Break
# Break out of nested loops
$found = $false
foreach ($i in 1..5) {
foreach ($j in 1..5) {
if ($i * $j -eq 12) {
Write-Output "Found: $i x $j = 12"
$found = $true
break # Exits inner loop only
}
}
if ($found) {
break # Exits outer loop
}
}
Real-World Examples
Example: Retry Logic with Exponential Backoff
Scenario: Try to connect to a server with increasing wait times between attempts
$maxRetries = 5
$retryCount = 0
$success = $false
$serverName = "Server01"
while ($retryCount -lt $maxRetries -and -not $success) {
$retryCount++
Write-Output "Attempt $retryCount of $maxRetries..."
$success = Test-Connection -ComputerName $serverName -Count 1 -Quiet
if (-not $success) {
# Exponential backoff: 2, 4, 8, 16, 32 seconds
$waitTime = [math]::Pow(2, $retryCount)
Write-Output "Failed. Waiting $waitTime seconds..."
Start-Sleep -Seconds $waitTime
}
}
if ($success) {
Write-Output "Successfully connected to $serverName"
} else {
Write-Output "Failed to connect after $maxRetries attempts"
}
Example: Batch Processing Files
Scenario: Process files in batches to avoid overwhelming the system
$files = Get-ChildItem -Path C:\Logs -Filter *.log
$batchSize = 10
$processedCount = 0
for ($i = 0; $i -lt $files.Count; $i += $batchSize) {
# Get current batch
$endIndex = [math]::Min($i + $batchSize - 1, $files.Count - 1)
$batch = $files[$i..$endIndex]
Write-Output "Processing batch: files $($i + 1) to $($endIndex + 1)"
foreach ($file in $batch) {
# Process each file in the batch
$content = Get-Content -Path $file.FullName
# Do something with content
$processedCount++
}
# Optional: pause between batches
if ($i + $batchSize -lt $files.Count) {
Write-Output "Pausing before next batch..."
Start-Sleep -Seconds 2
}
}
Write-Output "Processed $processedCount files in total"
Example: Monitor Until Condition Met
Scenario: Watch disk space and alert when it drops below threshold
$threshold = 20 # GB
$checkInterval = 300 # 5 minutes in seconds
Write-Output "Starting disk space monitor..."
Write-Output "Alert threshold: $threshold GB"
while ($true) {
$drive = Get-PSDrive C
$freeSpaceGB = [math]::Round($drive.Free / 1GB, 2)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Output "[$timestamp] Free space: $freeSpaceGB GB"
if ($freeSpaceGB -lt $threshold) {
Write-Output "ALERT: Free space below threshold!"
# Could send email, write to event log, etc.
break
}
# Wait before next check
Start-Sleep -Seconds $checkInterval
}
Performance Considerations
# SLOW: Using += in a loop creates new array each time
$results = @()
foreach ($i in 1..10000) {
$results += $i # Creates new array 10,000 times!
}
# FAST: Use ArrayList for growing collections
$results = [System.Collections.ArrayList]@()
foreach ($i in 1..10000) {
$results.Add($i) | Out-Null # Modifies existing list
}
# FAST: Pre-size array if you know the count
$results = New-Object int[] 10000
for ($i = 0; $i -lt 10000; $i++) {
$results[$i] = $i
}
# FAST: Use pipeline when possible
$results = 1..10000 | ForEach-Object { $_ }
Tips & Tricks
Use Range Operator for Simple Sequences
ForEach vs ForEach-Object
# ForEach statement (faster, no pipeline overhead)
foreach ($item in $collection) {
# Process $item
}
# ForEach-Object cmdlet (slower, but works in pipeline)
$collection | ForEach-Object {
# Process $_
}
# Use foreach when you have a collection variable
# Use ForEach-Object when you're in a pipeline
Label Loops for Complex Break/Continue
Infinite Loops
Modifying Collection While Looping
# DON'T do this - modifying array while iterating
$numbers = 1..10
foreach ($num in $numbers) {
if ($num -eq 5) {
$numbers += 11 # BAD: Modifying source array
}
}
# DO this - create new array with modifications
$numbers = 1..10
$newNumbers = foreach ($num in $numbers) {
$num
if ($num -eq 5) {
11 # Add to output
}
}
Loop Performance with Pipeline
Related Topics
- Arrays & Collections - Working with collections to loop over
- If Statements & Conditional Logic - Using conditions in loops
- Functions - Creating reusable loop logic
- The Pipeline - Understanding ForEach-Object in pipelines