1

Let's say you have two objects that are identical (meaning they have the same properties and the same values respectively).

How do you test for equality?

Example

$obj1 & $obj2 are identical

enter image description here

Here's what I've tried:

if($obj1 -eq $obj2)
{
    echo 'true'
} else {
    echo 'false'
}
# RETURNS "false"

if(Compare-Object -ReferenceObject $obj1 -DifferenceObject $obj2)
{
    echo 'true'
} else {
    echo 'false'
}
# RETURNS "false"

Edit

This is not identical

enter image description here

Kellen Stuart
  • 7,775
  • 7
  • 59
  • 82
  • Use `Compare-Object -IncludeEqual` otherwise it only tells you the differences. Otherwise, if there is no return, it was successful. – Maximilian Burszley Nov 30 '17 at 15:10
  • @TheIncorrigible1 not quite what I was looking for. If I add a property to `$obj1`, `-IncludeEqual` will still return `$true` – Kellen Stuart Nov 30 '17 at 15:12
  • check my answer. It should work how you expect. – Maximilian Burszley Nov 30 '17 at 15:17
  • @TheIncorrigible1 I just ran a test. If you add a new property to one object, it still returns "true", even though in true essence the two objects are not equal – Kellen Stuart Nov 30 '17 at 15:20
  • FTR, there's a difference between identity and equality in object orientation. Identity means you have the exact same object (same object reference), whereas two distinct objects with the same property values are just equal, not identical. – Ansgar Wiechers Nov 30 '17 at 15:21
  • @AnsgarWiechers Am I looking for "Identical" or "Equal" based on my above question? a little confused. I basically want the object to be exactly the same in every way. No difference at all – Kellen Stuart Nov 30 '17 at 15:26
  • I remember using `Compare-Object $obj1.PsObject.Properties $obj2.PsObject.Properties`, but I don't remember if that's complete anymore or if it just works for PSCustomObjects. – Bacon Bits Nov 30 '17 at 15:28
  • @BaconBits Yeah, at this point I'm wondering if I'm going to have to loop through all properties an values of each object – Kellen Stuart Nov 30 '17 at 15:29
  • Well, no, I mean that's what `Compare-Object $obj1.PsObject.Properties $obj2.PsObject.Properties` already does. The only additional think I can thing to do would be to add `$obj1.GetType() -eq $obj2.GetType()`. – Bacon Bits Nov 30 '17 at 15:33
  • 2
    Don't post picture output. Use code formatting and paste the output as text. – Bill_Stewart Nov 30 '17 at 15:49
  • @Bill_Stewart Why do I get a downvote on every question I ever ask from you? What exactly is unclear about my question? – Kellen Stuart Nov 30 '17 at 19:33
  • 1
    @KolobCanyon I didn't downvote your question. Perhaps someone else downvoted because you used pictures instead of pasting output text? (Wasn't me, though; I thought it was a good question.) – Bill_Stewart Nov 30 '17 at 20:16

5 Answers5

5

You can compare two PSObject objects for equality of properties and values by using Compare-Object to compare the Properties properties of both PSObjectobjects. Example:

if ( -not (Compare-Object $obj1.PSObject.Properties $obj2.PSObject.Properties) ) {
  "object properties and values match"
}
else {
  "object properties and values do not match"
}

If you want it in a function:

function Test-PSCustomObjectEquality {
  param(
    [Parameter(Mandatory = $true)]
    [PSCustomObject] $firstObject,

    [Parameter(Mandatory = $true)]
    [PSCustomObject] $secondObject
  )
  -not (Compare-Object $firstObject.PSObject.Properties $secondObject.PSObject.Properties)
}
Bill_Stewart
  • 22,916
  • 4
  • 51
  • 62
2

I'd suggest using Compare-Object for this task:

