PowerShell Loops: A Practical Guide to foreach, for, and do Loops

Published:20 December 2024 - 2 min. read

One of PowerShell’s key features is its ability to loop through data collections, enabling you to execute a code block repeatedly.

In this tutorial, we will explore three fundamental loop structures in PowerShell: foreach, for, and do loops.

By the end of this tutorial, you will have a solid understanding of how to use these loops to handle repetitive tasks and process collections of data more effectively.

Mastering foreach Loops: Iterating with Ease

When you need to execute a code block for every item, the foreach loop makes repetitive tasks simple and efficient. This loop is a more complex but ever-so-common structure that works wonders whether you’re working with an array of server names or processing any other collection.

Every foreach performs a common task; it allows you to run some code for each element in a collection. It performs ‘iteration’ to ‘iterate over’ each element in that collection, enabling you to address each element individually.

This first example has an array of strings representing server names. The goal is to simply read each string in that array.

$servers = @('localhost','SRV2','SRV3','SRV4')

Create a foreach loop with a variable called $server that represents each element in that array as it’s processing.

foreach ($server in $servers) {
    Write-Host "I'm processing server $server right now..."
}

You can see that PowerShell ran the Write-Host command and read each item in the array. PowerShell knew how many times to run because of how many items were in the array.

Iterating over each element in the array and running some code

If you ever have a large array, you can always use the Count() method to see how many times the loop would run.

$servers.Count
Checking loop iteration count

You can also use the ForEach-Object cmdlet, which is handy when you’d like to use the pipeline iterating over each item in an array. Typically, you’ll see the ForEach-Object cmdlet used with the pipeline.

ForEach-Object -InputObject $servers -Process {
    Write-Host "I'm processing server $server right now..."
}
Specifying the collection of items to be processed

Since the ForEach-Object cmdlet’s InputObject parameter supports pipeline input, you can pipe the collection directly to it. Exclude the Process parameter name and include the scriptblock immediately after the ForEach-Object call.

This method works great when processing small collections but can be slower when working with extensive collections.

$servers | ForEach-Object {
    Write-Host "I'm processing server $_ right now..."
}
Piping the collection directly to `ForEach-Object` for small collections

And finally, we have the foreach() method. As with earlier options, you can use this method on arrays, not a statement or command. It’s the fastest foreach loop, run by typical dot notation executing a method on an object with a scriptblock inside.

$servers.foreach({Write-Host "I'm processing server $_ right now..."})

Every foreach loop is generally the same when processing small collections. You can typically go with a foreach statement. Why? Because you can define a descriptive variable instead of using the pipeline variable (the dollar underscore), which isn’t as intuitive.

Choosing a descriptive foreach loop variable srcset=

Unlocking the Power of for Loops: Flexibility and Control

Next up, we have for loops. For loops are loops that aren’t quite as smart as foreach loops. For loops have no idea how many elements are in a collection. For loops start at a number, increment that number each time, and stop when a condition is true.

The for loop has four different elements to it:

Element Function
Iterator variable (e.g., $i) Typically defined as $i‘s starting point, which is at 0 here.
Condition ($true or $false) The condition that must be met for the loop to continue. In this case, $true.
Action (e.g., $i++) The action to perform for each iteration. You’ll typically see this to be $i++ which increments the variable by 1 for each iteration.
Scriptblock {} The scriptblock, enclosed in {} contains the code to run for each iteration.

In this example, this for loop:

  • Sets the iterator variable to 0.
  • Runs the code in the scriptblock, which is a Write-Host reference.
  • Checks to see if the iterator variable is less than 10, which it is.
  • Increments by 1 ($i++), setting the value to 1 since the iterator variable is less than 10.
  • Reruns the code and repeats that pattern.
## The for Loop (Allows you to reference element indexes dynamically)
for ($i = 0; $i -lt 10; $i++) {
    Write-Host "I'm on iteration number $i now."
}

To apply for loops to arrays, you can use the fact that arrays have an index starting at 0 and incrementing by 1 to mimic a foreach loop.

## Change the value of array items
for ($i = 0; $i -lt $servers.Length; $i++) {
    $servers[$i] = "new $($servers[$i])"
}
$servers

Another handy use of for loops is the ability to reference past and future elements while processing a current element.

For instance, you may need to reference the server name you just processed. You can’t do so in a foreach loop.

But in a for loop, you add or subtract from the iterator variable and use it as an array index to reference the server name you need.

