Control Flow

Summary: in this tutorial, you will learn master conditional statements (if/elseif/else, switch) and counted iteration (for, foreach) in powershell.

Control Flow

Control flow is how your scripts make decisions and repeat actions. Without control flow, code would execute line-by-line from top to bottom with no ability to branch based on conditions or repeat operations. Control flow statements are the fundamental building blocks that transform simple command sequences into intelligent, adaptive programs.

PowerShell provides all the standard control flow constructs you'd expect from a modern programming language — if, switch, for, foreach, while — plus some unique features that make scripts more concise and expressive.

Understanding Conditional Logic

Before diving into syntax, let's understand what makes conditional logic powerful. Your scripts need to adapt to different situations:

  • Is the file present before trying to read it?
  • Is the user authorized before granting access?
  • Did the network request succeed before processing the response?
  • Is this value within valid range before performing calculations?

Conditions let your code branch: "If this is true, do this; otherwise, do that." This adaptability is what separates simple automation from intelligent programs.

If / ElseIf / Else: Basic Decision Making

The if statement is the most fundamental conditional construct. It evaluates a condition and executes a block of code only if that condition is true.

Simple If Statements

$temperature = 72
 
if ($temperature -gt 85) {
    Write-Host "It's hot outside! Stay hydrated."
}
 

If $temperature is greater than 85, the message displays. Otherwise, nothing happens and execution continues to the next statement.

If-Else: Binary Decisions

When you have two mutually exclusive branches, use else:

$temperature = 72
 
if ($temperature -gt 85) {
    Write-Host "It's hot! Turn on the AC."
} else {
    Write-Host "Temperature is comfortable."
}
 

Exactly one of these blocks will execute — never both, never neither.

If-ElseIf-Else: Multiple Conditions

For more than two outcomes, chain conditions with elseif:

$temperature = 72
 
if ($temperature -gt 85) {
    "It's hot!"
} elseif ($temperature -gt 65) {
    "It's nice out."
} elseif ($temperature -gt 45) {
    "It's cool."
} else {
    "It's cold!"
}
 

PowerShell evaluates these conditions sequentially from top to bottom and executes the first matching block. Once a match is found, the remaining conditions are skipped. This ordering is crucial — if you put broad conditions before specific ones, the specific conditions will never execute.

Complex Conditions with Logical Operators

Real-world conditions often involve multiple factors:

$hour = (Get-Date).Hour
$day = (Get-Date).DayOfWeek
 
if ($day -in 'Saturday','Sunday') {
    "Weekend - enjoy your day off!"
} elseif ($hour -lt 9 -or $hour -ge 17) {
    "Outside of work hours"
} elseif ($hour -ge 12 -and $hour -lt 13) {
    "Lunch time!"
} else {
    "Working hours - stay productive!"
}
 

The -and operator requires both conditions to be true. The -or operator requires at least one to be true. You can combine these with parentheses for complex logic:

if (($status -eq "Active" -and $balance -gt 0) -or $isPremium) {
    "Account has full access"
}
 

If Statements as Expressions

Unlike many languages where if is purely a statement, PowerShell's if can return values:

$status = if ($count -gt 0) { "Active" } else { "Empty" }
 

The last expression evaluated in the chosen block becomes the return value. This makes assignments based on conditions more concise than storing results in separate statements.

In PowerShell 7+, you can use the even more concise ternary operator:

$status = $count -gt 0 ? "Active" : "Empty"
 

Both forms produce the same result — choose whichever is clearer in context.

Handling Null and Empty Values

PowerShell provides several ways to check for null or empty values, each with different semantics:

$value = $null
 
# Check for null explicitly
if ($null -eq $value) {
    "Value is null"
}
 
# Check for empty string
if ([string]::IsNullOrEmpty($value)) {
    "Value is null or empty string"
}
 
# Check for null, empty, or whitespace
if ([string]::IsNullOrWhiteSpace($value)) {
    "Value is null, empty, or only whitespace"
}
 
# Check for empty collection
if ($collection.Count -eq 0) {
    "Collection has no items"
}
 

Critical: Always put $null on the left side of comparisons

if ($null -eq $value) { ... }    # ✅ Correct
if ($value -eq $null) { ... }    # ❌ Can give wrong results
 

Why? When $value is an array, putting it on the left causes PowerShell to use -eq as a filter operator, returning matching elements rather than a boolean. With $null on the left, PowerShell correctly performs a boolean equality test.

$arr = @(1, 2, $null, 4)
$arr -eq $null       # Returns @($null) — the filtered array
$null -eq $arr       # Returns $false — correct boolean result
 

Switch Statement: PowerShell's Most Powerful Conditional

PowerShell's switch statement is far more capable than the switch found in languages like C# or Java. It supports pattern matching, regular expressions, evaluating multiple matches, and even processing entire files.

