none
Empty object evaluates as $True??

    Question

  • I can't figure out what is going on here.  I have what appears (to me) to be an empty object: 

    $adsiObj = New-Object System.DirectoryServices.DirectorySearcher([adsi]""

    )  
     

    $adsiObj.filter = "(&(objectCategory=person)(sAMAccountName=fakename))"

    $adsiObj.findall()

    $adsiObj.findall().count

    0

    $adsiObj.findall() | gm

    Get-Member : No object has been specified to the get-member cmdlet.
    At line:1 char:23
    + $adsiObj.findall() |gm <<<<
        + CategoryInfo          : CloseError: (:) [Get-Member], InvalidOperationException
        + FullyQualifiedErrorId : NoObjectInGetMember,Microsoft.PowerShell.Commands.GetMemberCommand

     

    but I it evaluates to $true:  if ($adsiObj.findall()) {$true}

    True

     

    Can someone enlighten me?

     


     

     

     

    • Edited by cascomp Friday, January 27, 2012 2:50 PM
    Friday, January 27, 2012 2:47 PM

Answers

  • $adsiobj.findall() is returning a collection.  It's an empty collection, but it's still a collection.

    ($adsiobj.findall()).gettype()

    IsPublic IsSerial Name                                     BaseType                  
    -------- -------- ----                                     --------                  
    True     False    SearchResultCollection                   System.MarshalByRefObject 

    When you send an arrray or collection  through the pipeline, it "unrolls" that collection into a stream of the individual objects.  Since the collection was emtpy, nothing got sent to get-member, hence the error message.

    To see the properties of the collection, you have to do this:

    get-member -inputobject $adsiobj

    The collection itself evaluates as $true, because it exists.


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    • Edited by mjolinor Friday, January 27, 2012 2:59 PM
    • Proposed as answer by Bigteddy Friday, January 27, 2012 3:01 PM
    • Marked as answer by cascomp Friday, January 27, 2012 5:01 PM
    Friday, January 27, 2012 2:58 PM

