Let me add some background information to Santiago Squarzon's helpful answer:
Indeed, PowerShell has two types of null values:
The scalar null, so to speak, analogous to null
in C# and other languages, for instance, which is the value of the automatic $null
variable.
- You can think of it as placeholder for a missing object.
$null
is sent through the pipeline as-is.
- Curiously, the same is not true for using
$null
as the input to a foreach
statement: foreach ($val in $null) { 'here!' }
produces no output, implying that the loop is never entered.
$null
is what trying to access nonexistent variables evaluates to, unfortunately,[1] but its explicit use - other than in tests such as $null -eq $value
- is rare in PowerShell.
The enumerable null, so to speak, which is a PowerShell-specific concept:
You can think of it as an enumerable that enumerates nothing.
Sadly, this special value has no official name as of this writing, thought it is often called "Automation null" and sometimes "empty null".
Also, there is no automatic variable for it, i.e. there is no analog to the automatic $null
variable for the scalar null.
Given that the enumerable null is technically the "return value" of commands that produce no output, the simplest way to obtain it is to execute $nullEnum = & {}
, i.e. to execute an empty script block. Specifically, the enumerable null is the [System.Management.Automation.Internal.AutomationNull]::Value
singleton (the documentation link provides no meaningful information).
In a pipeline (e.g., $value | Write-Output
)...
... the enumerable null behaves like a collection with no elements, which means that, given that collections are enumerated in the pipeline (have their elements sent one by one), no data is sent through the pipeline, which is (typically) a no-op: the commands in subsequent pipeline segments receive no input to operate on.
Note that the automatic enumeration logic also applies to the switch
statement and to the LHS of comparison operators (which act as filters with enumerable LHS values); providing the enumerable null as input to switch
effectively skips the statement (e.g. switch (& {}) { default { 'never get here' } }
); whether the enumerable null is treated as an enumerable as the LHS of a comparison operation depends on the specific operator; -match
treats it as an enumerable ((& {}) -match ''
-> empty array), -eq
does not ((& {}) -eq ''
-> $false
)
In an expression (e.g, $null -eq $value
)...
- ... the enumerable null behaves like
$null
- Additionally, when passing the enumerable null as an argument (parameter value) to a command, conversion to
$null
invariably happens - see GitHub issue #9150
The above explains the difference between $null | Set-Variable test1
(variable test
is set to the $null
value received via the pipeline) and & {} | Set-Variable test2
(variable test2
is never created or updated, because Set-Variable
receives no input; your Select-Object -SkipLast 1
call on the 1-element input collection produced no output, and therefore emitted the enumerable null).
See also:
This answer also touches on historic aspects of $null
vs. [System.Management.Automation.Null]
handling, covering the behavioral changes that happened in the transition from v2 to v3+.
This comment on GitHub issue #9150 summarizes how the null dichotomy could be handled in a consistent manner, if backward compatibility weren't a concern.
Given the fundamental behavioral differences, it is important:
to properly document these two null types, as well as give the enumerable null an official name.
to make it easy to programmatically distinguish the two types.
As of this writing (PowerShell 7.3.6), neither requirement is met.
Detecting the enumerable null is currently cumbersome and obscure:
$value = & {} # Obtain the enumerable null.
# Without the `-and $value.psobject` part, you couldn't distinguish
# $null from the enumerable null.
$isNullEnumerable =
$null -eq $value -and $value.psobject
While $null -eq $value
returns $true
for both a true $null
and the enumerable null, $value.psobject
only returns a value for the enumerable null (which, when coerced to a Boolean, evaluates to $true
). The reason is that, unlike $null
, the enumerable null is technically an object and therefore returns a value for the intrinsic psobject
property.
As a result of the discussion in GitHub issue #13465, the following improvement has been green-lit, but is yet to be implemented:
# NOT YET IMPLEMENTED as of PowerShell 7.3.6
$isNullEnumerable =
$value -is [System.Management.Automation.Null]
That is, you'll be able to use -is
, the type(-inheritance) / interface test operator with the yet-to-be-introduced [System.Management.Automation.Null]
type, which will supersede the "pubternal" [System.Management.Automation.Internal.AutomationNull]
type.[2]
Unfortunately, also introducing a type accelerator - which would simplify the test to $value -is [AutomationNull]
- was decided against.
[1] This default value is unfortunate, because it means that $noSuchVariable | ...
sends $null
through the pipeline. If the default were the "enumerable null" ("Automation null", [System.Management.Automation.Null]::Value
) instead, no data would be sent, the way the foreach
statement already - but surprisingly - handles $null
(too). With the enumerable null as the default, there would be no need for the asymmetry between pipeline and foreach
behavior, and $null
could consistently be preserved as such.
[2] This change involves more than just a new type name: the new type's singleton ([System.Management.Automation.Null]::Value
), i.e. the actual enumerable null, will then be of that same type, whereas the current singleton ([System.Management.Automation.Internal.AutomationNull]::Value
) is of a different type, namely just [psobject]
- see this GitHub comment for details.