2

When using notcontains to check for an object in array it is saying True when it should be false. This is the code that I have.

$SPSecUsers = Get-ADGroupMember Test
$AllLondon = Get-ADGroupMember Test_AllLondon
$SPSecUKUsers = @()
$SPSecUSUsers = @()

foreach ($SPSecUser in $SPSecUsers) {
    if ($SPSecUser.distinguishedName -match "DC=uk,DC=company,DC=com") {
        $SPSecUKUsers += $SPSecUser
    } else {
        $SPSecUSUsers += $SPSecUser
    }
}

foreach ($UKUser in $AllLondon) {
    if ($SPSecUKUsers -notcontains $UKUser) {
        Write-Host $UKUser.name -ForegroundColor Green
    } else {
        Write-Host $UKUser.name -ForegroundColor Red
    }
}

Every time I run this, $SPSecUKUsers -notcontains $UKUser brings up True for all users, even when object is in the array.

In debug mode I manually do a check and it still brings up True even though the User object is in the Array.

I have even set both groups to have the exact same users, and it still does not work.

Jason Pope
  • 125
  • 2
  • 7
  • the `-contains` and `-in` operators [and their `not` versions] require an EXACT MATCH. it's _extremely_ unlikely that you are seeing the same objects, much less the EXACT same objects. try using `$Collection.name` -notcontains $Item.Name` or some variant of that. – Lee_Dailey Feb 01 '19 at 16:57
  • I know it should be an EXACT match, which is confusing because the User accounts are the same account. They are the exact same AD Object. I looked in the debugger at the two objects and there is no difference between the two of them. They have all the same values in the object. – Jason Pope Feb 01 '19 at 17:07
  • 1
    I have used the .name on the collection and object and that works. Just really odd that comparing the two objects would not work. Thanks Lee_Dailey. – Jason Pope Feb 01 '19 at 17:10
  • 1
    That's the point. You're comparing two different objects. Even if they represent the same user - they are different. – Olaf Feb 01 '19 at 17:15
  • @JasonPope - as `olaf` pointed out, the two objects look the same but the array operator in question sees _something_ different. i'm glad to know you got it working! [*grin*] – Lee_Dailey Feb 01 '19 at 17:30

1 Answers1

0

tl;dr

Don't store the AD user objects themselves in your arrays, use their .SamAccountName property value instead (generally, pick a property that uniquely identifies the objects):

# ...
$SPSecUKUsers += $SPSecUser.SamAccountName
# ...
if ($SPSecUKUsers -notcontains $UKUser.SamAccountName) { # ...

See the next section, if you want to know why storing the objects themselves doesn't work.

Alternatively - for faster lookups - use a hashtable:

$SPSecUKUsers = @{} # initialize hashtabe
# ...
# Create an entry for the object at hand, using its .SamAccountName
# as the entry *key*; you can store the object itself as the entry *value*.
# If all you need are lookups by SAM account name, however, you can just
# use a fixed value such as $true.
$SPSecUKUsers[$SPSecUser.SamAccountName] = $SPSecUser
# ...
if ($SPSecUKUsers.ContainsKey($UKUser.SamAccountName)) { # ...

About PowerShell's containment (collection-membership) operators:

As Olaf and Lee_Dailey imply in the comments, PowerShell's containment operators (-contains / notcontains and -in / -notin) check the comparison operand for reference equality (identity) with the elements of the input array, if those elements are instance of .NET reference types, with the exception of [string] instances. [string] instances and instance of value types are tested with
value equality (equivalence) - see Equality Comparisons.

You can think of these operators as an implicit loop over the input array's elements, testing each against the comparison operand with the -eq operator (or, if you use the case-sensitive variant such as -ccontains, with -ceq), using reference equality or value equality, depending on the element type.

Important: Due to PowerShell's flexible automatic type-conversion rules, which operand is the LHS in an -eq operation matters. Using -in or -contains means that the LHS of the implied -eq operation is the array element being tested against, as the following examples show:

 # `, 10` creates a single-element array
 '0xa' -in , 10       # equivalent of: 10 -eq '0xa' => $true
 , 10 -contains '0xa' # ditto

 # 
 10 -in , '0xa'        # equivalent of: '0xa' -eq 10 => $false
 , '0xa' -contains 10  # ditto

In the first 2 operations, the LHS being a number ([int]) forces the string RHS ([string]) to a number ([int]) too, and the hex "number string" '0xa' converts to an [int] with decimal value 10 too.

In the latter 2, the LHS being a string ([string]) forces the number 10 to become a string too, and '10' obviously doesn't match '0xa'.

Value equality (equivalence) means that two objects have the same content, even though, with distinct value-type objects, that content is by definition stored in different memory locations.

Numeric types such as [int] and [double] are value types, for instance. As a rough rule of thumb, objects that have properties are often reference types. You can check a given type's .IsValueType property; e.g., [int].IsValueType returns $true.

Reference equality (identity) means that two values are only considered equal if they point to the very same object in memory, i.e., the same instance of a reference type.

Otherwise, they're considered not equal, even if they represent what is conceptually the same entity, which is what happened in your case: two separate calls to Get-ADUser return distinct objects, even if you (in part) ask for the same users in both cases (Get-ADUser returns instance of type Microsoft.ActiveDirectory.Management.ADUser, which is a reference type).

Examples:

# Create a custom object...
$customObject = [pscustomobject] @{ one = 1; two = 2 }
# which is an instance of a reference type.
$customObject.GetType().IsValueType # -> $false

# Create an array comprising a value-type instance (1)
# and a reference-type instance (the custom object).
$arr = 1, $customObject 

# Look for the value-type instance.
$objectToLookFor = 1

$arr -contains $objectToLookFor # value equality -> $true

# Create another custom object, with the same properties as above.
$objectToLookFor = [pscustomobject] @{ one = 1; two = 2 }

# This lookup *fails*, because $objectToLookFor, despite having the same
# properties as the custom object stored in the array, is a *different object* 
$arr -contains $objectToLookFor # reference equality -> $false(!)

# If we look for the very same object stored in the array, the lookup
# succeeds.
$arr -contains $customObject # -> $true
mklement0
  • 382,024
  • 64
  • 607
  • 775