Looping (or going through a list of objects one at a time) is a fundamental concept in any language, and PowerShell is no exception. There will come a time when you will need to execute a block of code numerous times. PowerShell is well equipped to handle this for you.
This section may be a bit confusing, as there is a difference between Foreach
and Foreach-Object
. Take a look at figure 23.1 for a visual representation of how Foreach
works.
Probably the most common form of looping is the Foreach
command. Foreach
allows you to iterate through a series of values in a collection of items, such as an array. The syntax of a Foreach
command is
The process block (the part surrounded by {}
) will execute as many times as the number of collection objects. Let’s look at the following command and break it down:
First, we made a variable called $array
that will contain an array of numbers from 1 to 10. Next, we are making a temporary variable ($a
) and assigning it to the current item in the collection that we are working with. The variable is available only inside the script block and will change as we iterate through the array.
Finally, the script block represented by the curly braces { }
will output $a
to the screen (figure 23.2).
The Foreach-Object
cmdlet performs an operation defined in a script block on each item in the input collection objects. Most frequently, the Foreach-Object
is called via the pipeline.
TIP Use Foreach
if you are looping through multiple objects, and use Foreach-Object
if you are using it in the pipeline.
Let’s look at the command Get-ChildItem | ForEach-Object {$_.name}
. First, we are running the command Get-ChildItem
and sending the objects down the pipeline to the Foreach-Object
cmdlet.
Next, we are saying for every item received from Get-ChildItem
, run the command $_.name
(figure 23.3). If you recall from earlier in the text, $_
is simply the current object in the pipeline. By using $_
.Name
, we are taking the name
property from the object and displaying it on the screen.
For both the Foreach
and Foreach-Object
cmdlets, the commands are executed sequentially, meaning it will take item[0]
, run the commands you have specified, followed by the following item[1]
, and so on until the input collection is empty. Usually this isn’t a problem, but eventually, if you have a lot of commands in the process block or your input collection is enormous, you can see where executing these one at a time would impact your script’s run time.
Hopefully, before you started diving into the chapter, you used the help feature to look at all the parameters available for Foreach-Object
.
Try it Now Run get-help
Foreach-Object
and review the results.
As we mentioned before, the main drawback with the Foreach-Object
command has been that it runs sequentially. There have been a few community-driven modules to help enable a parallel feature for the Foreach-Object
command. With the introduction of PowerShell 7 (preview 3), a new -Parallel
parameter was added to the Foreach-Object
command. Instead of the command(s) being run sequentially, we can now run the same commands on most or all of our input objects at the same time. For example, suppose you are creating 1,000 new users in Active Directory. You could run the command
which would run the New-Aduser
command 1,000 times sequentially. Or you can run the command with the Parallel
parameter:
The following command takes an array of numbers (1–5) and pipes it to a traditional Foreach-Object
command, writes the output to the screen, and sleeps for 2 seconds (figure 23.4).
We can see by using the measure-command
cmdlet that this will take 10 seconds to complete.
PS C:Scripts> measure-command {1..5 | ForEach-Object {Write-Output "$_"; ➥ start-sleep -Seconds 2}} Days : 0 Hours : 0 Minutes : 0 Seconds : 10 Milliseconds : 47 Ticks : 100471368 TotalDays : 0.000116286305555556 TotalHours : 0.00279087133333333 TotalMinutes : 0.16745228 TotalSeconds : 10.0471368 TotalMilliseconds : 10047.1368
When we add the -parallel
parameter, we will execute what is inside the command block on all the numbers in the array at once.
By using the parallel
parameter, we decreased our run time from 10 seconds to 2 seconds.
PS C:Scripts> measure-command {1..5 | ForEach-Object -parallel {Write-Output ➥ "$_"; start-sleep -Seconds 2}} Days : 0 Hours : 0 Minutes : 0 Seconds : 2 Milliseconds : 70 Ticks : 20702383 TotalDays : 2.39610914351852E-05 TotalHours : 0.000575066194444444 TotalMinutes : 0.0345039716666667 TotalSeconds : 2.0702383 TotalMilliseconds : 2070.2383
Because each script block is running simultaneously, the order in which the results are returned to the screen cannot be guaranteed. There is also a throttle limit or the maximum number of script blocks that can be run in parallel at once that we need to make sure you know about—the default is 5. In our example, we had only 5 items in our input collection, so all 5 script blocks were running simultaneously. However, if we change our example from 5 items to 10 items, we will notice the run time changes from 2 seconds to 4 seconds. We can, however, change the throttle limit to a higher one by using the -throttlelimit
parameter.
Try it Now Change the array to 10 items; then use the measure-command
cmdlet to see how long it takes to execute.
There is, however, a limitation with the parallel
feature. In order to run each script block simultaneously, a new runspace is created. This can lead to significant performance degradation if the script blocks you are running are resource intensive.
If you have done any kind of scripting or programming before, then a while
loop should not be new concept to you. A while
loop is an iterative loop, or it will run until the terminating condition is satisfied. Like the Foreach
loop we just talked about, the while
loop has a script block, where you can put your commands to be executed (figure 23.5). The basic syntax is as follows: While
(condition
) {commands
}.
Condition—A Boolean ($True
or $False
) statement. The loop will execute while the condition is True
and will terminate when the condition is False
. Example: While ($n -ne 10)
.
Commands—Simple or complex commands that you want to execute while the condition is True
.
We can also start adding logic operators such as -and
and -or
into our condition statement:
TIP If you were to run the above command, it would run indefinitely unless you happened to run it on 25-December
. Use Ctrl-C to break the execution.
As we mentioned before, the while
loop will execute only while the condition is true
. But what if you wanted to execute the loop at least once regardless of whether the condition was true
or not? That is where the Do While
loop comes into play.
With Do
{commands
} While
(condition
), notice that the script block and condition block are reversed. This will allow us to execute the script block at least one time, then evaluate our condition to see if we need to repeat the loop:
$date = get-date do { Write-Output "Checking if the month is December" $date = $date.AddMonths(1) } while ($date.Month -ne 12 )
Find a directory that has a lot of items in it. Use a Foreach
loop and count the number of characters in each filename.
Start the notepad process (or text editor of your choice); then write a do while
loop that will display the following text until the process is closed: $process is open
.
$items = Get-ChildItem SOMEWHERE YOU |CHOSE
foreach ($i in $items){Write-Output "The character length of $i is
18.118.135.250