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 : y
Description :
Value :
Visibility : Public
Module :
ModuleName :
Options : None
Attributes : {}
PS P:\> $z = $null
PS P:\> Get-Variable z | fl *
#Our $null variable is saved as a variable, with a $null value.
PSPath : Microsoft.PowerShell.Core\Variable::z
PSDrive : Variable
PSProvider : Microsoft.PowerShell.Core\Variable
PSIsContainer : False
Name : z
Description :
Value :
Visibility : Public
Module :
ModuleName :
Options : None
Attributes : {}
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.