All replies

  • $adsiobj.findall() is returning a collection.  It's an empty collection, but it's still a collection.

    ($adsiobj.findall()).gettype()

    IsPublic IsSerial Name                                     BaseType                  
    -------- -------- ----                                     --------                  
    True     False    SearchResultCollection                   System.MarshalByRefObject 

    When you send an arrray or collection  through the pipeline, it "unrolls" that collection into a stream of the individual objects.  Since the collection was emtpy, nothing got sent to get-member, hence the error message.

    To see the properties of the collection, you have to do this:

    get-member -inputobject $adsiobj

    The collection itself evaluates as $true, because it exists.


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    • Edited by mjolinor Friday, January 27, 2012 2:59 PM
    • Proposed as answer by Bigteddy Friday, January 27, 2012 3:01 PM
    • Marked as answer by cascomp Friday, January 27, 2012 5:01 PM
    Friday, January 27, 2012 2:58 PM
  • Why then don't these examples evaluate to True when empty in the If statements?
     
    # array
    $a = @()
    ($a).gettype()
    $a | get-member
    get-member -inputobject $a
    $a.count
    if ($a) {'true'} else {'false'}
     
    # arraylist collection
    $al = New-Object system.collections.ArrayList
    ($al).gettype()
    $al | get-member
    get-member -inputobject $al
    $al.count
    if ($al) {'true'} else {'false'}
     
    Friday, January 27, 2012 3:16 PM
  • Those are testing properties.  In the example given ($adsiobj.findall()) .findall is a method. 

    Try this:

    if ($al.getenumerator()) {'true'} else {'false'}

    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Friday, January 27, 2012 3:34 PM
  • im not sure that’s the case, you are testing the object returned from the
    method,
     
    $r = $searcher.FindAll()
    $r.GetType()
     
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    SearchResultCollection
    System.MarshalByRefObject
     
    I guess the question is, what is this shorthand?
     
    if($r)
     
    is that the same as
    if($r -eq $true)
     
    if that’s the case it running $r.equals and is passed $true and its up to
    the object as to how it returns.
    PS C:\ps> $r.Equals($true)
    False
     
    PS C:\ps> $al.Equals($true)
    False
     
    so its obviously not Equal its testing with...
     
    PS C:\ps> if($r -eq $true){'t'}else{'f'}
    f
     
    in this case, you'll notice that the base type is system.marshalbyrefobject,
    which I think it returns true because its testing that a reference exists.
    where as with $al the base type is just a system.object
     
    I really have no idea what im talking about, but I think im on to something
    :)
     
     

    Justin Rich
    http://jrich523.wordpress.com
    PowerShell V3 Guide (Technet)
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Friday, January 27, 2012 3:44 PM
  • Using
     
    # arraylist collection enumerator collection
    $al = New-Object system.collections.ArrayList
    $aln = $al.getenumerator()
    ($aln).gettype()
    $aln | get-member
    get-member -inputobject $aln
    $aln.count
    if ($aln) {'true'} else {'false'}
     
    I get the evaluation of $true
     
    I guess there is no count property in that type of collection object, an
    ArrayList+ArrayListEnumeratorSimple collection
     
    But there was a count property in the OP's collection object, a SearchResultCollection
     
    I'm having trouble find a webpage for ArrayList+ArrayListEnumeratorSimple that's in the style of
    the one for SearchResultCollection at
     
    What's the characteristic of SearchResultCollection that makes it different from
    ArrayList+ArrayListEnumeratorSimple ?
     
     
    Friday, January 27, 2012 4:05 PM
  • I resemble that remark! <grin>
     On 1/27/2012 9:44 AM, jrich wrote:
    > I really have no idea what im talking about, but I think im on to something
    > :)
     
    Friday, January 27, 2012 4:06 PM
  • I think it has to do with the way a boolean evaluation is done.  I believe it invokes the default method of the object (which is usually .tostring()) and evaluates that.

    Invoking the default method of a method is not the same as invoking that method on it's object.

    PS C:\> ($al.getenumerator()).tostring()
    System.Collections.ArrayList+ArrayListEnumeratorSimple

    ArrayListEnumeratorSimple


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Friday, January 27, 2012 4:07 PM
  • no clue what so ever.......
     

    Justin Rich
    http://jrich523.wordpress.com
    PowerShell V3 Guide (Technet)
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Friday, January 27, 2012 4:11 PM
  • It does evaluate to true because is returning 0 finding using the "Count".  This is a collection of objects and the returning value is not exactly a boolean ("true" or "false") nor a specific value.  Using the "Count" help in how many items have been found.

    So, in order the trap this correctly you need to add the condition to check for 0 (nothing found), then this will evaluate the results properly.

    Here's the MSDN document with some information about the .NET "findAll()" method:

    http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall.aspx

    The code will look like this:

    if($adsiObj.findall().count -eq 0) {"Search is empty"} else {"Items Found"}

    I hope this help you,

    Max


    Thanks, Max Trinidad Microsoft MVP - PowerShell Florida PowerShell User Group: http://www.flpsug.com/ Blog: http://www.maxtblog.com/ Old Blog: http://maxt2posh.wordpress.com/ Follow me on Twitter: @MaxTrinidad Member of: CITGA, UGSS, INETA and PASS.
    • Edited by MaxTrinidad Friday, January 27, 2012 4:21 PM
    Friday, January 27, 2012 4:16 PM
  • Im not really sure that answers our question. that’s a solution to avoid the
    problem, but we've dug deeper in to why that would return true.
     
    what test is the if($searcher.findall()) actually doing? if I were to write
    it out explicitly what would it be?
     
     

    Justin Rich
    http://jrich523.wordpress.com
    PowerShell V3 Guide (Technet)
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Friday, January 27, 2012 4:24 PM
  • It does evaluate to true because is returning 0 finding using the "Count". 

    I don't follow that.  In a normal boolean evaluation, anything that returns 0 evaluates as false.
    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Friday, January 27, 2012 5:06 PM
  • Now I'm having doubts about that.

    PS C:\> [bool]{}
    True

    PS C:\> {}.tostring()


    PS C:\>


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    • Edited by mjolinor Friday, January 27, 2012 5:38 PM
    Friday, January 27, 2012 5:38 PM
  • The best explanation I can provide is based on the MSDN Article about the "DirectorySearcher.FindAll()" method stating " Executes the search and returns a collection of the entries that are found. ":

    http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.findall.aspx

    Again, it returns a collection of objects, not a single value or boolean result not even a null.  So, if the find will result in nothing found. So, because this is a bucket to hold object found, in this case is 0 objects found.

    You can't evaluate nor expect to return a type Boolean when the actual object is a different type, in this case is a "SearchResultCollection".  This is why with when using the property "Count", we can properly check for items found in the collection.  This is why it always return "True".

    To properly fix this behaviour, we can use the value returning from ".Count" to identify "0" for Not-Found-Item(s) and =>1 for Found-Item(s).  Otherwise, using the " if($adsiObj.FindAll())" will always return "True" which is wrong logic.

    In my opinion, the correct logica statement should be:

    if($asdiObj.FindAll().Count -eq 0) {"Nothing in Search"} else {"Found Item(s)};

    Please, don't get me wrong, longtime ago I went in circles trying to figured this one out.  I took my awhile to understand.

    :)

    Max

     

     

     


    Thanks, Max Trinidad Microsoft MVP - PowerShell Florida PowerShell User Group: http://www.flpsug.com/ Blog: http://www.maxtblog.com/ Old Blog: http://maxt2posh.wordpress.com/ Follow me on Twitter: @MaxTrinidad Member of: CITGA, UGSS, INETA and PASS.
    Friday, January 27, 2012 6:48 PM
  • Thanks.

    The more I venture outside the box of native Powershell objects and basic generic types, the more I find there are some object types that display unexpected behaviour.

    Sometimes I can find or figure out that there's a good reason and a good explanation for it. 

    Other times I've come to the conclusion that the only explanation is that it seemed like a good idea at the time, but nobody remembers why any more.

     


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    • Edited by mjolinor Friday, January 27, 2012 7:43 PM
    Friday, January 27, 2012 7:02 PM
  • I think this statement
    'You can't evaluate nor expect to return a type Boolean when the actual
    object is a different type, in this case is a "SearchResultCollection". '
     
    is the heart of the matter, but as larry pointed out, if you do it with an
    array System.Object[] with no objects will return false, also if you use an
    ArrayList this will also return false if there are no objects in it.
     
    so why does an empty ArrayList return false but an empty
    SearchResultCollection return true?
     
    I tried to run this in C# to dig in to it and C# refuses to cast it as Bool,
    so, im not sure why powershell does it, or how powershell even does it,
    based on this I feel like powershell is just like "err, sure, true"
     
    Would like to try the Trace-Command, just in this situation it might be a
    tad over my head at the moment.
     
     

    Justin Rich
    http://jrich523.wordpress.com
    PowerShell V3 Guide (Technet)
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Friday, January 27, 2012 7:04 PM
  • lol I like that one "seemed like a good idea at the time"
     

    Justin Rich
    http://jrich523.wordpress.com
    PowerShell V3 Guide (Technet)
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Friday, January 27, 2012 7:16 PM
  • Maybe in the PowerShell implementation there is a situation where it is looking at a pointer and
    evaluating the pointer for zero/notzero and making the [Bool] conversion decision on that (almost
    always $True).
     
    I've always assumed that the code that implements PowerShell is not available to us, so it would
    take a Bruce Payette/Jeffrey Snover/??? person at MSFT to examine it for us.
     
    Friday, January 27, 2012 7:47 PM
  • Thanks.

    The more I venture outside the box of native Powershell objects and basic generic types, the more I find there are some object types that display unexpected behaviour.

    Sometimes I can find or figure out that there's a good reason and a good explanation for it. 

    Other times I've come to the conclusion that the only explanation is that it seemed like a good idea at the time, but nobody remembers why any more.

    The reason you are seeing this odd behavior in casting object to boolean is that Powershell implements "special" casting rules specifically to enable the behavior you are trying to take advantage of:

    $SomeObject = Some-Query

    if($SomeObject) {

    do something

    }

    "Normal" languages would not allow any arbitrary object to be cast to a boolean.

    The reason your System.DirectoryServices.SearchResultCollection is evaluating to true even when it is empty is because it does not implement the IList interface:

     

    PS C:\> [system.directoryservices.searchresultcollection].getinterfaces()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    ICollection
    True     False    IEnumerable
    True     False    IDisposable
    
    
    PS C:\> [system.array].getinterfaces()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    True     False    ICloneable
    True     False    IList
    True     False    ICollection
    True     False    IEnumerable
    
    
    PS C:\> [system.collections.arraylist].getinterfaces()
    
    IsPublic IsSerial Name                                     BaseType
    -------- -------- ----                                     --------
    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 what Powershell uses to determine if there is anything "in there" when it converts the object to a boolean.

    You can see 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 assume 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 (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 non-null object with evaluates to $true in Powershell.

    • Proposed as answer by jrich Saturday, January 28, 2012 9:15 PM
    Saturday, January 28, 2012 7:40 PM
  • wow, thats great. thanks a lot for walking us through that!
    Justin Rich
    http://jrich523.wordpress.com
    PowerShell V3 Guide (Technet)
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Saturday, January 28, 2012 9:15 PM
  • Thank you for the explanation.

    My next question would by whe that object type does not implement IList.  My background is in administration, and I never did 'grok' VB so my working knowlege of .net is admittedly shallow, but looking at some of the available online documentation, I'm not seeing anything that would preclude the return from that searcher being able to be representd as an enumerated list.


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Saturday, January 28, 2012 9:27 PM
  • I'm not where I can test, but given this information, combined with what I think I know about powershell script blocks, I suspect that if  $adsiObj.findall() had been put into a script block, either standalone or as the scriptblock of a function, and the return from invocation of the script block or fuction tested, it would have produced expected results.

     


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    • Proposed as answer by mjolinor Saturday, January 28, 2012 10:21 PM
    • Edited by mjolinor Saturday, January 28, 2012 10:23 PM
    • Unproposed as answer by mjolinor Saturday, January 28, 2012 10:31 PM
    Saturday, January 28, 2012 10:21 PM
  • Okay, FWIW,

    $adsiObj = New-Object System.DirectoryServices.DirectorySearcher([adsi]""

    )   
    $adsiObj.filter = "(&(objectCategory=person)(sAMAccountName=fakename))"

    [bool]($adsiObj.findall())  # RETURNS TRUE


    [bool](&{$adsiobj.findall()}) # RETURNS FALSE


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "
    Sunday, January 29, 2012 9:21 PM
  • There is a lot more to implement in IList than ICollection and ILists can be editable so you have to go through a lot of effort to mark them  as IsReadOnly, which basically turns them into just a collection.  Probably not much value there for a directory search result that everyone is just going to enumerate anyway.

    I think the better question is why doesn't the [bool] conversion logic use ICollection instead of IList?  The Count parameter they use for the test on the list is actually inherited from ICollection, the only thing IList is giving them extra is the ability to index into the first element in the "only 1 thing in there" case, which you could use an enumerator for instead.  Seems like this would give the "if ($ObjectBag)" syntax a lot more reach.

    Maybe some collections have one-shot enumerators and if powershell "peeked" at the first element it would ruin the user's ability to enumerate the collection?  IEnumerator does say it is valid to throw "NotImplemented" for the Reset() method...

    Monday, January 30, 2012 6:21 AM