Chapter 4. Looping and Flow Control
4.0. Introduction
As you begin to write scripts or commands that interact with unknown data, the concepts of looping and flow control become increasingly important.
PowerShell’s looping statements and commands let you perform an operation (or set of operations)without having to repeat the commands themselves. This includes, for example, doing something a specified number of times, processing each item in a collection, or working until a certain condition comes to pass.
PowerShell’s flow control and comparison statements let you to adapt your script or command to unknown data. They let you execute commands based on the value of that data, skip commands based on the value of that data, and more.
Together, looping and flow control statements add significant versatility to your PowerShell toolbox.
4.1. Make Decisions with Comparison and Logical Operators
Solution
Use PowerShell’s logical operators to compare pieces of data and make decisions based on them.
For a detailed description (and examples) of these operators, see “Comparison Operators” in Appendix A.
Discussion
PowerShell’s logical and comparison operators let you compare pieces of data, or test data for some condition. An operator either compares two pieces of data (a binary operator)or tests one piece of data (a unary operator). All comparison operators are binary operators (they compare two pieces of data), as are most of the logical operators. The only unary logical operator is the -not
operator, which returns the true/false
opposite of the data that it tests.
Comparison operators compare two pieces of data and return a result that depends on the specific comparison operator. For example, you might want to check whether a collection has at least a certain number of elements:
PS >(dir).Count -ge 4 True
or, check whether a string matches a given regular expression:
PS >"Hello World" -match "H.*World" True
Most comparison operators also adapt to the type of their input. For example, when you apply them to simple data such as a string, the -like
and -match
comparison operators determine whether the string matches the specified pattern. When you apply them to a collection of simple data, those same comparison operators return all elements in that collection that match the pattern you provide.
Tip
The -match
operator takes a regular expression as its argument. One of the more common regular expression symbols is the $ character, which represents the end of line. The $ character also represents the start of a PowerShell variable, though! To prevent PowerShell from interpreting characters as language terms or escape sequences, place the string in single quotes rather than double quotes:
PS >"Hello World" -match "Hello" True PS >"Hello World" -match 'Hello$' False
For a detailed description of how the individual comparison operators adapt to their input, see “Comparison Operators” in Appendix A.
Logical operators combine true
or false
statements and return a result that depends on the specific logical operator. For example, you might want to check whether a string matches the wildcard pattern you supply, and that it is longer than a certain number of characters:
PS >$data = "Hello World" PS >($data -like "*llo W*") -and ($data.Length -gt 10) True PS >($data -like "*llo W*") -and ($data.Length -gt 20) False
Some of the comparison operators actually incorporate aspects of the logical operators. Since using the opposite of a comparison (such as -like
)is so common, PowerShell provides comparison operators (such as -notlike
)that save you from having to use the -not
operator explicitly.
For a detailed description of the individual logical operators, see “Comparison Operators” in Appendix A.
Comparison operators and logical operators (when combined with flow control statements)form the core of how we write a script or command that adapts to its data and input.
See also “Conditional Statements” in Appendix A, for detailed information about these statements.
For more information about PowerShell’s operators, type Get-Help About_Operator
.
See Also
“Comparison Operators” in Appendix A
“Conditional Statements” in Appendix A
4.2. Adjust Script Flow Using Conditional Statements
Problem
You want to control the conditions under which PowerShell executes commands or portions of your script.
Solution
Use PowerShell’s if
, elseif
, and else
conditional statements to control the flow of execution in your script.
For example:
$temperature = 90 if($temperature -le 0) { "Balmy Canadian Summer" } elseif($temperature -le 32) { "Freezing" } elseif($temperature -le 50) { "Cold" } elseif($temperature -le 70) { "Warm" } else { "Hot" }
Discussion
Conditional statements include the following:
if
statementExecutes the script block that follows it if its condition evaluates to true
elseif
statementExecutes the script block that follows it if its condition evaluates to true, and none of the conditions in the
if
orelseif
statements before it evaluate to trueelse
statementExecutes the script block that follows it if none of the conditions in the
if
orelseif
statements before it evaluate to true
For more information about these flow control statements, type Get-Help About_Flow_Control
.
4.3. Manage Large Conditional Statements with Switches
Problem
You want to find an easier or more compact way to represent a large if
… elseif
… else
conditional statement.
Solution
Use PowerShell’s switch
statement to more easily represent a large if
… elseif
… else
conditional statement.
For example:
$temperature = 20 switch($temperature) { { $_ -lt 32 } { "Below Freezing"; break } 32 { "Exactly Freezing"; break } { $_ -le 50 } { "Cold"; break } { $_ -le 70 } { "Warm"; break } default { "Hot" } }
Discussion
PowerShell’s switch
statement lets you easily test its input against a large number of comparisons. The switch statement supports several options that allow you to configure how PowerShell compares the input against the conditions—such as with a wildcard, regular expression, or even arbitrary script block. Since scanning through the text in a file is such a common task, PowerShell’s switch
statement supports that directly. These additions make PowerShell switch
statements a great deal more powerful than those in C and C++.
Although used as a way to express large conditional statements more cleanly, a switch statement operates much like a large sequence of if
statements, as opposed to a large sequence of if
… elseif
… elseif
… else
statements. Given the input that you provide, PowerShell evaluates that input against each of the comparisons in the switch
statement. If the comparison evaluates to true
, PowerShell then executes the script block that follows it. Unless that script block contains a break
statement, PowerShell continues to evaluate the following comparisons.
For more information about PowerShell’s switch
statement, see “Conditional Statements” in Appendix A or type Get-Help About_Switch
.
See Also
“Conditional Statements” in Appendix A
4.4. Repeat Operations with Loops
Solution
Use one of PowerShell’s looping statements (for
, foreach
, while
, and do
), or PowerShell’s Foreach-Object
cmdlet to run a command or script block more than once. For a detailed description of these looping statements, see “Looping Statements” in Appendix A. For example:
forloop
for($counter = 1; $counter -le 10; $counter++) { "Loop number $counter" } foreachloop
foreach($file in dir) { "File length: " + $file.Length } Foreach-Objectcmdlet
Get-ChildItem | Foreach-Object { "File length: " + $_.Length } whileloop
$response = "" while($response -ne "QUIT") { $response = Read-Host "Type something" } do..whileloop
$response = "" do { $response = Read-Host "Type something" } while($response -ne "QUIT")
Discussion
Although any of the looping statements can be written to be functionally equivalent to any of the others, each lends itself to certain problems.
You usually use a for
loop when you need to perform an operation an exact number of times. Because using it this way is so common, it is often called a counted for loop.
You usually use a foreach
loop when you have a collection of objects and want to visit each item in that collection. If you do not yet have that entire collection in memory (as in the dir
collection from the foreach
example above), the Foreach-Object
cmdlet is usually a more efficient alternative.
Unlike the foreach
loop, the Foreach-Object
cmdlet allows you to process each element in the collection as PowerShell generates it. This is an important distinction; asking PowerShell to collect the entire output of a large command (such as Get-Content
hugefile.txt
) in a foreach
loop can easily drag down your system.
A handy shortcut to repeat an operation on the command line is:
PS >1..10 | foreach { "Working" } Working Working Working Working Working Working Working Working Working Working
The while
and do..while
loops are similar, in that they continue to execute the loop as long as its condition evaluates to true
. A while
loop checks for this before ever running your script block, while a do..while
loop checks the condition after running your script block.
For a detailed description of these looping statements, see “Looping Statements” in Appendix A or type Get-Help About_For
or type Get-Help About_Foreach
.
See Also
“Looping Statements” in Appendix A
4.5. Add a Pause or Delay
Solution
To pause until the user presses ENTER
, use the Read-Host
cmdlet:
PS >Read-Host "Press ENTER" Press ENTER:
To pause until the user presses a key, use the ReadKey()
method on the $host
object:
PS >$host.UI.RawUI.ReadKey()
To pause a script for a given amount of time, use the Start-Sleep
cmdlet:
PS >Start-Sleep 5 PS >Start-Sleep -Milliseconds 300
Discussion
When you want to pause your script until the user presses a key or for a set amount of time, the Read-Host
and Start-Sleep
cmdlets are the two you are most likely to use. For more information about using the Read-Host
cmdlet to read input from the user, see Recipe 12.1, “Read a Line of User Input.” In other situations, you may sometimes want to write a loop in your script that runs at a constant speed—such as once per minute, or 30 times per second. That is typically a difficult task, as the commands in the loop might take up a significant amount of time, or even an inconsistent amount of time.
In the past, many computer games suffered from solving this problem incorrectly. To control their game speed, game developers added commands to slow down their game. For example, after much tweaking and fiddling, the developers might realize that the game plays correctly on a typical machine if they make the computer count to one million every time it updates the screen. Unfortunately, these commands (such as counting)depend heavily on the speed of the computer. Since a fast computer can count to 1 million much more quickly than a slow computer, the game ends up running much quicker (often to the point of incomprehensibility)on faster computers!
To make your loop run at a regular speed, you can measure how long the commands in a loop take to complete, and then delay for whatever time is left, as shown in Example 4-1.
$loopDelayMilliseconds = 650 while($true) { $startTime = Get-Date ## Do commands here "Executing" $endTime = Get-Date $loopLength = ($endTime - $startTime).TotalMilliseconds $timeRemaining = $loopDelayMilliseconds - $loopLength if($timeRemaining -gt 0) { Start-Sleep -Milliseconds $timeRemaining } }
For more information about the Start-Sleep
cmdlet, type Get-Help Start-Sleep
.
See Also
Recipe 12.1, “Read a Line of User Input
Get Windows PowerShell Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.