Power$nippets

Boost your shell with reusable code stripped to its essentials

Merge-Hashtable

PowerShell is capable of just appending hashtables like: @{a = 1; b = 2} + @{c = 3; d = 4} but in case there is an overlap in the tables and the keys are not unique, you will receive an error:

Item has already been added. Key in dictionary: 'a' Key being added: 'a'
At line:1 char:1
+ @{a = 1; b = 2} + @{a = 3; d = 4}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException

The resolve this, it is important to decide what to do with values of the duplicate keys, keep the ones in the first table supplied, overrule them with any new table, or do something more advance like; calculating the average?
The Merge-Hashtable cmdlet below will by default put all the values of the duplicated keys in array and let the programmer decides how to merge them if required.

Instead of removing keys you might consider to simply overwrite them:

$h1 = @{a = 9; b = 8; c = 7}
$h2 = @{b = 6; c = 5; d = 4}
$h3 = @{c = 3; d = 2; e = 1}


Function Merge-Hashtables {
    $Output = @{}
    ForEach ($Hashtable in ($Input + $Args)) {
        If ($Hashtable -is [Hashtable]) {
            ForEach ($Key in $Hashtable.Keys) {$Output.$Key = $Hashtable.$Key}
        }
    }
    $Output
}

For this cmdlet you can use several syntaxes and you are not limited to two input tables: Using the pipeline: $h1, $h2, $h3 | Merge-Hashtables
Using arguments: Merge-Hashtables $h1 $h2 $h3
Or a combination: $h1 | Merge-Hashtables $h2 $h3
All above examples return the same hash table:

Name                           Value
----                           -----
e                              1
d                              2
b                              6
c                              3
a                              9

If there are any duplicate keys in the supplied hash tables, the value of the last hash table is taken.


(Added 2017-07-09)

Merge-Hashtables version 2

In general, I prefer more global functions which can be customized with parameters to specific needs as in the original question: “overwriting key-value pairs in the first if the same key exists in the second”. Why letting the last one overrule and not the first? Why removing anything at all? Maybe someone else want to merge or join the values or get the largest value or just the average…
The version below does no longer support supplying hash tables as arguments (you can only pipe hash tables to the function) but has a parameter that lets you decide how to treat the value array in duplicate entries by operating the value array assigned to the hash key presented in the current object ($_).

Function

Function Merge-Hashtables([ScriptBlock]$Operator) {
    $Output = @{}
    ForEach ($Hashtable in $Input) {
        If ($Hashtable -is [Hashtable]) {
            ForEach ($Key in $Hashtable.Keys) {$Output.$Key = If ($Output.ContainsKey($Key)) {@($Output.$Key) + $Hashtable.$Key} Else  {$Hashtable.$Key}}
        }
    }
    If ($Operator) {ForEach ($Key in @($Output.Keys)) {$_ = @($Output.$Key); $Output.$Key = Invoke-Command $Operator}}
    $Output
}

Syntax

HashTable[] <Hashtables> | Merge-Hashtables [-Operator <ScriptBlock>]

Default By default, all values from duplicated hash table entries will added to an array:

PS C:\> $h1, $h2, $h3 | Merge-Hashtables

Name                           Value
----                           -----
e                              1
d                              {4, 2}
b                              {8, 6}
c                              {7, 5, 3}
a                              9

Examples To get the same result as version 1 (using the last values) use the command: $h1, $h2, $h3 | Merge-Hashtables {$_[-1]}. If you would like to use the first values instead, the command is: $h1, $h2, $h3 | Merge-Hashtables {$_[0]} or the largest values: $h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Maximum).Maximum}.

More examples:

PS C:\> $h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Average).Average} # Take the average values"

Name                           Value
----                           -----
e                              1
d                              3
b                              7
c                              5
a                              9
PS C:\> $h1, $h2, $h3 | Merge-Hashtables {$_ -Join ""} # Join the values together

Name                           Value
----                           -----
e                              1
d                              42
b                              86
c                              753
a                              9
PS C:\> $h1, $h2, $h3 | Merge-Hashtables {$_ | Sort-Object} # Sort the values list

Name                           Value
----                           -----
e                              1
d                              {2, 4}
b                              {6, 8}
c                              {3, 5, 7}
a                              9

The Merge-HashTable cmdlet was originally published at StackOverflow

Last updated on 29 Jun 2020
Published on 6 Nov 2017