Control Flow

Master PowerShell control flow: if/elseif/else, switch statements, for loops, foreach, while loops, do-while, and do-until patterns.

📖 6 min read📅 2026-02-10Scripting

If / ElseIf / Else

The most basic control flow statement:

$score = 85
 
if ($score -ge 90) {
    Write-Host "Grade: A" -ForegroundColor Green
}
elseif ($score -ge 80) {
    Write-Host "Grade: B" -ForegroundColor Cyan
}
elseif ($score -ge 70) {
    Write-Host "Grade: C" -ForegroundColor Yellow
}
else {
    Write-Host "Grade: F" -ForegroundColor Red
}

Single-Line If

# For simple conditions
if ($isAdmin) { Write-Host "Administrator" }
 
# Ternary (PowerShell 7+)
$message = $isAdmin ? "Admin" : "User"

Nested If

$age = 25
$hasTicket = $true
 
if ($age -ge 18) {
    if ($hasTicket) {
        Write-Host "Welcome to the event!"
    }
    else {
        Write-Host "Please purchase a ticket."
    }
}
else {
    Write-Host "Must be 18 or older."
}

Switch Statement

More readable than multiple if/elseif blocks:

$day = (Get-Date).DayOfWeek
 
switch ($day) {
    "Monday"    { Write-Host "Start of the work week" }
    "Friday"    { Write-Host "TGIF!" }
    "Saturday"  { Write-Host "Weekend!" }
    "Sunday"    { Write-Host "Weekend!" }
    default     { Write-Host "Regular day" }
}

Switch with Wildcards

$os = "Windows 11 Pro"
 
switch -Wildcard ($os) {
    "Windows*"  { Write-Host "Windows OS detected" }
    "*Pro*"     { Write-Host "Professional edition" }
    "*Server*"  { Write-Host "Server edition" }
}
# Note: Multiple matches fire! Both "Windows*" and "*Pro*" will match

Switch with Regex

$input = "192.168.1.100"
 
switch -Regex ($input) {
    "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$" {
        Write-Host "Valid IP format: $input"
    }
    "^192\.168\." {
        Write-Host "Private network (Class C)"
    }
    "^10\." {
        Write-Host "Private network (Class A)"
    }
}

Switch on Arrays

$statusCodes = 200, 301, 404, 500, 200
 
switch ($statusCodes) {
    200 { Write-Host "OK" }
    301 { Write-Host "Redirect" }
    404 { Write-Host "Not Found" }
    500 { Write-Host "Server Error" }
}
# Evaluates each item in the array!

For Loop

Classic counting loop:

# Count from 1 to 10
for ($i = 1; $i -le 10; $i++) {
    Write-Host "Number: $i"
}
 
# Count by 2s
for ($i = 0; $i -le 20; $i += 2) {
    Write-Host "Even: $i"
}
 
# Countdown
for ($i = 10; $i -ge 0; $i--) {
    Write-Host "$i..."
}
Write-Host "Liftoff!"

ForEach Loop

Iterate over collections:

# ForEach statement
$fruits = "Apple", "Banana", "Cherry"
foreach ($fruit in $fruits) {
    Write-Host "I like $fruit"
}
 
# ForEach-Object cmdlet (pipeline)
$fruits | ForEach-Object { Write-Host "I like $_" }
 
# ForEach with index
$colors = "Red", "Green", "Blue"
for ($i = 0; $i -lt $colors.Count; $i++) {
    Write-Host "$($i + 1). $($colors[$i])"
}
 
# Practical: Process files
foreach ($file in Get-ChildItem *.txt) {
    $lines = (Get-Content $file).Count
    Write-Host "$($file.Name): $lines lines"
}

ForEach vs ForEach-Object

# ForEach statement - loads ALL items into memory first
foreach ($proc in Get-Process) {
    # All processes loaded, then iterated
}
 
# ForEach-Object - processes one at a time (streaming)
Get-Process | ForEach-Object {
    # Each process handled individually
}
 
# Use ForEach for small collections (faster)
# Use ForEach-Object for large data or pipeline integration

While Loop

Loop while a condition is true:

# Basic while
$count = 0
while ($count -lt 5) {
    Write-Host "Count: $count"
    $count++
}
 
