PowerShell unfortunately makes it quite easy to write inefficient code. Many people, for example, use the += syntax to populate an array. That is not recommended.

1
2
3
4
5
# Please do not do it this way!
$Array = @()
for ($i = 0; $i -lt 10000; $i++) {
    $Array += $i
}

We can measure how long the execution of a scriptblock takes with Measure-Command. On my test VM, the execution of the code above took over 2 seconds.

The result of Measure-Command: TotalMilliSeconds 2260

Reason

The reason is that arrays in PowerShell are actually static. They cannot really be extended with new entries. Instead, the contents of the array are copied, the new entry is added, and the result is stored in a new array object. In most cases, the old array is then stored in the same variable as before, which makes this process not very visible and easy to overlook.

Alternatives

There are several alternatives to the += method.

Capture the pipeline

If the data in the array only needs to be created once and does not need to be modified, I prefer the following approach. I take the variable in which I want to store the array and simply assign the output of my loop to it. No assignment takes place inside the loop itself; instead, an object is output. Initializing the variable as an array, for example with $MeinArray = @(), is also unnecessary in this case.

1
2
3
4
# $MeinArray receives the output of the loop as its content
$MeinArray = for ($i = 0; $i -lt 10000; $i++) {
    $i # This output is automatically stored in the variable $MeinArray
}

Generic List

If the data also needs to be changed, a generic list is practical. It must be initialized using a .NET method, which is slightly more complicated than normal PowerShell cmdlets. But the code can of course be copied, so it is not really a problem.

1
2
3
4
$List = [Collections.Generic.List[PSObject]]::new()
for ($i = 0; $i -lt 10000; $i++) {
    $List.Add($i)
}

I also show this in more detail in a video in the free PowerShell course.

Other alternatives

There are also other alternatives, such as hashtables.

1
2
3
4
$MeineHashtable = @{"Hallo"="Hallo PowerShell"}
# Various ways to add data to the hashtable
$MeineHashtable["hi"] = "Hi PowerShell :)"
$MeineHashTable.Add("moin","MOIN MOIN!")

Improvements in PowerShell 7.5

From PowerShell 7.5 onward (expected to arrive in November 2024), the problem is somewhat mitigated. The += method works much faster there than in previous PowerShell versions. It is still not 100 percent ideal, however, because data still needs to be copied unnecessarily through memory.

+= is much faster in PowerShell 7.5 than in previous versions. Here 55ms instead of 2768ms.

Measure performance

With the following code, I measured the speed of += vs. generic lists. The test code for “Allocated Memory” only works in PowerShell 7, not in Windows PowerShell 5.1.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#Requires -Version 7.4
$Host.UI.RawUI.WindowTitle = $PSVersionTable.PSVersion
Write-Host "Using PowerShell $($PSVersionTable.PSVersion)" -ForegroundColor DarkYellow
"--------------------------------------"

#region Test Array +=
$s = [gc]::GetTotalAllocatedBytes($true)

$Time = (Measure-Command {
    &{
        $val = @()
        for ($i = 0; $i -lt 10000; $i++) {
            $val += $i
        }
    }
}).TotalMilliseconds

$e = [gc]::GetTotalAllocatedBytes($true);
$RAM = [math]::Round(($e-$s)/1MB,2)

Write-Host "Using += took $($Time)ms"
Write-Host "Allocated Memory $($RAM)MB"
#endregion

"--------------------------------------"

#region Test List

$s = [gc]::GetTotalAllocatedBytes($true)

$Time = (Measure-Command {
    &{
        $List = [Collections.Generic.List[PSObject]]::new()
        for ($i = 0; $i -lt 10000; $i++) {
            $List.Add($i)
        }
    }
}).TotalMilliseconds

$e = [gc]::GetTotalAllocatedBytes($true);
$RAM = [math]::Round(($e-$s)/1MB,2)

Write-Host "Using List.Add() took $($Time)ms"
Write-Host "Allocated Memory $($RAM)MB"
#endregion