## Reference elements before or after based on the current item
for ($i = 1; $i -lt $servers.Length; $i++) {
    Write-Host $servers[$i] "comes after" $servers[$i-1]
}

Let’s finish off for loops with a real example based on the scenario we’ve been working with but changed up slightly.

You may need to read the contents of some application configuration files from multiple servers. The problem is that the file name isn’t the same on each server but follows a pattern. There’s a number in the file name, as you can see below (e.g., App_configuration1.txt).

We can’t simply use a foreach loop here because the file name differs every time. But since the file has an incrementing number, a for loop will work.

Create a for loop that:

  • Runs for as many elements as the $servers array has. You must define the iterator variable as less than the count of all elements in the $servers array.
  • References each $servers array index inside the script block that starts at 0 and increments the value by 1 to create the number in the configuration file.
## But what if the problem is a little more complex? A number in the file name
$servers = @('localhost','SRV2','SRV3','SRV4')
Get-Content -Path "\\$($servers[0])\c$\App_configuration1.txt"
Get-Content -Path "\\$($servers[1])\c$\App_configuration2.txt"
Get-Content -Path "\\$($servers[2])\c$\App_configuration3.txt"
Get-Content -Path "\\$($servers[3])\c$\App_configuration4.txt"

for ($i = 0; $i -lt $servers.Count; $i++) {
    Write-Host "Processing servers array index number $i which is server $($servers[$i])..."
    Write-Host "The next servers array index after this one is $($i + 1)."
    Write-Host "Looking for file name App_configuration$($i+1).txt..."
    Get-Content -Path "\\$($servers[$i])\c$\App_configuration$($i+1).txt"
}

You can see that the for loop processed each Get-Content command to read the appropriate file on the appropriate server.

Exploring do Loops: Ensuring Actions Before Conditions

Let’s finish off loops with a few useful ones that may not be as common as foreach and for, but when you need them, they come in clutch.

First up is the while loop. The while loop is useful when running a code while something else is in a particular state or meets a specific condition (e.g., a counter).

This while loop only runs while the $counter variable is less than 10.

## The while Loop (Do something while a specific state for something else)

$counter = 0
while ($counter -lt 10) {
    $counter
    $counter++
}

To prevent an infinite loop that never stops, add a scriptblock code incrementing the counter by 1 for each iteration. The loop returns 0 through 9, which is the value of $counter at the time of each iteration.

Now, let’s cover a real example.

For this while loop, it’s pinging a machine constantly only while it returns a $true value. So you don’t DOS the machine; it’s pausing for one second between attempts.

You can create some advanced wait functions with while loops like this.

## Ping SOMESERVER every second only while Test-Connection returns true
while (Test-Connection -ComputerName 'SOMESERVER' -Quiet -Count 1) {
    Start-Sleep -Seconds 1
}
Write-Host 'SOMESERVER is now offline.'

Next up is the do/while loop. This loop is similar to the while loop, but unlike the while loop, it always performs an action (the do part) and enters into that while loop.

Below is a nonsense way to use a do/while loop.

## The do/while loop (Do something THEN CONTINUE doing something until a condition is met)
do {
    $choice = Read-Host -Prompt 'What is the best programming language?'
} while ($choice -ne 'PowerShell')

Notice that it won’t exit the loop before I give the “correct” answer. The loop continues to run the Read-Host command continuously when the answer isn’t what it wants it to be.

And finally, we have the do/until loop.

Similar to the do/while loop, the do/until loop does something and then stops when a condition is met. This loop defines the stop condition and not the continuation condition.

## The do/until loop (Do something THEN STOP when a condition is met)
do {
    $choice = Read-Host -Prompt 'What is the best programming language?'
} until ($choice -eq 'PowerShell')
Write-Host -Object 'Correct!'

You can replicate the same scenario by flipping the not equal operator to equal in this loop.

Note that the do/until loop allows you to run some code after it meets the condition it was looking for—a feat impossible with the do/while loop.

Conclusion

Loops are essential in scripting for automation, and mastering the foreach, for, and do loops in PowerShell opens up a world of possibilities for handling complex tasks.

Whether you’re iterating through a list of servers, performing checks on multiple files, or running actions until a specific condition is met, these loops provide flexibility and control. They allow you to streamline your processes effectively.

Following these examples, you can now apply these loops to your scripts, making your automation tasks efficient and scalable.

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!