1

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?

Tobias Wollgam
  • 761
  • 2
  • 8
  • 25

2 Answers2

2

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.

Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • Thanks. While your answer is right I realize that the script example may not archive the whole problem. The original script has different behaviour in console and ISE. The example script behaves equal. – Tobias Wollgam Jun 15 '18 at 10:18
  • ISE and console are different host processes that are known to behave differently in some respects. I'd suggest posting a new question with a updated code and problem description if that issue persists. – Ansgar Wiechers Jun 15 '18 at 10:36
0

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.

Tobias Wollgam
  • 761
  • 2
  • 8
  • 25
  • It is hard to believe that the different behaviour of `$a = $a + "z"` and `$a += "z"` was intention and not a bug. – Tobias Wollgam Jun 15 '18 at 13:35
  • 1
    `$a += "z"` isn't just syntactic sugar for `$a = $a + "z"`. It's semantic is actually slightly different The latter statement means "append 'z' to the variable on the right-hand side and assign the result to the variable on the left-hand side" whereas the former means "append 'z' to the variable on the left-hand side". They produce the same result when you're using the same variable on both sides of the assignment. However, in your particular scenario that is not the case (`$a` on the left-hand side is in the local scope, `$a` on the right-hand side is in the global scope). – Ansgar Wiechers Jun 16 '18 at 20:58
  • You are totally right. But it makes code hard to understand (and to write). In `c++` one can code different behaviour for both operators `+` and `+=` but one should not, cause it is confusing. In `PS` it is by design!? – Tobias Wollgam Jun 18 '18 at 07:07
  • The problem is not with the operators per se, but with the different evaluation of scopes on either side of the assignment. Like I said, it's definitely a gotcha. I don't know the reasons for the decision to make PowerShell (or the underlying .Net framework) evaluate things that way, though. Maybe @BrucePayette could shed some light. – Ansgar Wiechers Jun 18 '18 at 08:16