Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(AD) object equality in PowerShell

The following seems quite weird to me:

$user1 = Get-ADUser sameuser
$user2 = Get-ADUser sameuser
$user1 -eq $user2                # -> false
# the same for groups:
$group1 = Get-ADGroup samegroup
$group2 = Get-ADGroup samegroup
$group1 -eq $group2              # -> false

Actually it seems that Powershell users can be happy that 1 -eq 1 is true. Also:

"1" -eq 1           # -> true
@("1") -contains 1  # -> true

But:

$h1 = @{bla = 1}
$h2 = @{bla = 1}
$h1 -eq $h2                          # -> false
$h1.GetHashCode(), $h2.GetHashCode() # -> 60847006, 5156994
# the above return values of course vary
$a1 = @(1;2;3)
$a2 = @(1;2;3)
$a1.GetHashCode(), $a2.GetHashCode() # -> 52954848, 34157931
# surprise, surprise:
$a1 -eq $a2           # no return value at all? (tested with versions 4.0 and 5.1)
($a1 -eq $a2).GetType()              # or an Array?
($a1 -eq $a2).count                  # -> 0

Aside from these funny behaviors what really feels frustrating is that I cannot simply do it this way:

$ones      = Get-ADPrincipalGroupMembership one
$seconds   = Get-ADPrincipalGroupMembership second
$excl_ones = $ones | ? { $_ -notin $seconds }

But have to do something like this:

$second_nms = $seconds | % name
$excl_ones = $ones | ? { $_.name -notin $second_nms }

Am I missing something?

like image 833
TNT Avatar asked Jan 28 '23 15:01

TNT


1 Answers

To understand some of the oddities your seeing we have to take a step back and consider the bigger picture, namely the framework PowerShell is built on top of: .NET!

Object Equality in .NET

$user1 -eq $user2 fails because $user1 and $user2 are two different objects - although they may both represent the same object in Active Directory.

When it comes to object equality in .NET, you'll need to distinguished between value equality and reference equality.

Two variables of a value type, like [int] for example, are considered equal if their underlying value is the same:

$a = 1
$b = 1
$a.Equals($b) # $true

Two variables of a reference type - anything that's not a value type - are usually only considered equal if they have the same identity - that is, they refer to the same object in memory:

$a = New-Object object
$b = New-Object object
$a.Equals($b) # $false

For all we know, $a and $b are exactly the same, but they refer to two distinct [object] instances in memory.

A type definition can override GetHashCode() (the function used to determine an object's identity) and Equals() (the function used to determine equality between two objects), so you may find that some reference types seem to act as value types when comparing them - [string] being a prime example:

$a = "test"
$b = "test"
$a.Equals($b)

The ADEntity class (the base type for all output objects in the ActiveDirectory module) doesn't attempt something like this, which is why you see the results you do.

Collection filtering in PowerShell

The above doesn't quite explain another weird thing you raised, namely this:

$a1 = @(1;2;3)
$a2 = @(1;2;3)
$a1 -eq $a2 # NOTHING! WHAT'S GOING ON HERE?

To understand what's going on here, you need to study comparison operator behavior in PowerShell itself!

All the comparison operators (-eq, -ne, -gt, -like, -match etc.) support two different modes depending on the left-hand side argument: scalar and filtering.

Scalar comparison

In scalar mode, a comparison operator takes a single object as its left-hand operand, a value expression as its right-hand operand and returns a boolean result: $true or $false.

Filtering using comparison operators

In filtering mode, a comparison operator takes a collection (an array or a list) as its left-hand operand, a value expression as its right-hand operand (just like before) and returns all the individual members of the left-hand collection that satisfy the comparison.

To see this in action, try the following:

$names  = "James","Jane","John"
$prefix = "Ja"

$names -like "$prefix*"

You'll see that the -like operations returns two strings - James and Jane.

If we apply this newfound knowledge to your example

@(1;2;3) - eq @(1;2;3)

it becomes obvious why nothing is returned - the left hand operand is clearly an array, and neither of the ensuing comparisons (1 -eq @(1;2;3), 2 -eq @(1;2;3) etc.) will return $true


Now on to the practical part of your problem. Active Directory is designed in a way so that each object in the directory has a unique identifier you can use to figure out the identity of it - the objectGUID value. A GUID in .NET happens to be a value type, so you can safely use it as a basis for you comparison:

$ones      = Get-ADPrincipalGroupMembership one
$seconds   = Get-ADPrincipalGroupMembership second
$excl_ones = $ones | ? { $_.objectGUID -notin $seconds.objectGUID }

For security principals (groups, users, computers etc.), another unique identifier that's safe to use is the objectSID - security identifiers are always unique.

like image 157
Mathias R. Jessen Avatar answered Feb 08 '23 03:02

Mathias R. Jessen