Basic Switch Syntax

$fruit = "apple"
 
switch ($fruit) {
    "apple"  { "It's red or green, crispy and sweet" }
    "banana" { "It's yellow and soft" }
    "cherry" { "It's small and red" }
    default  { "Unknown fruit: $fruit" }
}
 

The switch evaluates the input value against each case. When a match is found, it executes that block. The default case acts as a catch-all for values that don't match any explicit case.

Multiple Matches: Switch Evaluates All Cases

Unlike C#/Java where switch stops after the first match, PowerShell's switch evaluates ALL matching cases by default:

$value = 3
 
switch ($value) {
    {$_ -gt 0}       { "Positive" }
    {$_ -lt 10}      { "Single digit" }
    {$_ % 2 -eq 1}   { "Odd" }
}
# Output:
# Positive
# Single digit
# Odd
 

All three conditions match, so all three blocks execute. This behavior is often useful for categorizing data into multiple non-exclusive categories.

If you want to stop after the first match, use break:

$value = 3
 
switch ($value) {
    {$_ -gt 0}  { "Positive"; break }
    {$_ -lt 10} { "Single digit"; break }
}
# Output: Positive (stops after first match)
 

Within switch blocks, the special variable $_ refers to the current value being tested.

Pattern Matching with Wildcards

Use the -Wildcard parameter to match patterns:

$filename = "report-2025.pdf"
 
switch -Wildcard ($filename) {
    "*.pdf"   { "PDF document" }
    "*.docx"  { "Word document" }
    "*.xlsx"  { "Excel spreadsheet" }
    "report*" { "This is a report file" }
}
# Output:
# PDF document
# This is a report file
 

Both patterns match, so both blocks execute. Wildcards use * (zero or more characters), ? (exactly one character), and [abc] (character class) patterns.

Pattern Matching with Regular Expressions

For complex patterns, use the -Regex parameter:

$email = "admin@example.com"
 
switch -Regex ($email) {
    "^admin"           { "Admin account detected" }
    "@example\.com$"   { "Internal company email" }
    "^\w+@\w+\.\w+$"  { "Valid basic email format" }
}
# All three patterns match
 

Regular expressions provide much more power than wildcards, enabling validation of complex formats like emails, phone numbers, IP addresses, and structured data.

When using -Regex, you can access captured groups through the $Matches automatic variable:

$version = "PowerShell 7.4.1"
 
switch -Regex ($version) {
    "(\d+)\.(\d+)\.(\d+)" {
        $major = $Matches[1]
        $minor = $Matches[2]
        $patch = $Matches[3]
        "Version breakdown: Major=$major, Minor=$minor, Patch=$patch"
    }
}
 

Case-Sensitive Matching

By default, switch is case-insensitive. Use -CaseSensitive for exact matching:

$command = "EXIT"
 
switch -CaseSensitive ($command) {
    "exit"   { "Lowercase exit command" }
    "EXIT"   { "Uppercase EXIT command" }
    "Exit"   { "Title case Exit command" }
}
# Output: Uppercase EXIT command
 

Switch with Arrays: Automatic Iteration

One of PowerShell's most useful switch features: when you provide an array as input, switch automatically iterates over each element:

$colors = "red", "blue", "green", "yellow"
 
switch ($colors) {
    "red"   { "$_ is a warm color" }
    "blue"  { "$_ is a cool color" }
    "green" { "$_ is a cool color" }
    default { "$_ is not classified" }
}
# Output:
# red is a warm color
# blue is a cool color
# green is a cool color
# yellow is not classified
 

This processes each array element through the switch logic, which is incredibly concise compared to wrapping a switch in a foreach loop.

Switch with File Input: Processing Line by Line

The -File parameter makes switch read a file and process each line:

switch -Regex -File "C:\Logs\application.log" {
    "ERROR"   { Write-Host "Error found: $_" -ForegroundColor Red }
    "WARNING" { Write-Host "Warning found: $_" -ForegroundColor Yellow }
    "^\d{4}-\d{2}-\d{2}" { Write-Host "Dated entry: $_" }
}
 

This is remarkably efficient for log file processing — PowerShell reads the file line-by-line (streaming, not loading entire file into memory) and evaluates each line against your patterns. This is perfect for analyzing logs, configuration files, or any line-based text data.

Practical Switch Example: Seasonal Greetings

function Get-SeasonalGreeting {
    param([int]$Month = (Get-Date).Month)
 
    switch ($Month) {
        {$_ -in 3,4,5}   { "Spring has sprung! 🌸" }
        {$_ -in 6,7,8}   { "Summer vibes! ☀️" }
        {$_ -in 9,10,11} { "Fall colors! 🍂" }
        {$_ -in 12,1,2}  { "Winter wonderland! ❄️" }
    }
}
 
