Public/Merge-Hashtable.ps1
function Merge-Hashtable { <# .SYNOPSIS This function merges two hashtables into one. It uses the "Left" one as the kind of master and extends it with "Right" .DESCRIPTION Apteco PS Modules - PowerShell merge hashtables The function uses the "Left" one as the kind of master and extends it with "Right" This runs recursively through the hashtable and merges all values By default, keys in "Left" get overwritten, but with the flag "AddKeysFromRight" it also adds key from the right one add the -verbose flag if you want to know more whats about to happen Caution: This module is not an ordered dictionary so it will not preserve the correct order. You are invited to extend the module via pull request if you would like this functionality ;-) .PARAMETER Left Master Hashtable that is the base and where values are getting replaced from right .PARAMETER Right Those values will be added into the left one .PARAMETER AddKeysFromRight Add key from right to left, otherwise not matching keys from right will be ignored .PARAMETER MergePSCustomObjects PSCustomObjects with the same name would be overwritten by default. Use this flag to merge them .PARAMETER MergeArrays Array with the same name would be overwritten by default. Use this flag to merge them .PARAMETER MergeHashtables Hashtables with the same name would be overwritten by default. Use this flag to merge them .EXAMPLE $left = [hashtable]@{ "firstname" = "Florian" "lastname" = "Friedrichs" } $right = [hashtable]@{ "lastname" = "von Bracht" "Street" = "Schaumainkai 87" } Merge-Hashtable -Left $left -right $right results to Name Value ---- ----- lastname von Bracht firstname Florian So it replaces all values on left with the ones from right .EXAMPLE $left = [hashtable]@{ "firstname" = "Florian" "lastname" = "Friedrichs" } $right = [hashtable]@{ "lastname" = "von Bracht" "Street" = "Schaumainkai 87" } Merge-Hashtable -Left $left -right $right -AddKeysFromRight results to Name Value ---- ----- Street Schaumainkai 87 lastname von Bracht firstname Florian So it adds key from right to left that are not existing in left .EXAMPLE $left = [hashtable]@{ "firstname" = "Florian" "lastname" = "Friedrichs" "address" = [hashtable]@{ "Street" = "Schaumainkai 87" } } $right = [hashtable]@{ "lastname" = "von Bracht" "Street" = "Schaumainkai 87" "address" = [hashtable]@{ "Postcode" = 60596 } } Merge-Hashtable -Left $left -right $right results to Name Value ---- ----- lastname von Bracht address {[Postcode, 60596]} firstname Florian So it replaces the hashtable from left with the one from right. Using the -MergeHashtables flag will merge the child hashtables as well .EXAMPLE $left = [hashtable]@{ "firstname" = "Florian" "lastname" = "Friedrichs" "address" = [hashtable]@{ "Street" = "Schaumainkai 87" } } $right = [hashtable]@{ "lastname" = "von Bracht" "Street" = "Schaumainkai 87" "address" = [hashtable]@{ "Street" = "Kaiserstraße 35" "Postcode" = 60596 } } $h = Merge-Hashtable -Left $left -right $right -MergeHashtables So it replaces the also nested hashtables from left with the one from right. Using the -AddKeysFromRight flag will add keys from right to left, also in nested hashtables It results to Name Value ---- ----- lastname von Bracht address {[Street, Kaiserstraße 35]} firstname Florian .EXAMPLE $left = [hashtable]@{ "firstname" = "Florian" "lastname" = "Friedrichs" "address" = [hashtable]@{ "Street" = "Schaumainkai 87" } } $right = [hashtable]@{ "lastname" = "von Bracht" "Street" = "Schaumainkai 87" "address" = [hashtable]@{ "Street" = "Kaiserstraße 35" "Postcode" = 60596 } } Merge-Hashtable -Left $left -right $right -MergeHashtables -AddKeysFromRight will result to Name Value ---- ----- Street Schaumainkai 87 lastname von Bracht address {[Postcode, 60596], [Street, Kaiserstraße 35]} firstname Florian .INPUTS Hashtable .OUTPUTS Hashtable .NOTES Author: florian.von.bracht@apteco.de #> [CmdletBinding()] [OutputType([hashtable])] param ( [Parameter(Mandatory=$true,ValueFromPipeline)][hashtable]$Left ,[Parameter(Mandatory=$true)][hashtable]$Right ,[Parameter(Mandatory=$false)][Switch]$AddKeysFromRight = $false ,[Parameter(Mandatory=$false)][Switch]$MergePSCustomObjects = $false ,[Parameter(Mandatory=$false)][Switch]$MergeArrays = $false ,[Parameter(Mandatory=$false)][Switch]$MergeHashtables = $false ) begin { if ( $null -eq $Left ) { # return return $null } if ( $null -eq $Right ) { # return Write-Warning "-Right is null!" } } process { # Create an empty object $joined = [Hashtable]@{} # Go through the left object If ( $Left -is [hashtable] ) { # Read all properties $leftProps = @( $Left.Keys ) $rightProps = @( $Right.Keys ) # Compare $compare = Compare-Object -ReferenceObject $leftProps -DifferenceObject $rightProps -IncludeEqual # Go through all properties $compare | Where-Object { $_.SideIndicator -eq "<=" } | ForEach-Object { $propLeft = $_.InputObject $joined.Add($propLeft, $Left.($propLeft)) Write-Verbose "Add '$( $propLeft )' from left side" } # Now check if we can add more properties If ( $AddKeysFromRight -eq $true ) { $compare | Where-Object { $_.SideIndicator -eq "=>" } | ForEach-Object { $propRight = $_.InputObject $joined.Add($propRight, $Right.($propRight)) Write-Verbose "Add '$( $propRight )' from right side" } } # Now overwrite existing values or check to go deeper if needed $compare | Where-Object { $_.SideIndicator -eq "==" } | ForEach-Object { $propEqual = $_.InputObject If ( $MergePSCustomObjects -eq $true -and $Left.($propEqual) -is [PSCustomObject] -and $Right.($propEqual) -is [PSCustomObject] -and @( $Right.($propEqual).psobject.properties ).Count -gt 0) { Write-Verbose "Going recursively into '$( $propEqual )'" # Recursively call this function, if it is nested ps custom $params = [Hashtable]@{ "Left" = $Left.($propEqual) "Right" = $Right.($propEqual) "AddPropertiesFromRight" = $AddKeysFromRight "MergePSCustomObjects" = $MergePSCustomObjects "MergeArrays" = $MergeArrays "MergeHashtables" = $MergeHashtables } $recursive = Merge-PSCustomObject @params $joined.Add($propEqual, $recursive) } elseif ( $MergeArrays -eq $true -and $Left.($propEqual) -is [Array] -and $Right.($propEqual) -is [Array] ) { Write-Verbose "Merging arrays from '$( $propEqual )'" # Merge array $newArr = [Array]@( $Left.($propEqual) + $Right.($propEqual) ) | Sort-Object -unique $joined.Add($propEqual, $newArr) } elseif ( $MergeArrays -eq $true -and $Left.($propEqual) -is [System.Collections.ArrayList] -and $Right.($propEqual) -is [System.Collections.ArrayList] ) { Write-Verbose "Merging arraylists from '$( $propEqual )'" # Merge arraylist $newArr = [System.Collections.ArrayList]@() $newArr.AddRange($Left.($propEqual)) $newArr.AddRange($Right.($propEqual)) $newArrSorted = [System.Collections.ArrayList]@( $newArr | Sort-Object -Unique ) $joined | Add-Member -MemberType NoteProperty -Name $propEqual -Value $newArrSorted } elseif ( $MergeHashtables -eq $true -and $Left.($propEqual) -is [hashtable] -and $Right.($propEqual) -is [hashtable] -and @( $Right.($propEqual).Keys ).Count -gt 0) { Write-Verbose "Merging hashtables from '$( $propEqual )'" # Recursively call this function, if it is nested hashtable $params = [Hashtable]@{ "Left" = $Left.($propEqual) "Right" = $Right.($propEqual) "AddKeysFromRight" = $AddKeysFromRight "MergePSCustomObjects" = $MergePSCustomObjects "MergeArrays" = $MergeArrays "MergeHashtables" = $MergeHashtables } $recursive = Merge-Hashtable @params $joined.Add($propEqual, $recursive) } else { # just overwrite existing values if datatypes of attribute are different or no merging is wished $joined.Add($propEqual, $Right.($propEqual)) Write-Verbose "Overwrite '$( $propEqual )' with value from right side" #Write-Verbose "Datatypes of '$( $propEqual )' are not the same on left and right" } } } # return $joined } end { } } |