71

I'm using an array variable in PowerShell 2.0. If it does not have a value, it will be $null, which I can test for successfully:

PS C:\> [array]$foo = $null
PS C:\> $foo -eq $null
True

But when I give it a value, the test for $null does not return anything:

PS C:\> [array]$foo = @("bar")
PS C:\> $foo -eq $null
PS C:\>

How can "-eq $null" give no results? It's either $null or it's not.

What is the correct way to determine if an array is populated vs. $null?

Mark Berry
  • 17,843
  • 4
  • 58
  • 88

8 Answers8

110

It's an array, so you're looking for Count to test for contents.

I'd recommend

$foo.count -gt 0

The "why" of this is related to how PSH handles comparison of collection objects

Nae
  • 14,209
  • 7
  • 52
  • 79
Taylor Bird
  • 7,767
  • 1
  • 25
  • 31
  • 2
    Unfortunately that doesn't work when `[array]$foo = $null`. Instead of 0, `$foo.count` returns nothing. – Mark Berry Feb 24 '11 at 23:25
  • 6
    but (nothing -gt 0 == FALSE), which is why I suggested using that operator. If you check that you'll have a true/false, regardless of nullness – Taylor Bird Feb 24 '11 at 23:47
  • Ah, I see. Yes that is shorter and somewhat more intuitive than having to use the `else` block of an `if ($foo -eq $null)` statement. Interesting that when `[array]$foo = $null`, `$foo.count -eq 0` is False, but `$foo.count -lt 0` is True. I guess $null is less than 0. Wonder if that is documented somewhere? – Mark Berry Feb 25 '11 at 00:32
  • Well an array is a construct, even when empty .. and the count() is on the construct object, not its contents, so the minute you create the array it has a count property – Taylor Bird Feb 25 '11 at 00:39
  • Just noting that `$null.count -lt 0` is True, while `$null.count -gt 0` is False. If that's the guaranteed result, then `$foo.count -lt 0` could be used to check for $null, e.g when I want to do something if the array hasn't been created. Although in that case, the `if ($foo -eq $null`) might be more readable. – Mark Berry Feb 25 '11 at 00:53
  • 4
    If you really wanna hurt your head, powershell will tell you that $null -lt 0 == TRUE, but $null -lt -1 == FALSE. so -1 < null < 0 – Taylor Bird Feb 25 '11 at 02:16
  • @Taylor $foo.count returns empty in case the array has only one value – Ram Jan 31 '13 at 02:54
  • 4
    This approach fails if you have Set-StrictMode -Version Latest – mnaoumov Apr 16 '13 at 06:35
49

You can reorder the operands:

$null -eq $foo

Note that -eq in PowerShell is not an equivalence relation.

Joey
  • 344,408
  • 85
  • 689
  • 683
22
if($foo -eq $null) { "yes" } else { "no" }

help about_comparison_operators 

displays help and includes this text:

All comparison operators except the containment operators (-contains, -notcontains) and type operators (-is, -isnot) return a Boolean value when the input to the operator (the value on the left side of the operator) is a single value (a scalar). When the input is a collection of values, the containment operators and the type operators return any matching values. If there are no matches in a collection, these operators do not return anything. The containment operators and type operators always return a Boolean value.

John Weldon
  • 39,849
  • 11
  • 94
  • 127
  • 1
    Yeah I was missing the idea that -eq scans a non-empty collection for matching values and returns the VALUE, not True or False. Very confusing especially if the collection contains empty strings. If `[array]$foo=@("")`, your if statement works, but its inverse does not: `if($foo -ne $null) { "no" } else { "yes" }` returns "yes". Since the main thing I want to do is test for NOT null (i.e. there are elements to work on), that means I have to use `if($foo -eq $null) { # Do nothing } else { #Do something }`. – Mark Berry Feb 25 '11 at 00:06
  • Thanks. I was trying to figure out why this seemed to return no answer: `$a = @(); $a -eq $null` – js2010 Mar 22 '18 at 14:36
  • 1
    In PowerShell, $null should be at the left side of the comparison. – Mohammad Rayan Mar 25 '19 at 13:28
9

If your solution requires returning 0 instead of true/false, I've found this to be useful:

PS C:\> [array]$foo = $null
PS C:\> ($foo | Measure-Object).Count
0

This operation is different from the count property of the array, because Measure-Object is counting objects. Since there are none, it will return 0.

Anthony Neace
  • 25,013
  • 7
  • 114
  • 129
