Friday, September 23, 2016

PowerShell v2 Foreach Versus Foreach Object

Last week Justin Rich and I were discussing something and the distinction between foreach and Foreach-Object came up.  Having never really dug into this I decided to check it out and found some interesting things. Until we started talking about it I always used foreach.  This is a construct where you can pass a collection to the keywork (foreach) and then reference individual objects in a scoped block. In practice it looks like this:
$integers = 1..10
foreach($int in $integers)
{
  $int
}
So, the 1..10 stores an array of numbers 1 to 10 in $integers. When you place the $integers object in the foreach as shown above it iterates over the collection one item at a time so you can do as you like with it in the block.

Now, what I did not understand until I started looking into this "keyword" is that it is not actually a separate construct. In fact, it is just an alias for Foreach-Object as proven by these commands:
  • Get-Help foreach
  • Get-Alias foreach
The real question, then, is Foreach-Object. According to the help we see this in v2:
Get-Help ForEach-Object

NAME
ForEach-Object

SYNOPSIS
Performs an operation against each of a set of input objects.


SYNTAX
ForEach-Object [-Process] <ScriptBlock[]> [-Begin <scriptblock>] [-End <scriptblock>] [-InputObject <psobject>] [<CommonParameters>]


DESCRIPTION
The ForEach-Object cmdlet performs an operation on each of a set of input objects. The input objects can be piped to the cmdlet or specified by using the InputObject parameter.

The operation to perform is described within a script block that is provided to the cmdlet as the value of the Process parameter. The script block can contain any Windows PowerShell script.

Within the script block, the current input object is represented by the $_ variable.
In addition to using the script block that describes the operations to be carried out on each input object, you can provide two additional script blocks. One, specified as the value of the Begin parameter, runs before the first input object is processed. The other, specified as the value of the End parameter, runs after the last input object is processed.

The results of the evaluation of all the script blocks, including the ones specified with Begin and End, are passed down the pipeline.


RELATED LINKS
Online version: http://go.microsoft.com/fwlink/?LinkID=113300

REMARKS
To see the examples, type: "get-help ForEach-Object -examples".
For more information, type: "get-help ForEach-Object -detailed".
For technical information, type: "get-help ForEach-Object -full".
Having gotten more information I can now see how foreach and Foreach-Object connect. With the Foreach-Object cmdlet (as opposed to the aliased foreach) you use the pipeline placeholder ($_) instead of a named placeholder ($int in our example above).  Also, Foreach-Object allows you to tap into the -Begin, -Process, and, -End blocks, where -Process (that is pipelined operations) are in fact the default used without specification. Whats cool about this is you can use the -Begin and -End blocks to help iterate over a collection and handle initialization and clean up all in one single cmdlet call.  For instance, if I want to install a bunch of ServerManager module Windows feature, but, report on it as part of an installation for logging, I can do this with Foreach-Object:
$ServerRoles = @(Web-Server,Web-Webserver)
# Output status to hostWrite-Output "$(wts): Attempting to install server roles."

# Install server roles
$ServerRoles | ForEach-Object `
  -Begin { `
    Write-Output "$(wts): Attempting to install server role." `
  } `
  -Process { `
    Write-Output "$(wts): Attempting to install $_." `
    Add-WindowsFeature -FeatureName $_ | Out-Null `
  }
Using a more easily demonstrated example, watch how this functions with the -Begin, -Process, and, -End block:

$array = 1,2,3,4
$array | ForEach-Object `
`
-Begin {
    "Starting"
} `
`
-Process { 
    $_
} `
`
-End {
    "Done"
}
This takes an array of 1,2,3,4, spits out the word Starting once, iterates over the array (lists 1,2,3,4), and, to close, writes Done on the host.  See results below:


Starting
1
2
3
4
Done

To met this really cool. I have always used foreach and had to wrap beginning and closing statements, for reporting, in separate Write-Output statements.  This is much more succinct and clean.

Whats more is you can simple store scriptblocks as your desired functionality in variables (or here strings) and pass the variables to the Foreach-Object:


$array = 1..4
$begin = { "Starting" }
$process = { $_ } 
$end = { "Done" }
$array | ForEach-Object -begin $begin -process $process -end $end

This is much easier to read, more organized, and, runs exactly the same as the previous output.

Related Post:

0 comments:

Post a Comment

 
Copyright 2009 Information Blog
Powered By Blogger