| Executive Summary: The Windows PowerShell 101 series introduced you to Microsoft’s Windows PowerShell environment and the scripting language it supports. The lessons in that series describe concepts that provide a foundation for using PowerShell and writing PowerShell statements. The PowerShell 201 series digs deeper into those concepts and explains the PowerShell structures that let you fully leverage PowerShell. In Lesson 1, you'll learn how to use the foreach statement and the ForEach-Object cmdlet to iterate through collections. |
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.
Learning Path
Windows it pro resources
To read the “PowerShell 101” series, go to
“PowerShell 101, Lesson 1,” InstantDoc ID 97742
“PowerShell 101, Lesson 2,” InstantDoc ID 97959
“PowerShell 101, Lesson 3,” InstantDoc ID 98177
“PowerShell 101, Lesson 4,” InstantDoc ID 98447
“PowerShell 101, Lesson 5,” InstantDoc ID 98793
“PowerShell 101, Lesson 6,” InstantDoc ID 99104 |
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 = 3
crow = 4
robin = 5
wren = 4
jay = 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.length
Write-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 = 3
crow = 4
robin = 5
wren = 4
jay = 3
Total 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.
Continue to page 2
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.
I like to print out the articles to put in a binder so I have all of the Powershell series in one place, but the Figures turn out to be useless since the article prints without them and they are too small. Also, I know that magazines love to play with graphics to make themselves look classy, but NOTHING is better and easier on the eyes than black ink on white paper. The white on blue looks great on my monitor, but not so hot on the printed page.