Powershell – arrays and collections’s iteration


To totally unlock this section you need to Log-in


Login

To fully leverage the power of Windows PowerShell, you need to know how to use a foreach loop to iterate through collections such as a string array or a list of Windows services. PowerShell provides two types of foreach loops: the foreach statement and the ForEach-Object cmdlet. Although you can obtain the same results with both types of loops, they differ in several important respects.

But, before approaching these types of iterations, we need to know better how arrays can be manipulated in a Powershell session. Let's begin viewing how to create a simple static array and we will note the type System.Array:

$Fruits = "Apple","Pear","Banana","Orange"

$Fruits.GetType()

Powershell - arrays and collections's iteration

However, if we try to Add or Remove items to the array we get errors that the "Collection was of a fixed size":

$Fruits.Add("Kiwi")

$Fruits.Remove("Apple")
$Fruits.IsFixedSize

Powershell - arrays and collections's iteration

We can see that the array we originally created is of a fixed size. Note that it (a System.Array object) supports the modification of existing elements, but not the addition or removal of others.

Powershell - arrays and collections's iteration

One way to deal with this is to use a System.Collections.ArrayList instead:

[System.Collections.ArrayList]$ArrayList = $Fruits

$ArrayList.GetType()

Powershell - arrays and collections's iteration

Now if we try the previous add and remove methods we are successful:

$ArrayList.Add("Kiwi")

$ArrayList
$ArrayList.Remove("Apple")
$ArrayList

Powershell - arrays and collections's iteration

Alternatively, if we had stuck with the original Array object we could do the following to 'add' an item to it. (Actually it creates a new array with an additional item):

$New = $Fruits += "Kiwi"

$New
$New.GetType()

Powershell - arrays and collections's iteration

However, we can't remove items in this way:

$New2 = $Fruits -= "Apple"

Powershell - arrays and collections's iteration

We could instead do this:

$New3 = $Fruits -ne "Apple"

$New3

Powershell - arrays and collections's iteration

Taking your initial array you can convert it to a System.Collections.ObjectModel.Collection like this, which may be easier to remember than using the full type name of either a collection or array list:

$Collection = {$Fruits}.Invoke()

$Collection
$Collection.GetType()

Powershell - arrays and collections's iteration

Now we can add and remove items using the Add and Remove methods:

$Collection.Add("Melon")

$Collection
$Collection.Remove("Apple")
$Collection

Powershell - arrays and collections's iteration

Now that we have seen how arrays can be managed in Powershell and the main difference between the Array object and the ArrayList object, we can go forward with ForEach loops.

The foreach Statement

The foreach statement loops through the elements in a collection. The loop runs one time for each element, executing a block of statements called a script block. To create a foreach loop, you must define the collection that you will loop through, a variable to hold each element in that collection, and the script block that runs each time you step through the collection.

Let's take a look at an example to see how this works. The following command declares the $birds variable and initializes it with a string array, then uses the variable in a foreach statement:

     
$birds = "owl","crow","robin","wren","jay" 

foreach ($bird in $birds) {
"$bird = " + $bird.length
}

The foreach statement begins with the foreach keyword, followed by a set of parentheses that enclose three components ($bird in $birds).

The first component is the element variable, which you define specifically to use in the foreach statement. In this case, the element variable is $bird, but you can name the variable whatever you want, as long as you adhere to PowerShell’s naming conventions.

The element variable holds the collection's current value as the statement loops through the collection. For example, the $bird variable’s value is owl in the first loop, crow in the second loop, and so on.

The second component in the parentheses is the keyword in. Use this just as is. The third element is the collection itself, which in this case is accessed through the $birds variable.

Next comes a set of braces. The braces enclose the script block that executes whenever a loop runs. In this example, the block contains only one statement — "$bird = " + $bird.length — that creates a simple string, which is output to the console.

In this code, the $bird variable retrieves the collection value, and the Length property retrieves the number of characters in that value.

The command returns the results:

owl = 3

crow = 4
robin = 5
wren = 4
jay = 3

Although this example includes only one statement in the script block, you can include as many statements as necessary. For example, here's a script block that contains three statements:

$count = 0

$birds = "owl","crow","robin","wren","jay"
foreach ($bird in $birds) {
$count += 1
"$bird = " + $bird.length
Write-Host }
"Total number of birds is $count."

The first statement in the script block increments the $count variable by 1. (The $count variable is defined in the first line and is used to track the running total of collection elements.)

The second statement creates the string and outputs it to the console, as you saw in the last example. The third statement is a Write-Host cmdlet, which simply adds a blank line to the output.

When each loop runs, all three statements in the script block run. However, the code after the script block runs only once, after the last loop has completed.

This code uses $count within the outputted text. In this case, the value in $count is 5. This is the value assigned to that variable during the last loop, as shown in the results:

owl = 3

crow = 4
robin = 5
wren = 4
jay = 3
Total number of birds is 5.

Although the preceding commands assign the collection (a string array) to a variable, you don’t have to take this approach. You can define your collection directly within the foreach statement, and you can define a collection made up of other object types, as in:

foreach ($svc in Get-Service) {  

$svc.name + ": " + $svc.canstop.tostring().toupper()
}

In this code, the third component in the parentheses is Get-Service. This cmdlet returns a collection of objects, one object for each service on the local machine.

A service object is assigned to the $svc variable each time through the loop. In the loop, the foreach statement uses $svc to retrieve the service’s name (through the service object’s Name property) and appends a colon to it.

Next, foreach uses $svc to access the service object’s CanStop property, which returns a Boolean value that specifies whether the service can be stopped once it has started. Finally, the foreach statement calls the ToString and ToUpper methods to format that value.

You must first convert the CanStop property's value to a string with the ToString method before uppercasing it with the ToUpper method because the ToUpper method is available only to string values. If you don’t want to convert the results to uppercase, you don’t need to include the ToString method or the ToUpper method. Figure 1 shows the results:

Powershell - arrays and collections's iteration

Note that when you reference an object’s methods and properties, their names are case insensitive. For example, you can call the ToString method by using letters that are all lowercase (as we've done in previous examples), all uppercase, or mixed case.

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. 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 parenthesis 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() }

Powershell - arrays and collections's iteration

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 have 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 ForEach-Object 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.

Powershell - arrays and collections's iteration

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.

Powershell - arrays and collections's iteration

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.

1 thought on “Powershell – arrays and collections’s iteration”

  1. Managing arrays and loops in Powershell for automation scripts and to manage large data can be challenging, especially when dealing with fixed size arrays and pipening to ForEach loops. :cool:

    Read it more on HeelpBook​:

    Powershell – arrays and collections’s iteration – http://heelpbook.altervista.org/2015/powershell-arrays-and-collectionss-iteration/ #howto #powershell #microsoft #windows #automation #activedirectory Heelpbook Portal​ Stefano Maggi​

Comments are closed.