Function Test-Objects
{
    Param(
    [Parameter(Mandatory,Position=0)]
    [PSCustomObject]$Obj1,
    [Parameter(Mandatory,Position=1)]
    [PSCustomObject]$Obj2
    )

    [Void](Compare-Object -ReferenceObject $Obj1.PSObject.Properties -DifferenceObject.PSObject.Properties $Obj2 -OutVariable 'Test')

    ## Tests whether they are equal, no return = success
    If (-not $Test)
    {
        $True
    }
    Else
    {
        $False
    }
}

PS C:\> $Obj1 = [PSCustomObject]@{
    Property1 = 'Value1'
    Property2 = 'Value2'
    Property3 = 'Value3'
    Property4 = 'Value4'
    Property5 = 'Value5'
}
PS C:\> $Obj2 = [PSCustomObject]@{
    Property1 = 'Value1'
    Property2 = 'Value2'
    Property3 = 'Value3'
    Property4 = 'Value4'
    Property5 = 'Value5'
}
PS C:\> Test-Objects $Obj1 $Obj2
True
PS C:\> $Obj2 | Add-Member -MemberType 'NoteProperty' -Name 'Prop6' -Value 'Value6'
PS C:\> Test-Objects $Obj1 $Obj2
False
Maximilian Burszley
  • 18,243
  • 4
  • 34
  • 63
  • Say you do this... `$obj1 | Add-Member -NotePropertyName 'NewProperty' -NotePropertyValue 'NewValue'`... if you run the code above it still considers `$obj1` equal to `$obj2`, even though they are not. I just tested it – Kellen Stuart Nov 30 '17 at 15:18
  • Compare-Object uses `ToString()` for comparison - this is unreliable. Use `$obj1 = [pscustomobject] @{ 'a' = '5'; 'b' = 7; }; $obj2 = [pscustomobject] @{ 'c' = '6'; 'b' = 7; }` for example - it says they are equal. See: https://stackoverflow.com/a/18346380/1244910 – Thomas Glaser Nov 30 '17 at 15:19
  • @KolobCanyon That is factually wrong. In my testing (v2, v5), it worked properly. – Maximilian Burszley Nov 30 '17 at 16:17
  • Why `if (-not $Test) { $true } else { $false }` when you can write simply `-not $Test`? – Bill_Stewart Nov 30 '17 at 17:17
  • @Bill_Stewart I was too focused on his question example, but you're absolutely right – Maximilian Burszley Nov 30 '17 at 18:07
1

If you'd like to test for equality for every object property, one at a time, in order to compare and contrast two objects and see which individual pieces are different, you can use the following function, adapted from this article on how to compare all properties of two objects in Windows PowerShell

