Features of PowerShell loops
Say Again?
Microsoft has emphasized that the increased use of the PowerShell scripting language will make life easier for system managers. After all, maintaining and operating Windows systems means above all that many tasks need to be handled regularly and for a large number of computers. Just as robots tirelessly fit doors and windshields on assembly lines, small and not-so-small scripts can handle recurring work, such as creating users, monitoring processes, or installing software.
Any programming or scripting language needs instructions the user can employ to design loops. It is irrelevant whether the language comes from the structured days of old or is one of the latest generation of object-oriented languages. PowerShell would not be PowerShell if it did not offer several approaches and options for almost every task and requirement – including loop constructs. Beginners thus first need to struggle with the distinction between the for
loop, the foreach
construct, and the foreach-object
cmdlet – not forgetting the do
and while
loops, which also can be interrupted as needed.
The for Loop
Anyone who has ever programmed a computer is probably familiar with for
loops. Using this construct lets arbitrary commands reiterate within a script block, as long as a previously defined condition is true. The basic structure looks like this:
for(<start value>; <run condition>; <iteration>) {commands}
Use of this kind of loop is especially useful when the programmer knows exactly how often the loop should iterate. The following example creates exactly four text files with a corresponding index:
for($count=1; $count -lt 5; $count++) {New-Item -Path h:\tmp -name "file$count.txt" -itemtype file}
The variable $count
is initially assigned the value 1
; then, a file named file1.txt
is created in the h:\tmp
directory with the help of the New-Item
cmdlet; then, $count
is incremented by one with $count++
. The loop is executed as long as the value for $count
remains less than five (-lt 5
).
Of course, you could also achieve exactly the same result with the race condition -le 4
(less than or equal to four): In both cases, the for
loop iterates four times. Omitting individual components of the for
loop can create infinite loops. The parentheses after for
must contain at least two semicolons, as in
$count=1 For(;;) {$count}
or:
$count=2; For(;;count+=2) {$count};
Whereas the first call constantly returns 1
, the second call outputs even numbers starting with 2
. To drop out of these loops, you need to press the Ctrl+C keyboard shortcut. Even though this type of call initially might not look very useful, you will find such constructs time and time again in applications using shell scripts.
We found a special tip in the PowerShell community for programming for
loops that can prove very useful in practice. As our examples show here, loops are used to generate data and store data in variables, which in principle is unlikely to be a problem, even for large numbers of variables, given the speed of modern computer systems. The following example creates an empty array named table
, fills it with 15,000 random numbers between 2
and 10
, and reports the number of items in the table:
$table = @() for($count=1; $count -le 15000; $count++) {$table += Get-Random -Minimum 2 -Maximum 10} $table.count
If you run this script, you will notice a certain delay – even on a powerful PC – before you see the results, because each time the script iterates through the loop the results of the Get-Random
cmdlet are stored in an array variable. You can speed this up as follows:
$table = for($count=1; $count -le 15000; $count++) {Get-Random -Minimum 2 -Maximum 10} $table.count
When you run this code, the output appears on the screen far faster than with the first approach. In this case, the value is not stored in a variable by Get-Random
on each iteration; instead, PowerShell takes care of creating and managing the array. At the end, the script outputs the results, which are stored in $table
after the loop terminates.
ForEach and ForEach-Object
In addition to the for
loop, PowerShell users can rely on foreach
or foreach-object
for loop programming. The basic syntax of a ForEach loop is:
foreach (<variable in collection>) {<commands>}
A simple example shows the basic workings of a ForEach loop:
$names = "Hugo", "Emmy Lou", "William", "Frank", "Jane" foreach ($count in $names) {"$count =" + $count.length}
It lists the elements in a collection, which is often an array. We created a simple string array with five elements. The loop starts with the foreach
keyword, followed by parentheses with three components: The first component is the variable, which the user specifically defined previously for this ForEach command. In the example, it is called $count
, but of course you can choose the name freely, as long as you adhere to the PowerShell naming conventions.
This variable contains the current element during each loop iteration, so it would contain the value Hugo
on the first iteration and Emmy Lou
during the second iteration. The second component within the parentheses is the in
keyword, which always has to be used in exactly this way. Finally, the third component is the collection of values to be processed: It is represented by the variable $names
.
The statements within the parentheses are processed during each iteration of the loop, with the current value of the control variable (i.e., $count
). This example composes a string that outputs the number of characters in the string in addition to the content of the variable. The listed collection does not necessarily have to be an array, as the following example illustrates very nicely:
foreach ($service in Get-Service) { $service.name + "can be stopped: " + $service.canstop.tostring().toupper() }
In this example, we called the Get-Service
cmdlet in parentheses as the third component. It returns a collection of objects, in which one object is issued for each service on the local system. The loop is set up so that a service object is assigned to the $service
variable at each pass. Inside the loop, the foreach
statement then uses this variable to read the name of the service by using the name
property of the service object. At the same time, explanatory text and a colon are appended to the name.
In the next step, a property of the service object named canstop
is read. This property returns a Boolean value indicating whether a service can be stopped once started. Finally, the foreach
statement calls the tostring
and toupper
methods to convert this value into a string with uppercase characters.
The order in which the two methods are used is important: The Boolean value first must be converted to a string with ToString before the ToUpper method can be used, because ToUpper can only be applied to strings. If you don't want to see the output of the Boolean value, False or True , in capital letters on the screen, you can leave out these two methods.
PowerShell programming is always interesting when you leverage the powerful pipe mechanism and object orientation. Both approaches come to fruition when you use the ForEach-Object cmdlet instead of the ForEach loop. In the next example, we will be using the Where-Object
cmdlet – or, in this case, its alias where
:
Get-Service | where {$_.status -eq 'running'} | foreach-object {$_.name + " can be stopped: " + $_.canstop.tostring().toupper() }
With this script, too, the system services are shown on the screen (Figure 1). The Get-Service
cmdlet pipes these to Where-Object (where
), where a filter then finds objects that have the status "running." Where-Object then sends these results to a pipe, which is read by foreach-object
.
The foreach-object
has no code in parentheses – just a script block in curly brackets. If you choose this approach, you also do not need a separate run condition variable, as shown here, which is one distinction between the foreach-object
cmdlet and the foreach
statements previously presented. Instead, we used the fixed shell variable $_.
, with which we can then access the current value of each object in the pipe. The PowerShell ForEach-Object also provides an alias named foreach
. Although this may be confusing in terms of the code, PowerShell has no problem distinguishing this alias from the "normal" foreach
. Whenever a foreach
appears in a pipe, you can be sure that it is an alias for ForEach-Object.
Another difference lies in the way PowerShell handles the two calls: When the shell processes a foreach
statement, it first creates the complete collection of objects in memory before it sets out to iterate through the loop. Therefore, it can take quite some time before processing begins if you have large collections. However, when the shell iterates through a foreach-object
cmdlet, each object is processed as soon as it moves through the pipe (i.e., real-time processing).
Eternal Loops: do and while
If you are an experienced programmer, you might be familiar with the terms "head-controlled" (or "count-controlled") and "foot-controlled" loops. Classic for
loops that query a condition before entering the loop body are considered "head-controlled," whereas loop constructs that test a condition only after processing the instructions in the loop body are referred to as "foot-controlled." Thanks to this principle, it is guaranteed that a foot-controlled loop in the program sequence will run at least once, whatever happens.
PowerShell lets users use such loops with the do
and while
statements. Basically do
and while
are useful for loops in which the programmer does not know how often they will iterate in the course of the program. You can even use them to create infinite loops. The formal structure of a do
-while
loop looks like this:
do { <commands> } while (<condition>)
The following is a very simple example of using such a loop:
do { $input= Read-Host "Please enter" } while ($input -notlike "secret" ) Write-Host "correct"
This loop runs until the user enters the word secret
; the -notlike
operator performs a string comparison to do this (Figure 2). Of course, this example is far removed from a real password because the comparison is not case sensitive; however, it illustrates the principle of this kind of loop. In terms of the program logic with this type of loop, it makes sense to query the cancellation condition at the beginning of the loop. In such cases, you would omit the do
keyword and get started with the while
query:
$file = [system.io.file]::OpenText("H:\tmp\poem.txt") ** while (!($file.EndOfStream)) { $file.ReadLine() } $file.close
This little program first opens a text file. The while
condition tests whether the end-of-file ($file.EndOfStream
) has been reached; the query is negated by the !
character, which stands for "not." If the end-of-file is reached, we exit the loop and use $file.close
to close the file outside of the loop.
Buy this article as PDF
(incl. VAT)
Buy ADMIN Magazine
Subscribe to our ADMIN Newsletters
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Most Popular
Support Our Work
ADMIN content is made possible with support from readers like you. Please consider contributing when you've found an article to be beneficial.