Utilities/Merge-ObjectDeep.ps1

function Merge-ObjectDeep {
    param(
        [Parameter(Mandatory)] 
        $Base,
        
        [Parameter(Mandatory)] 
        $Override
    )

    if ($null -eq $Override) { 
        return $Base 
    }

    # For hashtables and PSCustomObjects, walk properties
    if ($Base -is [hashtable] -or $Base -is [pscustomobject]) {

        $result = if ($Base -is [hashtable]) { 
            @{} 
        } 
        else { 
            [pscustomobject]@{} 
        }

        $allKeys = @(if ($Base -is [hashtable]) { $Base.Keys } else { $Base.PSObject.Properties.Name }) + @(if ($Override -is [hashtable]) { $Override.Keys } else { $Override.PSObject.Properties.Name }) | Select-Object -Unique

        foreach ($k in $allKeys) {
            $b = if ($Base -is [hashtable]) { 
                $Base[$k] 
            } 
            else { 
                $Base.$k 
            }

            $o = if ($Override -is [hashtable]) { 
                $Override[$k] 
            } 
            else { 
                $Override.$k 
            }

            if ($null -ne $o -and ($b -is [hashtable] -or $b -is [pscustomobject])) {
                $result | Add-Member -NotePropertyName $k -NotePropertyValue (Merge-ObjectDeep $b $o)
            } 
            elseif ($null -ne $o) {
                $result | Add-Member -NotePropertyName $k -NotePropertyValue $o
            } 
            else {
                $result | Add-Member -NotePropertyName $k -NotePropertyValue $b
            }
        }
        return $result
    }

    # For scalars/arrays: override wins if provided, else base
    if ($null -ne $Override) { 
        return $Override 
    } 
    else { 
        return $Base 
    }
}