Function Compare-ObjectProperties {
    Param(
        [PSObject]$leftObj,
        [PSObject]$rightObj 
    )

    $leftProps = $leftObj.PSObject.Properties.Name
    $rightProps = $rightObj.PSObject.Properties.Name
    $allProps = $leftProps + $rightProps | Sort | Select -Unique

    $props = @()

    foreach ($propName in $allProps) {

        # test if has prop
        $leftHasProp = $propName -in $leftProps
        $rightHasProp = $propName -in $rightProps

        # get value from object
        $leftVal = $leftObj.$propName
        $rightVal = $rightObj.$propName

        # create custom output - 
        $prop = [pscustomobject] @{   
            Match = $(If ($propName -eq "SamAccountName" ) {"1st"} Else {
                        $(If ($leftHasProp -and !$rightHasProp ) {"Left"} Else {
                            $(If ($rightHasProp -and !$leftHasProp ) {"Right"} Else {
                                $(If ($leftVal -eq $rightVal ) {"Same"} Else {"Diff"})
                            })
                          })
                     })
            PropName = $propName
            LeftVal = $leftVal
            RightVal = $rightVal
        }

        $props += $prop
    }

    # sort & format table widths
    $props | Sort-Object Match, PropName | Format-Table -Property `
               @{ Expression={$_.Match}; Label="Match"; Width=6}, 
               @{ Expression={$_.PropName}; Label="Property Name"; Width=25}, 
               @{ Expression={$_.LeftVal }; Label="Left Value";    Width=40}, 
               @{ Expression={$_.RightVal}; Label="Right Value";   Width=40}

}

And then use like this:

$adUser1 = Get-ADUser 'Grace.Hopper' -Properties *
$adUser2 = Get-ADUser 'Katherine.Johnson' -Properties *   
Compare-ObjectProperties $adUser1 $adUser2

Couple Interesting Notes:

KyleMit
  • 30,350
  • 66
  • 462
  • 664
0

Here's the function I used:

function Test-ObjectEquality {
    param(
        [Parameter(Mandatory = $true)]
        $Object1,
        [Parameter(Mandatory = $true)]
        $Object2
    )

    return !(Compare-Object $Object1.PSObject.Properties $Object2.PSObject.Properties)
}

Examples:

PS C:\> $obj1 = [pscustomobject] @{ 'a' = '5'; 'b' = 7; };
PS C:\> $obj2 = [pscustomobject] @{ 'a' = '5'; 'b' = 7; };
PS C:\> Test-ObjectEquality $obj1 $obj2
True
PS C:\> $obj2 = [psobject] @{ 'a' = '5'; 'b' = 7; };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
PS C:\> $obj2 = New-Object -TypeName PSObject -Property @{ 'a' = '5'; 'b' = 7; };
PS C:\> Test-ObjectEquality $obj1 $obj2
True
PS C:\> $obj2 = [pscustomobject] @{ 'c' = '6'; 'b' = 7; };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
PS C:\> $obj2 = [pscustomobject] @{ 'a' = '5'; 'b' = 8; };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
PS C:\> $obj2 = [pscustomobject] @{ 'a' = '5'; 'b' = 7; c = 8 };
PS C:\> Test-ObjectEquality $obj1 $obj2
False
PS C:\> $obj2 = [pscustomobject] @{ 'a' = '5'; 'b' = '7'; };
PS C:\> Test-ObjectEquality $obj1 $obj2
False

I certainly believe it's possible for this to miss things; however, if you look at what's in Properties you can see what's being compared for every property on an object:

PS C:\> $obj1.PSObject.Properties | Select-Object -First 1


MemberType      : NoteProperty
IsSettable      : True
IsGettable      : True
Value           : 5
TypeNameOfValue : System.String
Name            : a
IsInstance      : True

It's not often that I've cared about more than the MemberType, Name, TypeNameOfValue, or Value of an object's properties.

Also, note that if you really need to, you can compare .PSObject.Members instead of .PSObject.Properties. That will compare properties and methods, although you're only comparing the method calls and not the method definitions.

Bacon Bits
  • 30,782
  • 5
  • 59
  • 66
-1

I wrote a function that checks for exact equality:

 function Global:Test-IdenticalObjects
 {
    param(
        [Parameter(Mandatory=$true)]$Object1,
        [Parameter(Mandatory=$true)]$Object2,
        $SecondRun=$false
    )

    if(-not ($Object1 -is [PsCustomObject] -and $Object2 -is [PsCustomObject))
    {
        Write-Error "Objects must be PsCustomObjects"
        return
    }

    foreach($property1 in $Object1.PsObject.Properties)
    {
        $prop1_name = $property1.Name
        $prop1_value = $Object1.$prop1_name
        $found_property = $false
        foreach($property2 in $Object2.PsObject.Properties)
        {
            $prop2_name = $property2.Name
            $prop2_value = $Object2.$prop2_name
            if($prop1_name -eq $prop2_name)
            {
                $found_property = $true
                if($prop1_value -ne $prop2_value)
                {
                    return $false
                }
            }
        } # j loop
        if(-not $found_property) { return $false }
    } # i loop
    if($SecondRun)
    {
        return $true
    } else {
        Test-IdenticalObjects -Object1 $Object2 -Object2 $Object1 -SecondRun $true
    }
 } # function
Kellen Stuart
  • 7,775
  • 7
  • 59
  • 82
  • 1
    1) Don't recommend `global:` scope ID. 2) Lots of unnecessary work and overhead; a single `Compare-Object` will do (see my answer). – Bill_Stewart Nov 30 '17 at 16:08