Question:
I am trying to understand the behavior of the @() array constructor, and I came across this very strange test.
It seems that the value of an empty pipeline is “not quite” the same as $null, even though it is -eq $null
The output of each statement is shown after the ###
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$y = 1,2,3,4 | ? { $_ -ge 5 } $z = $null if ($y -eq $null) {'y is null'} else {'y NOT null'} ### y is null if ($z -eq $null) {'z is null'} else {'z NOT null'} ### z is null $ay = @($y) $az = @($z) "ay.length = " + $ay.length ### ay.length = 0 "az.length = " + $az.length ### az.length = 1 $az[0].GetType() ### throws exception because $az[0] is null |
So the $az array has length one, and $az[0] is $null.
But the real question is: how is it possible that both $y and $z are both -eq $null, and yet when I construct arrays with @(…) then one array is empty, and the other contains a single $null element?
Answer:
Expanding on Frode F.’s answer, “nothing” is a mostly magical value in PowerShell – it’s called [System.Management.Automation.Internal.AutomationNull]::Value. The following will work similarly:
1 2 3 |
$y = 1,2,3,4 | ? { $_ -ge 5 } $y = [System.Management.Automation.Internal.AutomationNull]::Value |
PowerShell treats the value AutomationNull.Value like $null in most places, but not everywhere. One notable example is in a pipeline:
1 2 3 |
$null | % { 'saw $null' } [System.Management.Automation.Internal.AutomationNull]::Value | % { 'saw AutomationNull.Value' } |
This will only print:
1 2 |
saw $null |
Note that expressions are themselves pipelines even if you don’t have a pipeline character, so the following are roughly equivalent:
1 2 3 |
@($y) @($y | Write-Output) |
Understanding this, it should be clear that if $y holds the value AutomationNull.Value, nothing is written to the pipeline, and hence the array is empty.
One might ask why $null is written to the pipeline. It’s a reasonable question. There are some situations where scripts/cmdlets need to indicate “failed” without using exceptions – so “no result” must be different, $null is the obvious value to use for such situations.
I’ve never run across a scenario where one needs to know if you have “no value” or $null, but if you did, you could use something like this:
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 |
function Test-IsAutomationNull { param( [Parameter(ValueFromPipeline)] $InputObject) begin { if ($PSBoundParameters.ContainsKey('InputObject')) { throw "Test-IsAutomationNull only works with piped input" } $isAutomationNull = $true } process { $isAutomationNull = $false } end { return $isAutomationNull } } dir nosuchfile* | Test-IsAutomationNull $null | Test-IsAutomationNull |