The ForEach statement is used for stepping through a collection of objects. Usually some block of code is executed for each step when the ForEach statement is used. In other words, "take these steps for each thing in the collection of things." Here's the syntax for this statement:
foreach ($<item> in $<collection>){<command_block>}
This statement is expecting a variable and a collection in parenthesis. The command block that is contained in the braces will be executed for each variable, each time it goes through the collection. The command block can be as simple as something like this:
PS C:\> $var=("apple","banana","pineapple","orange") PS C:\> foreach ($fruit in $var) {$fruit} apple banana pineapple orange PS C:\>
We first create an array of fruits. Remember that an array is a collection. The ForEach statement says that for each fruit variable in the fruit collection ($var), display the value of the fruit variable.
Here's a slightly more involved example:
ForEachFruit.ps1
#ForEachFruit.ps1 $var=("apple","banana","pineapple","orange") foreach ($fruit in $var) { $i++ #this is a counter that is incremented by one each time through write-host "Adding" $fruit } write-host "Added" $i "pieces of fruit"
When this script is run, it produces the following output:
Adding apple Adding banana Adding pineapple Adding orange Added 4 pieces of fruit PS C:\>
We can even nest other logic constructs within a ForEach statement:
ForEachFile.ps1
#ForEachFile.ps1 set-location "C:\" $sum=0 foreach ($file in get-childitem) { #$file.GetType() if (($file.GetType()).Name -eq "FileInfo") { write-host $file.fullname `t $file.length "bytes" $sum=$sum+$file.length $i++ } } write-host "Counted" $i "file for a total of" $sum "bytes."
In this script we're using the Get-Childitem cmdlet to return all items in C:\. We can do this because the results of the Get-Childitem cmdlet return a collection object. So even though we don't know the contents of the collection, we can still enumerate on the fly. For each $file variable in the collection, if the object type name is FileInfo, then we display the name and file size (using the length property). We've also added code to calculate a running total of the sum of all the files using $sum and we use $i as a counter that increases by one each time.
When the script is run it generates the following output:
C:\AUTOEXEC.BAT 0 bytes C:\AVG7QT.DAT 12283633 bytes C:\COMLOG.txt 0 bytes C:\CONFIG.SYS 0 bytes C:\docs.csv 24938 bytes C:\DVDPATH.TXT 55 bytes C:\EventCombMT_Debug.log 854 bytes C:\hpfr5550.xml 488 bytes C:\IALog.txt 271 bytes C:\log.csv 10734 bytes C:\Log.txt 72 bytes C:\mtaedt22.exe 2650696 bytes C:\netdom.exe 142848 bytes C:\new-object.txt 10240 bytes C:\out-grid.ps1 811 bytes C:\out-propertyGrid.ps1 1330 bytes C:\processes.html 118828 bytes C:\servers.txt 19 bytes C:\showprocessinfo.ps1 710 bytes C:\showservices.ps1 477 bytes C:\test.ps1 88 bytes C:\txt.csv 22995 bytes Counted 22 file for a total of 15270087 bytes. PS C:\>
Finally, we can also use ForEach in a pipeline to process output from a cmdlet or other operation. This is the syntax:
<command> | foreach {<command_block>}
For example, you might want to try something like this:
PS C:\> get-service | foreach {write-host $_.displayname ` "("$_.status")"} Alerter ( Stopped ) Application Layer Gateway Service ( Stopped ) Application Management ( Stopped ) ASP.NET State Service ( Stopped ) Windows Audio ( Running ) AVG7 Alert Manager Server ( Running ) AVG7 Update Service ( Running ) AVG E-mail Scanner ( Running ) #output abbreviated
When used in this manner we're putting the collection first and pipelining it to ForEach. The script block uses the $_ variable to indicate the current item in the collection. In this case, the collection is a service object, which means we can show its displayname and status.
You might use this technique in a script like this to add some extra features:
ForEachSvc.ps1
#ForEachSvc.ps1 get-service | foreach { if ($_.status -eq "Running") { write-host $_.displayname "("$_.status")" -foregroundcolor "Green" write-host `t "Service Type="$_.servicetype -foregroundcolor "Green" } else { write-host $_.displayname "("$_.status")" -foregroundcolor "Red" } }
This is essentially the same command except that we've nested an If statement that says if the service status is running, then display the information in green and also show the service type. Otherwise, display the information in red.
Alias Alert
The ForEach statement is actually an alias for the ForEach-Object cmdlet. This doesn't really change how we use it. However, we're pointing this out in case you find examples using the cmdlet. Using the alias just means a little less typing.