internal/functions/Confirm-ObjectValueEqualityDeep.ps1

function Confirm-ObjectValueEqualityDeep {
    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        $Object1,

        [Parameter(Position = 1)]
        $Object2
    )


    if ($Object1 -eq $Object2) {
        return $true
    }
    else {
        if ($null -eq $Object1) {
            # $Object2 is not $null (always true); swap object 1 and object2, so that $Object1 is always not $null
            $tempObject = $Object2
            $Object2 = $Object1
            $Object1 = $tempObject
        }
        $type1 = $Object1.GetType()
        $typeName1 = $type1.Name
        $type2 = $null
        $typeName2 = "null"
        if ($null -ne $Object2) {
            $type2 = $Object2.GetType()
            $typeName2 = $type2.Name
        }
        if ($typeName1 -in @( "Object[]", "ArrayList") -or $typeName2 -in @( "Object[]", "ArrayList")) {
            if ($null -eq $Object2) {
                return $Object1.Count -eq 0
            }
            else {
                if ($typeName1 -notin @( "Object[]", "ArrayList")) {
                    # convert single element $Object1 into an array
                    $Object1 = @( $Object1 )
                }
                if ($typeName2 -notin @( "Object[]", "ArrayList")) {
                    # convert single element $Object2 into an array
                    $Object2 = @( $Object2 )
                }
                # both objects are now of type array or ArrayList
                if ($Object1.Count -ne $Object2.Count) {
                    return $false
                }
                else {
                    # iterate and recurse
                    $object2List = [System.Collections.ArrayList]::new($Object2)
                    foreach ($item1 in $Object1) {
                        $foundMatch = $false
                        for ($i = 0; $i -lt $object2List.Count; $i++) {
                            $item2 = $object2List[$i]
                            if ($item1 -eq $item2 -or (Confirm-ObjectValueEqualityDeep $item1 $item2)) {
                                $foundMatch = $true
                                $null = $object2List.RemoveAt($i)
                                break
                            }
                        }
                        if (!$foundMatch) {
                            return $false
                        }
                    }
                    return $true
                }
            }
        }
        elseif ($typeName1 -eq "DateTime" -or $typeName1 -eq "DateTime") {
            $dateString1 = $Object1
            if ($typeName1 -eq "DateTime") {
                $dateString1 = $Object1.ToString("yyyy-MM-dd")
            }
            $dateString2 = $Object2
            if ($typeName2 -eq "DateTime") {
                $dateString2 = $Object2.ToString("yyyy-MM-dd")
            }
            return $dateString1 -eq $dateString2
        }
        elseif ($typeName1 -eq "String" -or $typeName2 -eq "String") {
            if ($typeName1 -ne "String") {
                $Object1 = $Object1.ToString()
            }
            if ($null -eq $Object2) {
                $Object2 = ""
            }
            elseif ($typeName2 -ne "String") {
                $Object2 = $Object2.ToString()
            }
            return $Object1 -eq $Object2
        }
        elseif ($Object1 -is [System.ValueType] -or $Object2 -is [System.ValueType]) {
            return $false
        }
        elseif (($typeName1 -in @( "Hashtable", "OrderedDictionary", "OrderedHashtable" ) -or $Object1 -is [PSCustomObject]) `
                -and ($typeName2 -in @( "null", "Hashtable", "OrderedDictionary", "OrderedHashtable" ) -or $Object2 -is [PSCustomObject])) {

            if ($Object1 -is [PSCustomObject]) {
                $Object1 = Get-DeepClone $Object1 -AsHashTable
            }

            if ($null -eq $Object2) {
                $Object2 = @{}
            }
            elseif ($Object2 -is [PSCustomObject]) {
                $Object2 = Get-DeepClone $Object2 -AsHashTable
            }

            # walk both sets of keys
            $uniqueKeys = @(@($Object1.Keys) + @($Object2.Keys) | Sort-Object -Unique )
            foreach ($key in $uniqueKeys) {
                #recurse
                $item1 = $Object1.$key
                $item2 = $Object2.$key
                if ($item1 -ne $item2 -and !(Confirm-ObjectValueEqualityDeep $item1 $item2)) {
                    return $false
                }
            }
            return $true
        }
        else {
            # equality of other types handled in the then clause of the outer else clause
            return $false
        }
    }
}