Tobias Wollgam
Tobias Wollgam

Reputation: 789

Adding element to array in powershell scriptblock converts array to string

I noticed odd behaviour using arrays in scriptblocks. The following code shows the problem:

$array = @("x", "y")

Write-Host "$($array.GetType().Name)"
Write-Host "$($array.GetType().BaseType)"

$bad = {
    $array += "z"
    Write-Host "$($array.GetType().Name)"
    Write-Host "$($array.GetType().BaseType)"
    $array
}

$good = {
    $array = $array.Clone()
    $array += "z"
    Write-Host "$($array.GetType().Name)"
    Write-Host "$($array.GetType().BaseType)"
    $array
}

& $good
& $bad

Executing the script will produce the following output:

Object[]
array
Object[]
array
x
y
z
String
System.Object
z

The scriptblock $bad does not work as I would expect. It converts the array to string, but it should simply add the element z to the array. If there is no element added, the array can be used as expected.

I noticed this behaviour in powershell 5.0 and 5.1 but not in the ISE. Is it a bug or can anyone explain this?

Upvotes: 1

Views: 910

Answers (2)

Tobias Wollgam
Tobias Wollgam

Reputation: 789

I would like to post my preferred solution which bases on Ansgars explanation:

$array = @("x", "y")

$not_bad = {
    $array = $array + "z"
    Write-Host "$($array.GetType().Name)"
    Write-Host "$($array.GetType().BaseType)"
    $array
}

& $not_bad

Important is the assignment to the local variable (or better to create a local variable) before adding further elements. A simple

$array = $array

would do, but this line may be confusing.

Upvotes: 0

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200273

It's a scope issue. The variable on the left side of the assignment operation in the scriptblocks is defined in the local scope.

This statement

$array = $array.Clone()

clones the value of the global variable $array and assigns it to the local variable $array (same name, but different variable due to different scope). The local variable $array then contains a copy of the original array, so the next statement

$array += "z"

appends a new element to that array.

In your other scriptblock you immediately append a string to the (local) variable $array. In that context the local variable is empty, so $array += "z" has the same effect as $array = "z", leaving you with a variable containing just the string "z".

Specify the correct scope and you'll get the behavior you expect:

$array = @("x", "y")

$not_bad = {
    $script:array += "z"
    Write-Host "$($script:array.GetType().Name)"
    Write-Host "$($script:array.GetType().BaseType)"
    $script:array
}

& $not_bad

Beware, however, that this will actually modify the original array in the global/script scope (your $good example leaves the original array unchanged).

I'm not sure if I would consider this behavior a bug, but it's definitely a gotcha.

Upvotes: 2

Related Questions