Casting Objects to Boolean in Powershell

A question came up on the Powershell technet forum asking why an empty System.DirectoryServices.SearchResultCollection was evaluating to $true. The original post is HERE, but the gist of the question is this:

Why does an empty System.DirectoryServices.SearchResultCollection object evaluate to $true when used in an if statement (ie if($SearchResult)), but an empty System.Array and System.Collections.ArrayList both evaulate to $false?

I was aware that Powershell has some special rules for casting objects, but a search for exactly what those rule are only returned very generic terms for how collections were handled (ie this blog post and this book). Nothing specified exactly which types/interfaces were expanded as collections and which were not.

So I did a little digging and came up with the following…

The reason a SearchResultCollection evaluates to true even when it is empty is because it does not implement the IList interface:

PS> [System.DirectoryServices.SearchResultCollection].GetInterfaces()

IsPublic IsSerial Name
-------- -------- ----
True False ICollection
True False IEnumerable
True False IDisposable

PS> [System.Array].GetInterfaces()

IsPublic IsSerial Name
-------- -------- ----
True False ICloneable
True False IList
True False ICollection
True False IEnumerable

PS> [System.Collections.ArrayList].GetInterfaces()

IsPublic IsSerial Name
-------- -------- ----
True False IList
True False ICollection
True False IEnumerable
True False ICloneable

As you can see, both System.Array and System.Collections.ArrayList implement IList. This is apparently what Powershell uses to determine if it should "look inside" when it converts the object to a boolean.

You can see (what I think is) the proof of this if you load up Reflector (dotPeek, ILSpy, and JustDecompile are free alternatives):

  1. Open System.Management.Automation from the GAC
  2. Expand the System.Management.Automation namespace
  3. Browse down to LanguagePrimitives
  4. Browse down to the IsTrue(object obj) method and decompile it

In there you can see the algorithm (which I am assuming is being used in this case) that converts objects to boolean. The basic logic is:

  1. If it is null, return $false.
  2. If it is a boolean, return the boolean.
  3. If it is a string, return $false if it is empty, else return $true.
  4. If it is a number, return $false if it is 0, else return $true.
  5. If it is a SwitchParameter, call its own ToBool() method.
  6. Convert it to an IList:
    1. If this conversion fails, return $true (meaning it was an object that was not null, not any of the "special" things above, and not a list for PS to count).
    2. If it is a list and has 0 elements, return $false.
    3. If it is a list and has 1 element, return the IsTrue(list[0]) value (ie recurse on the one element and return its value.
    4. If it is a list with more than 1 thing in it, return $true.

As you can see, the Array and ArrayList fall into rules 6.2-6.4 because they implement IList, whereas the SearchResultCollection falls into rule 6.1 because it does not implement IList so the conversion to a list fails, which means it was a plain old non-null object which evaluates to $true in Powershell.


comments powered by Disqus