Functions/Merge-DryADPSObjects.ps1

Function Merge-DryADPSObjects {
    [CmdLetBinding()]
    Param (
        $FirstObject,
         
        $SecondObject,

        [Switch]$PreferSecondObjectOnConflict,

        [Switch]$FailOnConflict
    )
    Try {
        # This will accumulate the result
        $Private:Resultobject = New-Object -TypeName psobject
        $Private:ProcessedConflictingPropertyNames = @()

        # is both are arrays, merge
        If (($FirstObject -is [Array]) -and ($SecondObject -is [Array])) {
            $Private:ResultArray+=$FirstObject
            $Private:ResultArray+=$SecondObject
            Return $Private:ResultArray 
        }
        ElseIf ( ($FirstObject -is [String]) -and $SecondObject -is [String] ) {
            # This happens when properties are identical in above iterations. By default, the property from
            # $FirstObject is returned, unless the switch $PreferSecondObjectOnConflict is passed - then
            # the property from $SecondObject is returned. In any case, if the switch $FailOnConflict,
            # is passed, we throw an error
            If ($FailOnConflict) {
                Throw "There was conflict (identical properties) and you passed -FailonConflict"
            }
            Else {
                If ($PreferSecondObjectOnConflict) {
                    Return $SecondObject
                } 
                Else {
                    Return $FirstObject
                }
            }
        }
        ElseIf ( ($FirstObject -is [PSCustomObject]) -and $SecondObject -is [PSCustomObject] ) {
            # Iterate through each object property of $FirstObject
            ForEach ($Property in $FirstObject | Get-Member -Type NoteProperty,Property) {
                # does SecondObject have a matching node?
                If ($null -eq $SecondObject.$($Property.Name)) {
                    # $SecondObject does not contain the current property from $FirstObject, so
                    # the property can be added to $Private:Resultobject as it is
                    $Private:Resultobject | Add-Member -MemberType $Property.MemberType -Name $Property.Name -Value $FirstObject.($Property.Name)
                }
                Else {
                    # $SecondObject contains the current property from $FirstObject, so
                    # the two must be merged. Call Merge-DryADPSObject
                    $Private:Resultobject | Add-Member $Property.MemberType -Name $Property.Name -Value ( Merge-DryADPSObjects -FirstObject ($FirstObject.$($Property.Name)) -SecondObject ($SecondObject.$($Property.Name)) -PreferSecondObjectOnConflict:$PreferSecondObjectOnConflict -FailOnConflict:$FailOnConflict)
                    $Private:ProcessedConflictingPropertyNames += $Property.Name
                }
            }

            # Members in $SecondObject that are not yet processed, has no
            # match in $FirstObject, and may be added to the result as is
            ForEach ($Property in $SecondObject | Get-Member -type NoteProperty, Property) {
                If ($Private:ProcessedConflictingPropertyNames -notcontains $Property.Name) {
                    ol d "Trying to add property '$($Property.Name)', type '$($Property.MemberType)', Value '$($SecondObject.($Property.Name))' "

                    $Private:Resultobject | Add-Member -MemberType $Property.MemberType -Name $Property.Name -Value $SecondObject.($Property.Name)
                }
                Else {
                    ol d "Property '$($Property.Name)' is already processed"
                }
            }
            Return $Private:Resultobject
        }
        Else {
            ol e "FirstObject type: $($($FirstObject.Gettype()).Name) (Basetype: $($($FirstObject.Gettype()).BaseType))"
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    Catch {
        $PSCmdLet.ThrowTerminatingError($_)
    }
}