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.
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
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..." }
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..." }
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.
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 to1
since the iterator variable is less than10
. - 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 at0
and increments the value by1
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.