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.