Iterating Through Collections with PowerShell's foreach Loops
Lesson 1 in the PowerShell 201 series explores how to use the foreach statement and ForEach-Object cmdlet
September 25, 2008
To fully leverage the power of Windows PowerShell, you need to know how to use a foreach loop to iterate through collections such as a string array or a list of Windows services. PowerShell provides two types of foreach loops: the foreach statement and the ForEach-Object cmdlet. Although you can obtain the same results with both types of loops, they differ in several important respects. In this lesson, I’ll explain the differences and demonstrate how to use the foreach statement and the ForEach-Object cmdlet. Note that this and subsequent lessons in the PowerShell 201 series build on concepts explained in the PowerShell 101 series. (See the Learning Path for the lessons in that series.)
The foreach Statement
The foreach statement loops through the elements in a collection. The loop runs one time for each element, executing a block of statements called a script block. To create a foreach loop, you must define the collection that you’ll loop through, a variable to hold each element in that collection, and the script block that runs each time you step through the collection.
Let’s take a look at an example to see how this works. The following command declares the $birds variable and initializes it with a string array, then uses the variable in a foreach statement:
$birds = "owl","crow","robin","wren","jay" foreach ($bird in $birds) { "$bird = " + $bird.length }
The foreach statement begins with the foreach keyword, followed by a set of parentheses that enclose three components ($bird in $birds). The first component is the element variable, which you define specifically to use in the foreach statement. In this case, the element variable is $bird, but you can name the variable whatever you want, as long as you adhere to PowerShell’s naming conventions. The element variable holds the collection’s current value as the statement loops through the collection. For example, the $bird variable’s value is owl in the first loop, crow in the second loop, and so on.
The second component in the parentheses is the keyword in. Use this just as is. The third element is the collection itself, which in this case is accessed through the $birds variable.
Next comes a set of braces. The braces enclose the script block that executes whenever a loop runs. In this example, the block contains only one statement—"$bird = " + $bird.length—that creates a simple string, which is output to the console. In this code, the $bird variable retrieves the collection value, and the Length property retrieves the number of characters in that value.
The command returns the results
owl = 3crow = 4robin = 5wren = 4jay = 3
Although this example includes only one statement in the script block, you can include as many statements as necessary. For example, here’s a script block that contains three statements:
$count = 0$birds = "owl","crow","robin", ` "wren","jay"foreach ($bird in $birds){ $count += 1 "$bird = " + $bird.lengthWrite-Host}"Total number of birds is $count."
The first statement in the script block increments the $count variable by 1. (The $count variable is defined in the first line and is used to track the running total of collection elements.) The second statement creates the string and outputs it to the console, as you saw in the last example. The third statement is a Write-Host cmdlet, which simply adds a blank line to the output.
When each loop runs, all three statements in the script block run. However, the code after the script block runs only once, after the last loop has completed. This code uses $count within the outputted text. In this case, the value in $count is 5. This is the value assigned to that variable during the last loop, as shown in the results
owl = 3crow = 4robin = 5wren = 4jay = 3Total number of birds is 5.
Although the preceding commands assign the collection (a string array) to a variable, you don’t have to take this approach. You can define your collection directly within the foreach statement, and you can define a collection made up of other object types, as in
foreach ($svc in Get-Service){ $svc.name + ": " + $svc.canstop.tostring().toupper()}
In this code, the third component in the parentheses is Get-Service. This cmdlet returns a collection of objects, one object for each service on the local machine. A service object is assigned to the $svc variable each time through the loop. In the loop, the foreach statement uses $svc to retrieve the service’s name (through the service object’s Name property) and appends a colon to it. Next, foreach uses $svc to access the service object’s CanStop property, which returns a Boolean value that specifies whether the service can be stopped once it has started. Finally, the foreach statement calls the ToString and ToUpper methods to format that value. You must first convert the CanStop property's value to a string with the ToString method before uppercasing it with the ToUpper method because the ToUpper method is available only to string values. If you don’t want to convert the results to uppercase, you don’t need to include the ToString method or the ToUpper method. Figure 1 shows the results.
Note that when you reference an object’s methods and properties, their names are case insensitive. For example, you can call the ToString method by using letters that are all lowercase (as I’ve done in my examples), all uppercase, or mixed case.
Whenever you define a collection in a foreach statement, you’re basically implementing a pipeline. In the example just given, the pipeline is made up of the output from the Get-Service cmdlet. However, you can implement more complex pipelines, as in
foreach ($svc in Get-Service |where {$_.status -eq 'running' }){ $svc.name + ": " + $svc.canstop.tostring(). toupper()}
In this command, the output from Get- Service is piped to the Where-Object cmdlet (referenced by the where alias), which limits the values returned by Get-Service to only those service objects whose Status property value is running. As I discussed in “Power- Shell 101, Lesson 2” (March 2008, Instant- Doc ID 97959), the Where-Object cmdlet uses the built-in $_ variable to access the current value in the pipeline. Figure 2 shows sample results returned by this command.
As you can probably deduce, including the entire pipeline within the parentheses could get a bit unwieldy. A better approach might be to assign the service objects to a variable, then call that variable in a foreach statement, as in
$svcs = Get-Service |where {$_.status -eq 'running'}foreach ($svc in $svcs){ $svc.name + ": " + $svc.canstop.tostring(). toupper()}
As you can see, the foreach statement uses $svcs to call the collection. This command returns the same results as those returned by the previous command.
The Foreach-Object Cmdlet
You’ve seen how to use a foreach statement to step through a collection, but that’s not the whole story. PowerShell also includes the ForEach-Object cmdlet—and to keep things interesting, foreach is the name of the built-in alias used to reference that cmdlet.
The ForEach-Object cmdlet receives a collection from the pipeline and loops through that collection just like a foreach statement. For example, the following command returns the same results (shown in Figure 2) as those returned by the previous two commands:
Get-Service |where {$_.status -eq 'running'} |foreach { $_.name + ": " + $_.canstop.tostring().toupper()}
This statement begins by piping Get- Service’s output to the Where-Object cmdlet. The collection returned by Where- Object is then piped to the ForEach-Object cmdlet (referenced by the foreach alias). Notice that the foreach alias is followed only by a script block—there isn’t any code in parentheses. The implication of this difference between the foreach statement and the ForEach-Object cmdlet is that, instead of defining an element variable, you use the $_ built-in variable. Otherwise, the rest of the script block is the same as the preceding two examples. (Note that you must place the opening brace on the same line as the foreach alias; otherwise PowerShell treats the first line as a complete statement.)
But how does PowerShell distinguish between the foreach keyword and the foreach alias? If foreach appears at the beginning of a statement, PowerShell interprets it as the keyword and processes the code that follows as a foreach statement. If it appears anywhere else, PowerShell interprets it as the ForEach-Object cmdlet alias.
PowerShell supports another alias to reference the ForEach-Object cmdlet: the percent (%) sign. For example, the statement
Get-Service |where {$_.status -eq'running'} |% { $_.name + ": " + $_.canstop.tostring().toupper()}
returns the same results as the preceding example, except that it uses % rather than foreach.
The Differences
Although you can use the foreach statement or ForEach-Object cmdlet to return the same results, there are several differences between them. First, as you’ve already seen, the cmdlet is a little simpler because you don’t have to create a special element variable. Instead, you use the $_ built-in variable.
Another difference is the way PowerShell processes the two statements. When PowerShell processes a foreach statement, it generates the entire collection before processing individual values. When PowerShell processes a ForEachObject cmdlet, it processes each value as it passes through the pipeline, so it uses less memory at any given time. If memory usage is an important consideration, you’ll want to use the cmdlet.
A third difference is that you can pass the ForEach-Object cmdlet’s output down the pipeline, but you can’t do this with the foreach statement’s output. For example, the following code passes the ForEach-Object cmdlet’s output to the Sort-Object cmdlet:
Get-Service |where {$_.status -eq 'running'} |foreach { $_.name + ": " + $_.canstop.tostring().toupper()} | sort -descending
The Sort-Object cmdlet (referenced by the sort alias) sorts the output in the pipeline in descending order, as Figure 3 shows.
Another advantage of the ForEach- Object cmdlet over the foreach statement is that the cmdlet supports three types of script blocks, as shown in the code
Get-Service |where {$_.status -eq 'running'} |foreach { $count = 0 } { $_.name + ": " + $_.canstop.tostring().toupper() $count ++ } { Write-Host “$count services are running.” Write-Host}
The first script block assigns 0 to the $count variable. This variable tracks the number of elements in the collection. The second script block retrieves the Name and CanStop property values for each service and increases the $count value by 1. The third script block prints a message that includes the total number of services, based on the last value in $count.
When you include three script blocks in this way, PowerShell runs the first block before the first loop, runs the second block one time for each loop, and runs the third block after the last loop. If you refer to Figure 4, you can see how the last script block displays a total number of services.
Moving Forward
The foreach statement and ForEach-Object cmdlet provide powerful tools for working with collections. You can use either one to create loops that execute a set of statements for each element in a collection. You’ll find that you’ll use foreach loops often in your PowerShell scripts. And as you’ll see in subsequent lessons, you can create far more complex commands than what I’ve shown you so far.
About the Author
You May Also Like