Prof. Powershell
Extending the Reach of ForEach in PowerShell
Here's how to make the ForEach command work for you.
- By Jeffery Hicks
- 07/30/2013
Most of the time we rely on the PowerShell pipeline and the fact that cmdlets can often accept pipelined input.
Get-Service w* | where {$_.status –eq 'running'} | restart-service
You probably are also aware of ForEach-Object when you need to handle objects in an individual basis, such as invoking a method or running multiple commands on the same object.
PS C:\> get-content C:\work\testnames.txt | foreach { get-service wuauserv -computer $_ | Select Machinename,Status,Name} | format-table -autosize
MachineName Status Name
----------- ------ ----
chi-dc04 Running wuauserv
chi-dc01 Running wuauserv
chi-dc02 Running wuauserv
I realize there are other ways to use PowerShell to get the same result that might be more efficient to stick with me. I just wanted a simple example to demonstrate ForEach-Object. In this example I'm getting a list of computer names from the text file. For each name (or object) I want to run Get-Service for the Windows Update service on the specified computer. In this context, the $_ indicates the current object in the pipeline, or each name from the list. I then grab a few properties and format the result as a nice table. In PowerShell 3.0 you can also use $psitem instead of $_.
But there's more to ForEach-Object than what you might expect. ForEach-Object actually has 3 scriptblocks, referenced by parameters. The one we are used to seeing is the –Process scriptblock. ForEach-Object gives you a way from the command prompt to process objects the way a cmdlet or an advanced function would. The other parameters you can use are –Begin and –End.
The –Begin scriptblock is a chunk of PowerShell code that runs once before anything is processed from the pipeline. Then there's the –Process scriptblock you are familiar with. The parameter name is positional which is why you may not have known what it is called. The code in this scriptblock is run once on every piped in object. Finally there is the –End scriptblock which runs once after everything has been processed.
Here's a revised version of my command using all the scriptblocks:
get-content C:\work\testnames.txt |
foreach -Begin {
#initialize some variables before processing any names
$offline=@()
$found=@()
$myErr=@()
} -Process {
<#
run this code for each name in the list. I'll define a variable
for the computername to keep things clear and also so that I
can reference it in the Catch scriptblock in the event of an error.
#>
$computer = $_
#test if the computer is running
Write-Host "Processing $computer" -ForegroundColor Cyan
if (Test-Connection -computername $computer -quiet -Count 1) {
#get the service and add it to the results
Try {
$found+=Get-Service -Name wuauserv -ComputerName $computer -ErrorAction Stop
}
Catch {
#add the exception message and name to the error variable
$myErr+="Error querying $computer. $($_.exception.message)."
}
}
else {
#computer is offline so add it $offline
$offline+=$psitem
}
} -end {
#display results after all names have been processed
$found | Select Machinename,Status,Name
#display problems and errors if detected
if ($offline) {
Write-Warning "Failed to query $($offline -join ",")"
}
if ($myErr) {
Write-Warning ($myErr | Out-String)
}
}
The code has a number of comments so I won't repeat myself. In figure 1 you can see the end result:
The –Begin scriptblock executes once defining some variables. Then each name is processed and finally the –End scriptblock runs displaying the results and problems. Again, use my sample here as a learning aid, not as indication of how to get services from a remote computer. ForEach-Object has a lot to offer beyond what you may be used to so take some time to tinker.
About the Author
Jeffery Hicks is an IT veteran with over 25 years of experience, much of it spent as an IT infrastructure consultant specializing in Microsoft server technologies with an emphasis in automation and efficiency. He is a multi-year recipient of the Microsoft MVP Award in Windows PowerShell. He works today as an independent author, trainer and consultant. Jeff has written for numerous online sites and print publications, is a contributing editor at Petri.com, and a frequent speaker at technology conferences and user groups.