6

The other answers address the main thrust of the question, but just to comment on this part...

PS C:\> [array]$foo = @("bar")
PS C:\> $foo -eq $null
PS C:\>

How can "-eq $null" give no results? It's either $null or it's not.

It's confusing at first, but that is giving you the result of $foo -eq $null, it's just that the result has no displayable representation.

Since $foo holds an array, $foo -eq $null means "return an array containing the elements of $foo that are equal to $null". Are there any elements of $foo that are equal to $null? No, so $foo -eq $null should return an empty array. That's exactly what it does, the problem is that when an empty array is displayed at the console you see...nothing...

PS> @()
PS> 

The array is still there, even if you can't see its elements...

PS> @().GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS> @().Length
0

We can use similar commands to confirm that $foo -eq $null is returning an array that we're not able to "see"...

PS> $foo -eq $null
PS> ($foo -eq $null).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS> ($foo -eq $null).Length
0
PS> ($foo -eq $null).GetValue(0)
Exception calling "GetValue" with "1" argument(s): "Index was outside the bounds of the array."
At line:1 char:1
+ ($foo -eq $null).GetValue(0)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : IndexOutOfRangeException

Note that I am calling the Array.GetValue method instead of using the indexer (i.e. ($foo -eq $null)[0]) because the latter returns $null for invalid indices and there's no way to distinguish them from a valid index that happens to contain $null.

We see similar behavior if we test for $null in/against an array that contains $null elements...

PS> $bar = @($null)
PS> $bar -eq $null
PS> ($bar -eq $null).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS> ($bar -eq $null).Length
1
PS> ($bar -eq $null).GetValue(0)
PS> $null -eq ($bar -eq $null).GetValue(0)
True
PS> ($bar -eq $null).GetValue(0) -eq $null
True
PS> ($bar -eq $null).GetValue(1)
Exception calling "GetValue" with "1" argument(s): "Index was outside the bounds of the array."
At line:1 char:1
+ ($bar -eq $null).GetValue(1)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : IndexOutOfRangeException

In this case, $bar -eq $null returns an array containing one element, $null, which has no visual representation at the console...

PS> @($null)
PS> @($null).GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array


PS> @($null).Length
1
Lance U. Matthews
  • 15,725
  • 6
  • 48
  • 68
  • Wow. Thanks for the explanation! – Mark Berry Dec 23 '18 at 04:46
  • I used this to do a similar thing with Pester: # If the variable is an empty array. $value = @() $value.GetType().BaseType.ToString() | Should -Be 'System.Array' # If the variable is $null. $value = $null { $value.GetType().BaseType.ToString() } | Should -Throw – Matt Bussing Nov 09 '20 at 21:35
3

How do you want things to behave?

If you want arrays with no elements to be treated the same as unassigned arrays, use:

[array]$foo = @() #example where we'd want TRUE to be returned
@($foo).Count -eq 0

If you want a blank array to be seen as having a value (albeit an empty one), use:

[array]$foo = @() #example where we'd want FALSE to be returned
$foo.PSObject -eq $null

If you want an array which is populated with only null values to be treated as null:

[array]$foo = $null,$null
@($foo | ?{$_.PSObject}).Count -eq 0 

NB: In the above I use $_.PSObject over $_ to avoid [bool]$false, [int]0, [string]'', etc from being filtered out; since here we're focussed solely on nulls.

JohnLBevan
  • 22,735
  • 13
  • 96
  • 178
  • Those are helpful thoughts about how to initialize arrays for various behaviors, but this doesn't answer the original question, "What is the correct way to determine if an array is populated vs. $null?" Perhaps that should be amended to add, "even if I don't know how the array was initialized?" – Mark Berry Mar 18 '17 at 18:19
0

Watch out for switch. It will never run with a null array, for example as the output of an empty directory.

switch ( $null ) { default { 'yes' } }
yes

switch ( @() ) { default { 'yes' } }  # no output

mkdir foo
switch ( dir foo ) { default { 'yes' } }  # no output
js2010
  • 23,033
  • 6
  • 64
  • 66
0

Not all of these answers work for me. I ended up doing this, treating it like a boolean.

$foo = @()
if (! $foo) { 'empty array' }

empty array

Actually, I came across an arraylist inside an object.

[pscustomobject]@{config = [Collections.ArrayList]@()} | ? { ! $_.config }

config
------
{}
js2010
  • 23,033
  • 6
  • 64
  • 66