Search code examples
arrayspowershellscriptblock

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?


Solution

  • 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.