Public/Common/Merge-Hashtable.ps1
function Merge-Hashtable { [CmdletBinding()] param ( # The primary object [Parameter(Mandatory = $true, Position = 0)] [hashtable] $BaseObject, # The secondary object [Parameter(Mandatory = $true, Position = 1)] [hashtable[]] $InputObject, # If set will attempt to do a deep merge [Parameter(Mandatory = $false)] [switch] $Deep ) begin { $Return = $null } process { # Set the return to be the base object $Return = $BaseObject.Clone() # We may have multiple input objects, iterate over each try { $InputObject | ForEach-Object { $_.GetEnumerator() | ForEach-Object { # Because things can get confusing when using $_ notation I'm splitting things into variables to make tracking them easier $InputObjectKeyName = $_.Key $InputObjectValue = $_.Value # First check if the key already exists in the base object if ($Return.Keys -contains $InputObjectKeyName) { $BaseObjectValue = $Return.($InputObjectKeyName) Write-Verbose "BaseObject already contains key of '$($InputObjectKeyName)'" # If the key does already exist and we're doing a deep merge we need to see what content # we are working with if ($Deep) { if ($BaseObjectValue -is [array]) { # Validate that the value we're bringing in is also an array if ($InputObjectValue -is [array]) { # We make the concious choice to remove duplicate entries when merging arrays, I'm not sure # if this is standard behaviour when merging objects but we can could make this a parameter if we need to Write-Verbose "Key '$InputObjectKeyName' is an array, performing merge." Write-Debug "BaseObject values:`n$($BaseObjectValue -join "`n")`nInputObject values:`n$($InputObjectValue -join "`n")" $MergedArray = $BaseObjectValue + $InputObjectValue $DedupeArray = $MergedArray | Select-Object -Unique # We've seen issues when there's just one item that results in a string being returned instead of an array if ($DedupeArray -is [string]) { $DedupeArray = @($DedupeArray) } $Return.($InputObjectKeyName) = $DedupeArray } else { throw "Keys are of different type.`nBaseObject key is: '$($Return.($InputObjectKeyName).GetType().Name)'`nInputObject key is: '$($InputObjectKeyName.GetType().Name)'" } } elseif ($BaseObjectValue -is [hashtable]) { if ($InputObjectValue -is [hashtable]) { Write-Verbose "Key '$InputObjectKeyName' is a hashtable, performing merge." $Return.($InputObjectKeyName) = Merge-Hashtable ` -BaseObject $Return.($InputObjectKeyName) ` -InputObject $InputObjectValue ` -Deep:$Deep ` -ErrorAction 'Stop' } else { throw "Keys are of different type.`nBaseObject key is: '$($Return.($InputObjectKeyName).GetType().Name)'`nInputObject key is: '$($InputObjectKeyName.GetType().Name)'" } } else { Write-Debug "Overwriting key: $($InputObjectKeyName) with value: $($InputObjectValue)" $Return.($InputObjectKeyName) = $InputObjectValue } } else { Write-Debug "Overwriting key: $($InputObjectKeyName) with value: $($InputObjectValue)" $Return.($InputObjectKeyName) = $InputObjectValue } } else { Write-Debug "Adding new key: $($InputObjectKeyName) with value: $($InputObjectValue)" $Return.Add($InputObjectKeyName, $InputObjectValue) } } } } catch { throw "Failed to merge hashtable's.`n$($_.Exception.Message)" } } end { if ($Return) { return $Return } else { return $null } } } |