I just came across an unexpected behaviour of Where-Object which I couldn't find any explanation for:
$foo = $null | Where-Object {$false}
$foo -eq $null
> True
($null, 1 | Measure-Object).Count
> 1
($foo, 1 | Measure-Object).Count
> 1
($null, $null, 1 | Measure-Object).Count
> 1
($foo, $foo, 1 | Measure-Object).Count
> 0
If the condition of Where-Object is false, $foo should be $null (which appears to be correct).
However, piping $foo at least twice before any value into the pipeline seems to break it.
What is causing this?
Other inconsistencies:
($foo, $null, 1 | Measure-Object).Count
> 1
($foo, $null, $foo, 1 | Measure-Object).Count
> 0
($null, $foo, $null, 1 | Measure-Object).Count
> 1
($foo, 1, $foo, $foo | Measure-Object).Count
> 1
($null, $foo, $null, $foo, 1 | Measure-Object).Count
> 0
tl;dr:
Not all apparent $null values are the same, as Jeroen Mostert's comments indicate: PowerShell has two types of null that situationally behave differently - see the next section.
Additionally, you're seeing perhaps surprising Measure-Object behavior and a pipeline bug - see the bottom section.
It's best to eliminate Measure-Object from your test commands and simply invoke .Count directly on your arrays; e.g. (the simplest way to create the type of null as in your question is: $foo = & {}):
($foo, $null, 1).Count yields 3($null, $foo, $null, $foo, 1).Count yields 5As you can see, both types of null (discussed below) properly become elements of an array.
There are two distinct kinds of null values in PowerShell:
There's bona fide scalar null (corresponding to null in C#, for instance).
$null variable.There's also the enumerable "collection null" (also called "AutomationNull", based on its class name), which is technically the System.Management.Automation.Internal.AutomationNull.Value singleton, which is itself a [psobject] instance.
& {} , i.e. by executing an empty script block; of course, you can also use [System.Management.Automation.Internal.AutomationNull]::Value explicitly).Unfortunately, the collection null value is nontrivial to distinguish from the scalar null, as of PowerShell 7.2:
GitHub issue #13465 proposes allowing detection of collection null via $var -is [AutomationNull] in a future PowerShell version.
For now, there are several workarounds for testing whether a given value $var contains collection null; perhaps the simplest (but non-obvious) is:
$null -eq $var -and $var -is [psobject] is $true only if $var contains the collection null value, because only collection null is technically an object.Behavioral differences:
In expression contexts and in parameter binding, there is no difference in that collection null is implicitly converted to $null.
Note that this means that you cannot pass collection null as an argument - see the discussion in GitHub issue #9150.
The exception in the context of expressions is the LHS of operators that support collections as their LHS: they treat collection null as an empty collection and therefore evaluate to an empty array (@()) rather than $null:
$var -replace 'foo' | ForEach-Object { 'hi' } prints 'hi' only if $var is scalar $null, not with with collection null, because the -replace operation then outputs an empty array, which sends nothing through the pipeline.In the pipeline:
Scalar $null is sent through the pipeline - it behaves like a single object: $null | ForEach-Object { '$_ is $null? ' + ($null -eq $_) } prints '$_ is $null? True';
Collection null is not sent through the pipeline - it behaves like a collection without elements; that is, just like @() | ForEach-Object { 'hi' } (sending an empty array), & {} | ForEach-Object { 'hi' } sends nothing through the pipeline, because there is nothing to enumerate, and therefore never outputs 'hi'.
Curiously, by contrast, in a foreach loop statement (as opposed to the ForEach-Object cmdlet) scalar $null too is not enumerated and the loop body is never entered in the following (ditto for collection null):
foreach ($i in $null) { 'hi' }
Measure-Object and pipeline problems:
Measure-Object generally ignores $null values, presumably by design.
-IncludeNull switch to support considering $null values on an opt-in basis. (The default behavior will not change so as not to break backward compatibility.)However, you've discovered an outright bug in PowerShell's pipeline with respect to multi-object input involving collection nulls (as of PowerShell 7.1.2) , which Measure-Object only surfaces, as you've noted yourself:
On encountering a second collection null in multi-object input, sending objects through the pipeline unexpectedly stops:
(1, (& {}), 2, (& {}), 3, 4, 5 | Measure-Object).Count yields just 2: only 1 and 2 are counted (the collection nulls themselves are not sent through the pipeline), because the second collection null unexpectedly stops enumeration, so that the remaining objects - 3, 4, and 5 - aren't even sent to Measure-Object.See GitHub issue #14920.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With