While loops in production automation scripts must always have a maximum iteration count or a timeout guard. A script polling for a service to start can loop forever if the service never responds. Pattern: $attempts = 0; while ($attempts -lt 30 -and (Get-Service xyz).Status -ne 'Running') { Start-Sleep 5; $attempts++ }. If it exits because of the count limit, throw an error—never silently continue.
Control Flow, Loops, and Conditions
Control which code runs and how many times: use if/elseif/else for decisions, switch for multi-branch logic, and for/foreach/while loops to process collections and repeat operations automatically.
🧒 Simple Explanation (ELI5)
Imagine you are sorting packages at a warehouse: if the package is fragile, put it on the gentle shelf; if it is heavy, get help; otherwise handle it normally. That is an if statement. Loops are like repeatedly picking up the next package in the queue until the queue is empty. Automation scripts make the same kind of decisions and repetitions, just for servers and files instead of boxes.
🔧 Why Do We Need It?
- Decision making: the script must behave differently in dev vs. production—an if statement branches the logic.
- Bulk processing: iterating over 100 servers with a foreach loop is one line of code instead of 100 repeated commands.
- Waiting for readiness: a while loop keeps polling until a service starts or a deployment completes, instead of hardcoding a sleep.
- Switch statements: cleaner than long if/elseif chains when branching on a single variable with many known values (environment names, status codes).
⚙️ Technical Explanation
if / elseif / else: evaluates a boolean condition and executes the matching block. Conditions use comparison and logical operators from the previous lesson.
switch: matches a value against multiple patterns. PowerShell switch supports regex matching (-Regex), wildcard matching (-Wildcard), and file mode (-File). The default block runs when no case matches.
foreach (loop keyword): enumerates a collection with a named variable. ForEach-Object (cmdlet): streams pipeline objects. Both serve similar purposes but foreach loops load the collection into memory first and are better for index access and break/continue control.
for: classic index-based loop. while: continues while a condition is true—test before first iteration. do-while: always executes at least once—test after first iteration.
Use switch when you are branching on the same single variable against multiple explicit values (status codes, environment names, exit codes). Use if/elseif when your conditions are complex expressions or involve multiple variables. A switch with 5 cases is far more readable than 5 elseif blocks on the same variable.
📊 Visual Representation
⌨️ Commands / Syntax
# if / elseif / else
$diskPct = 85
if ($diskPct -ge 90) {
Write-Warning "CRITICAL: disk above 90%"
} elseif ($diskPct -ge 80) {
Write-Warning "WARNING: disk above 80%"
} else {
Write-Host "Disk OK: $diskPct%"
}
# switch statement
$environment = "staging"
switch ($environment) {
"dev" { $rg = "rg-dev"; $sku = "Standard_B1s" }
"staging" { $rg = "rg-staging"; $sku = "Standard_D2s_v3" }
"prod" { $rg = "rg-prod"; $sku = "Standard_D4s_v3" }
default { throw "Unknown environment: $environment" }
}
# foreach loop over a collection
$servers = @("web01", "web02", "web03")
foreach ($server in $servers) {
Write-Host "Checking $server..."
$svc = Get-Service -ComputerName $server -Name "wuauserv" -ErrorAction SilentlyContinue
if ($svc -and $svc.Status -eq "Stopped") {
Write-Warning "$server: Windows Update service is stopped"
}
}
# for loop (index-based)
for ($i = 0; $i -lt $servers.Count; $i++) {
Write-Host "Server $($i+1): $($servers[$i])"
}
# while loop with guard
$attempts = 0
$maxAttempts = 12
while ($attempts -lt $maxAttempts) {
$status = (Get-Service -Name "AppServiceProd").Status
if ($status -eq "Running") {
Write-Host "Service is running after $attempts checks"
break
}
Start-Sleep -Seconds 5
$attempts++
}
if ($attempts -ge $maxAttempts) {
throw "Service did not start within $($maxAttempts * 5) seconds"
}
# break and continue
foreach ($file in Get-ChildItem C:\Logs -Filter *.log) {
if ($file.Length -eq 0) { continue } # skip empty files
if ($file.Name -like "CRITICAL*") { break } # stop on critical
Process-LogFile $file
}
💼 Example (Real-world Use Case)
A deployment health-check script iterates over a list of web server hostnames, checks whether the IIS service is running, and builds a report hashtable: healthy servers accumulate in one list, unhealthy ones in another. After the loop, the script emails the report if unhealthy count is above zero, and exits with a non-zero return code to fail the CI/CD pipeline gate.
🧪 Hands-on
- Write an if/else that writes "large" if a number is above 100, "small" otherwise.
- Write a switch statement for three environment names (dev/staging/prod) that assigns a resource group name.
- Loop over an array of five server names with foreach and print each one with a counter: "Server 1: web01".
- Write a while loop that counts to 5 with a 200ms sleep between iterations.
- Add a
continuestatement inside a foreach to skip any server whose name contains "decom".
Write a script that checks disk usage on your local drives using Get-PSDrive -PSProvider FileSystem and categorizes each drive as "OK", "Warning" (above 70% used), or "Critical" (above 90% used). Hint: calculate percentage as ($drive.Used / ($drive.Used + $drive.Free)) * 100. Use if/elseif/else inside a foreach loop over the drives.
🐛 Debugging Scenario
Problem: a while loop that polls for a deployment to complete runs forever and the pipeline hangs.
- Cause: the exit condition is never true because the variable being checked does not update inside the loop—it was assigned before the loop and is not re-queried each iteration.
- Diagnose: add
Write-Host "Iteration $attempts: status = $status"inside the loop. If status never changes, the cmdlet is returning a cached object. - Fix: move the status query inside the loop so it runs fresh each iteration. For API calls, make sure you are not hitting a rate-limited cached endpoint. Always add a maximum iteration guard so the loop cannot run indefinitely.
🎯 Interview Questions
Beginner
if (condition) { ... } elseif (condition) { ... } else { ... }. Conditions use PowerShell comparison operators (-eq, -gt, -like, etc.) and logical connectors (-and, -or, -not).
The foreach keyword enumerates a collection that is already in memory. ForEach-Object is a pipeline cmdlet that processes objects one at a time as they stream. For large datasets, ForEach-Object is more memory-efficient. The foreach keyword supports break and continue; ForEach-Object does not support break the same way.
When branching on the same single variable against multiple known explicit values (environment names, service states, exit codes). switch is cleaner, easier to read, and supports regex and wildcard matching modes.
break immediately exits the innermost enclosing loop, skipping any remaining iterations. It is used to stop processing once a condition is met—for example, stopping when a service is found running or when a critical file is encountered.
while tests its condition before executing the body—it may execute zero times if the condition starts false. do-while executes the body once before testing, so it always runs at least once.
Scenario-based
Add a maximum attempt count and a timeout guard: check attempts against a max value alongside checking the success condition. When the limit is reached, throw an explicit error rather than silently exiting. Add the deployment's error response to the exception message so the pipeline failure log is actionable.
Use foreach over Get-ChildItem results: check the Name for the stop flag file first inside the loop and call break if found. Check the Length and call continue for files below 1KB. This gives you clean early-stop and skip behavior without nested if logic.
🌐 Real-world Usage
Deployment scripts use switch on environment parameters to select Azure resource group names and VM sizes. Service health-check scripts use foreach to iterate over server lists and while loops to wait for services to start after deployments. Log processing scripts use loops with continue to skip empty files and break on fatal error indicators.
📝 Summary
Use if/elseif/else for conditional branches and switch for multi-value branching on a single variable. Foreach loops iterate over known collections; while loops continue based on a condition—always guard them with a maximum count. Use break to exit early and continue to skip an iteration. Control flow is the foundation of every non-trivial automation script.