4

Some background: When an empty pipeline is assigned to a variable, the value is [System.Management.Automation.Internal.AutomationNull]::Value as explained here https://stackoverflow.com/a/22365076/37572

AutomationNull has some interesting properties

  • AutomationNull -eq $null
  • AutomationNull -is [psobject]
  • if AutomationNull is passed to function($x), then the $x argument value is a simple $null

The code below assigns AutomationNull to an array member and then calls function f($x) with the array as the argument. The surprising result is that the array member that used to contain AutomationNull now contains a simple $null.

"PSVersion "+ $PSVersionTable.PSVersion

Write-Host -ForegroundColor Yellow "******** array initialized"
$array = @(0,$null,2)

for($i=0; $i -lt $array.Count; $i++) {
    if ($null -eq $array[$i]) {
        if ($array[$i] -is [psobject]) {"array[$i] is null and [psobject]"
        } else {"array[$i] is null"}
    } else {"array[$i] is " + $array[$i]}
}

Write-Host -ForegroundColor Yellow "******** array[1] set to empty pipeline"
$emptypipe = (@()|%{1})
$array[1] = $emptypipe

for($i=0; $i -lt $array.Count; $i++) {
    if ($null -eq $array[$i]) {
        if ($array[$i] -is [psobject]) {"array[$i] is null and [psobject]"
        } else {"array[$i] is null"}
    } else {"array[$i] is " + $array[$i]}
}


function f($x) {Write-Host -ForegroundColor Yellow '******** called: f -x $array'}
f -x $array

for($i=0; $i -lt $array.Count; $i++) {
    if ($null -eq $array[$i]) {
        if ($array[$i] -is [psobject]) {"array[$i] is null and [psobject]"
        } else {"array[$i] is null"}
    } else {"array[$i] is " + $array[$i]}
}

Results:

PSVersion 3.0
******** array initialized
array[0] is 0
array[1] is null
array[2] is 2
******** array[1] set to empty pipeline
array[0] is 0
array[1] is null and [psobject]
array[2] is 2
******** called: f -x $array
array[0] is 0
array[1] is null
array[2] is 2

So it seems that simply calling f($x) with the array as the argument has changed the contents of the array.

In itself I agree that it is a weird edge case, but it seems to be an example of a pretty basic assumption being broken by the language.

Can anyone explain this behaviour?

P.S. Sorry that the above code has to repeat the for loop verbatim three times, but turning it into a function hides the actual test result.

mklement0
  • 382,024
  • 64
  • 607
  • 775
John Rees
  • 1,553
  • 17
  • 24
  • 1
    Excellent find; as an aside: the simplest way to create a "null collection" (`[System.Management.Automation.Internal.AutomationNull]::Value]`) is to use `$nullc = & {}`. – mklement0 Jan 22 '18 at 22:18
  • Since this repros in PS Core v6.0.0, I encourage you to report the issue at https://github.com/PowerShell/PowerShell/issues – mklement0 Jan 22 '18 at 22:20
  • Everything in your question makes sense, except the... question part. Given the conditions you described and the code you posted, we see the expected behavior. Can you clarify the "broken assumption" part a bit? – Mathias R. Jessen Jan 22 '18 at 22:34
  • 2
    @MathiasR.Jessen: The unexpected behavior is that mere act of passing the array as an argument to a function quietly changes an element of that array from `[System.Management.Automation.Internal.AutomationNull]::Val‌​ue` to `$null` – mklement0 Jan 22 '18 at 22:50
  • 1
    @MathiasR.Jessen Just to clarify, I know that my intro says that an AutomationNull passed to a function is automatically converted to a simple $null. That is understood and expected. What is unexpected is that the content of an array is permanently changed (in the caller's scope) simply by passing the array to a function. – John Rees Jan 22 '18 at 22:54
  • 1
    @mklement0 Thanks. I raised the issue at https://github.com/PowerShell/PowerShell/issues/5987 – John Rees Jan 22 '18 at 22:57
  • 1
    Thanks for the clarification. Pretty sure this also happens to the array element during parameter binding, will need to dig through the source to verify though – Mathias R. Jessen Jan 22 '18 at 23:07

0 Answers0