Get-SeasonalGreeting    # Uses current month
Get-SeasonalGreeting -Month 7    # "Summer vibes! ☀️"
 

By using script blocks with conditions, you can group related values and express complex logic clearly.

For Loop: Counted Iteration

The for loop is your tool for counted iteration — when you know exactly how many times you want to repeat an operation or when you need fine control over loop variables.

Basic For Loop Structure

for ($i = 0; $i -lt 5; $i++) {
    "Iteration $i"
}
# Output:
# Iteration 0
# Iteration 1
# Iteration 2
# Iteration 3
# Iteration 4
 

The for loop has three parts (separated by semicolons):

  1. Initialization: $i = 0 — runs once before loop starts
  2. Condition: $i -lt 5 — checked before each iteration; loop continues while true
  3. Increment: $i++ — runs after each iteration

Counting Down

for ($i = 10; $i -gt 0; $i--) {
    "$i..."
    Start-Sleep -Milliseconds 500
}
Write-Host "Launch! 🚀" -ForegroundColor Green
 

By changing the condition and increment, you can count in any direction.

Custom Step Sizes

for ($i = 0; $i -le 100; $i += 10) {
    "Progress: $i%"
}
# Output: Progress: 0%, Progress: 10%, ..., Progress: 100%
 

Use += with any value to control the increment size.

Multiple Loop Variables

You can initialize and update multiple variables:

for ($i = 0, $j = 10; $i -lt $j; $i++, $j--) {
    "i=$i, j=$j, sum=$($i+$j)"
}
# Output:
# i=0, j=10, sum=10
# i=1, j=9, sum=10
# i=2, j=8, sum=10
# i=3, j=7, sum=10
# i=4, j=6, sum=10
 

This pattern is useful for algorithms that need converging or diverging indices.

When to Use For vs Other Loops

Use for when:

  • You know the exact number of iterations
  • You need an index variable
  • You're implementing mathematical algorithms
  • You need precise control over initialization and increment logic

Use foreach (next section) when you're iterating over collections and don't need index management.

Foreach Loop: Iterating Over Collections

The foreach loop iterates over each item in a collection. It's the most natural way to process arrays, lists, files, and pipeline output.

Basic Foreach Syntax

$fruits = @("apple", "banana", "cherry")
foreach ($fruit in $fruits) {
    "I like $fruit"
}
# Output:
# I like apple
# I like banana
# I like cherry
 

The loop variable ($fruit) takes on each value from the collection in sequence.

Iterating Over Files

foreach ($file in Get-ChildItem *.txt) {
    "File: $($file.Name)"
    "Size: $($file.Length) bytes"
    "Modified: $($file.LastWriteTime)"
    ""
}
 

This pattern processes each file returned by Get-ChildItem, accessing properties of the file object.

Iterating Over Hashtables

Hashtables require special handling because they have keys and values:

$config = @{
    Host = "localhost"
    Port = 8080
    Debug = $true
    LogLevel = "Info"
}
 
# Method 1: Iterate over keys
foreach ($key in $config.Keys) {
    "$key = $($config[$key])"
}
 
# Method 2: Use GetEnumerator (gives you key-value pairs)
foreach ($entry in $config.GetEnumerator()) {
    "$($entry.Key) = $($entry.Value)"
}
 

GetEnumerator() returns objects with .Key and .Value properties, which is often more convenient than accessing keys and looking up values separately.

Modifying Collections During Iteration

Do not modify a collection while iterating over it with foreach

$numbers = @(1,2,3,4,5)
foreach ($num in $numbers) {
    if ($num -gt 3) {
        $numbers += 10    # ❌ Modifying collection being iterated
    }
}
 

This can cause unpredictable behavior. Instead, build a new collection or use a for loop with explicit indices.

Foreach Statement vs ForEach-Object Cmdlet

PowerShell has two constructs that look similar but behave differently:

foreach statement:

foreach ($proc in Get-Process) {
    $proc.Name
}
 

ForEach-Object cmdlet:

Get-Process | ForEach-Object {
    $_.Name
}
 

Key differences:

foreach statement:

  • Loads the entire collection into memory first
  • Faster for small to medium datasets
  • Can use break to exit early
  • Cannot be used in pipeline chains

ForEach-Object cmdlet:

  • Processes items one at a time from the pipeline (streaming)
  • Better for large datasets or infinite streams
  • Essential for pipeline processing
  • Can be interrupted with Ctrl+C more responsively

Choose foreach for in-memory collections where you need loop control. Choose ForEach-Object when working with pipelines or very large datasets.

Was this page helpful?
SR

Written by the ShellRAG Team

The ShellRAG editorial team writes practical, beginner-friendly PowerShell tutorials with tested code examples and real-world use cases. Every article is technically reviewed for accuracy and updated regularly.

Learn more about us →