Control Flow: Conditionals, Loops & Switch
PowerShell Fundamentals
Chapter 6 · Control Flow: Conditionals, Loops & Switch
Chapter 4 already introduced ForEach-Object — but strictly as one stage inside a pipeline, using $_ to touch each object flowing through it. This chapter is about writing an actual script body: if/elseif/else, a genuinely more capable switch statement than most languages ship with, and a foreach loop keyword that looks almost identical to Chapter 4's cmdlet by name — while behaving differently enough underneath that mixing the two up is one of the most common early PowerShell mistakes.
if / elseif / else
Braces, not indentation (unlike Python) and not then/fi keywords (unlike Bash) — conditions go in parentheses, bodies go in braces:
switch: PowerShell's Own Surprisingly Powerful Version
A basic switch looks familiar from other languages, but it comes with real extras — wildcard matching, regex matching, and (unlike almost anything else called "switch") the ability to iterate an entire array directly:
switch stops at the first match unless you fall through on purpose. PowerShell's does the opposite by default: every clause whose condition matches actually runs, one after another, for the same input. switch ($x) { {$_ -gt 0} {"positive"} {$_ -gt 10} {"big"} } run with $x = 15 prints both "positive" and "big" — both conditions are true for 15. If you want only the first match to run, add break at the end of each clause body.
foreach: The Loop Keyword (Not the Cmdlet)
foreach written as a bare keyword — no pipe, no -Object — is a completely different piece of syntax from Chapter 4's ForEach-Object, despite the near-identical name:
foreach is a language statement: it fully evaluates Get-Process into a complete, in-memory collection first, then loops over that finished collection. Get-Process | ForEach-Object {...} (Chapter 4) is a pipeline stage: it processes each process object as soon as it arrives, one at a time, without ever holding the full set in memory at once. On a small result set the difference is invisible. On something genuinely large — every row from a multi-million-row query, or every file under a massive directory tree — ForEach-Object's streaming behavior can matter a great deal, while a bare foreach has to build the whole collection before the loop body runs even once.
while, do-while & do-until
do-while and do-until are easy to swap by mistake specifically because their loop shape is identical — only the condition's meaning flips (continue while true vs. continue until true, i.e. while false). Reading the condition out loud in plain English before choosing which one to use avoids the mix-up.
for: The Classic Three-Part Loop
break & continue
Both work exactly as they do in most C-family languages — break exits the loop (or, per the warn-box above, a switch clause) entirely; continue skips straight to the next iteration:
Iterating a Hashtable, Properly
Chapter 5 introduced @{}/[ordered]@{} but deferred looping over one — here's the real pattern, using .GetEnumerator() rather than trying to foreach the hashtable directly:
foreach and ForEach-Object, ask one question: is this collection already sitting fully in memory (an array, a hashtable) or is it streaming out of a pipeline (Get-ChildItem on a huge tree, a database query)? Already-in-memory favors foreach; a live pipeline favors ForEach-Object.
Hands-On Exercises
Using this chapter's own array-iteration example, explain why switch (@(1, 2, 3, 4)) { {$_ % 2 -eq 0} {"$_ is even"} default {"$_ is odd"} } produces four separate results rather than just one.
Given switch ($x) { {$_ -gt 0} {"positive"} {$_ -gt 10} {"big"} } run with $x = 15, explain why both "positive" and "big" print. Then explain how you'd change the code so only "big" prints.
Explain the practical difference between foreach ($p in Get-Process) {...} and Get-Process | ForEach-Object {...}. Describe a concrete situation where that difference would actually matter, not just a situation where it happens to be invisible.
Chapter 6 Quick Reference
if / elseif / else— parentheses around the condition, braces around the bodyswitch— supports-Wildcard/-Regex, auto-iterates an array; runs every matching clause unless you addbreakforeach ($x in $collection)— a loop keyword; evaluates the whole collection into memory firstForEach-Object(Chapter 4) — a pipeline cmdlet; streams one object at a time, never holding the whole set in memorywhile— checks before each pass, may run zero timesdo-while/do-until— always run at least once; inverted continue-condition logic (while true vs. until true)for ($i=0; $i -lt N; $i++)— the classic three-part loopbreak/continue— exit entirely / skip to next iteration;breakalso stops aswitchclause from letting later clauses run$hash.GetEnumerator()— the correct way toforeachover a hashtable's key/value pairs- Next chapter: Functions & Script Files (.ps1)