Why is an empty PowerShell pipeline not the same as null?
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:
$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:
$null | % { 'saw $null' }[System.Management.Automation.Internal.AutomationNull]::Value | % { 'saw AutomationNull.Value' }
This will only print:
saw $null
Note that expressions are themselves pipelines even if you don't have a pipeline character, so the following are roughly equivalent:
@($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:
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
The reason you're experiencing this behaviour is becuase $null
is a value. It's a "nothing value", but it's still a value.
PS P:\> $y = 1,2,3,4 | ? { $_ -ge 5 }PS P:\> Get-Variable y | fl *#No value survived the where-test, so y was never saved as a variable, just as a "reference"Name : yDescription : Value : Visibility : PublicModule : ModuleName : Options : NoneAttributes : {}PS P:\> $z = $nullPS P:\> Get-Variable z | fl *#Our $null variable is saved as a variable, with a $null value.PSPath : Microsoft.PowerShell.Core\Variable::zPSDrive : VariablePSProvider : Microsoft.PowerShell.Core\VariablePSIsContainer : FalseName : zDescription : Value : Visibility : PublicModule : ModuleName : Options : NoneAttributes : {}
The way @()
works, is that it guarantees that the result is delievered inside a wrapper(an array). This means that as long as you have one or more objects, it will wrap it inside an array(if it's not already in an array like multiple objects would be).
$y
is nothing, it's a reference, but no variable data was stored. So there is nothing to create an array with. $z
however, IS a stored variable, with nothing(null-object) as the value. Since this object exists, the array constructor can create an array with that one item.