# Wait for a process to start
while (-not (Get-Process -Name "notepad" -ErrorAction SilentlyContinue)) {
    Write-Host "Waiting for Notepad..."
    Start-Sleep -Seconds 2
}
Write-Host "Notepad is running!"
 
# Read user input until valid
$valid = $false
while (-not $valid) {
    $input = Read-Host "Enter a number between 1-10"
    if ($input -match "^\d+$" -and [int]$input -ge 1 -and [int]$input -le 10) {
        $valid = $true
        Write-Host "You entered: $input"
    }
    else {
        Write-Host "Invalid input. Try again."
    }
}

Do-While and Do-Until

Execute at least once, then check condition:

# Do-While: Loop WHILE condition is true
$attempts = 0
do {
    $attempts++
    $guess = Read-Host "Guess the number (1-10)"
} while ($guess -ne "7" -and $attempts -lt 3)
 
# Do-Until: Loop UNTIL condition becomes true
$response = ""
do {
    $response = Read-Host "Type 'quit' to exit"
} until ($response -eq "quit")

Loop Control

Break — Exit the Loop

foreach ($i in 1..100) {
    if ($i -eq 5) {
        Write-Host "Found 5, breaking out!"
        break
    }
    Write-Host $i
}
# Output: 1, 2, 3, 4, Found 5, breaking out!

Continue — Skip to Next Iteration

foreach ($i in 1..10) {
    if ($i % 2 -eq 0) {
        continue  # Skip even numbers
    }
    Write-Host "Odd: $i"
}
# Output: 1, 3, 5, 7, 9

Labels — Control Nested Loops

:outer foreach ($i in 1..5) {
    foreach ($j in 1..5) {
        if ($j -eq 3) {
            continue outer  # Skip to next iteration of outer loop
        }
        Write-Host "i=$i, j=$j"
    }
}

Practical Examples

Example 1: Menu System

do {
    Write-Host "`n=== System Menu ===" -ForegroundColor Cyan
    Write-Host "1. Show running processes"
    Write-Host "2. Show disk space"
    Write-Host "3. Show network info"
    Write-Host "4. Exit"
 
    $choice = Read-Host "`nSelect an option"
 
    switch ($choice) {
        "1" {
            Get-Process | Sort-Object CPU -Descending |
                Select-Object -First 10 Name, CPU, WorkingSet |
                Format-Table -AutoSize
        }
        "2" {
            Get-PSDrive -PSProvider FileSystem |
                Select-Object Name,
                    @{N="Used(GB)"; E={[math]::Round($_.Used/1GB,2)}},
                    @{N="Free(GB)"; E={[math]::Round($_.Free/1GB,2)}} |
                Format-Table -AutoSize
        }
        "3" {
            Get-NetIPAddress -AddressFamily IPv4 |
                Where-Object IPAddress -ne "127.0.0.1" |
                Select-Object InterfaceAlias, IPAddress |
                Format-Table -AutoSize
        }
        "4" { Write-Host "Goodbye!" -ForegroundColor Green }
        default { Write-Host "Invalid option" -ForegroundColor Red }
    }
} while ($choice -ne "4")

Example 2: Retry Logic

$maxRetries = 3
$retryCount = 0
$success = $false
 
while (-not $success -and $retryCount -lt $maxRetries) {
    $retryCount++
    Write-Host "Attempt $retryCount of $maxRetries..."
 
    try {
        # Simulate an operation that might fail
        $result = Invoke-WebRequest "https://api.example.com/health" -TimeoutSec 5
        if ($result.StatusCode -eq 200) {
            $success = $true
            Write-Host "Success!" -ForegroundColor Green
        }
    }
    catch {
        Write-Host "Failed. Retrying in 5 seconds..." -ForegroundColor Yellow
        Start-Sleep -Seconds 5
    }
}
 
if (-not $success) {
    Write-Host "All attempts failed!" -ForegroundColor Red
}

Exercises

  1. Write a script that prints the multiplication table (1-10) for a given number
  2. Create a number guessing game (random 1-100, give hints "higher/lower")
  3. Write a while loop that monitors a folder for new files every 10 seconds
  4. Use a switch statement to categorize HTTP status codes (1xx, 2xx, 3xx, 4xx, 5xx)
  5. Create a menu-driven script to manage files (create, list, delete, rename)

Next: Functions and Scripts — learn to write reusable code!