internal/functions/Confirm-ObjectValueEqualityDeep.ps1

#Requires -PSEdition Core

function Confirm-ObjectValueEqualityDeep {
    [CmdletBinding()]
    param(
        $existingObj,
        $definedObj
    )

    if ($definedObj -is [array] -or $existingObj -is [array]) {
        [array] $definedArray = $() + $definedObj
        [array] $existingArray = $() + $existingObj
        if (($null -eq $definedObj -and $null -ne $existingObj -and $existingArray.Length -eq 0) -or `
            ($null -eq $existingObj -and $null -ne $definedObj -and $definedArray.Length -eq 0)) {
            # null and zero length is the same
            return $true
        }
        else {
            if ($definedArray.Length -ne $existingArray.Length) {
                return $false
            }
            else {
                $notMatches = $definedArray.Length
                if ($existingArray) { $nextExistingArray = $existingArray.clone() }else { $nextExistingArray = @{} }
                foreach ($definedItem in $definedArray) {
                    $found = $false
                    if ($nextExistingArray) { $currentArray = $nextExistingArray.clone() }else { $currentArray = @{} }
                    $nextExistingArray = @()
                    foreach ($existingItem in $currentArray) {
                        if ($found) {
                            $nextExistingArray += $existingItem
                        }
                        elseif (Confirm-ObjectValueEqualityDeep -existingObj $existingItem -definedObj $definedItem) {
                            $null = $notMatches--
                            $found = $true
                        }
                        else {
                            $nextExistingArray += $existingItem
                        }
                    }
                    if (!$found) {
                        return $false
                    }
                }
                return $notMatches -lt 1
            }
        }
    }
    elseif ($definedObj -is [datetime] -xor $existingObj -is [datetime]) {
        if ($definedObj -is [datetime]) {
            $date = $definedObj.ToString("yyyy-MM-dd")
            return $date -eq $existingObj
        }
        else {
            $date = $existingObj.ToString("yyyy-MM-dd")
            return $date -eq $definedObj
        }
    }
    elseif (($null -ne $definedObj.Keys -and $null -ne $definedObj.Values) -or ($null -ne $existingObj.Keys -and $null -ne $existingObj.Values) `
            -or ($definedObj -is [PSCustomObject] -or $existingObj -is [PSCustomObject])) {
        # Line 1 Hashtable or [ordered]
        # Line 2 PSCustomObject

        # Try to convert to [hashtable] - creates a clone if already a hashtable
        [hashtable] $definedHt = ConvertTo-HashTable $definedObj
        [hashtable] $existingHt = ConvertTo-HashTable $existingObj
        foreach ($key in $existingHt.Keys) {
            if ($definedHt.ContainsKey($key)) {
                if (!(Confirm-ObjectValueEqualityDeep -existingObj $existingHt.$key -definedObj $definedHt.$key)) {
                    return $false
                }
                $null = $definedHt.Remove($key)
            }
            elseif ($null -eq $existingHt.$key) {
                # not existing and null is the same
            }
            else {
                # existing ht has more elements
                return $false
            }
        }
        # Does defined contain additional items
        return $definedHt.Count -lt 1
    }
    else {
        return $definedObj -eq $existingObj
    }
}