Join PowerShell Hash Tables

handshakeI received a lot of feedback and interest in my ConvertTo-Hashtable function. One question I received was “Why?” Well, one reason might be that you want to combine two objects into a single object. Joining them as two hashtables makes this an easier process.

First off, combining two hashtables is as simple as adding them together.


PS C:\> $a=@{Name="Jeff";Count=3;Color="Green"}
PS C:\> $b=@{Computer="HAL";Enabled=$True;Year=2020}
PS C:\> $a+$b

Name Value
---- -----
Name Jeff
Color Green
Year 2020
Computer HAL
Count 3
Enabled True

This works fine as long as there are no duplicate keys.


PS C:\> $b=@{Computer="HAL";Enabled=$True;Year=2020;Color="Red"}
PS C:\> $a+$b
+ : Bad argument to operator '+': Item has already been added. Key in dictionary: 'Color' Key bein
g added: 'Color'.
At line:1 char:4
+ $a+ <<<< $b
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : BadOperatorArgument

So as part of my continued fun with hashtables, I decided to put together a function to join hashtables and handle these key conflicts.


Function Join-Hashtable {

#comment based help is here
[cmdletbinding()]
Param (
[hashtable]$First,
[hashtable]$Second,
[switch]$Force
)

#create clones of hashtables so originals are not modified
$Primary = $First.Clone()
$Secondary = $Second.Clone()

#check for any duplicate keys
$duplicates = $Primary.keys | where {$Secondary.ContainsKey($_)}
if ($duplicates) {
foreach ($item in $duplicates) {
if ($force) {
#force primary key, so remove secondary conflict
$Secondary.Remove($item)
}
else {
Write-Host "Duplicate key $item" -ForegroundColor Yellow
Write-Host "A $($Primary.Item($item))" -ForegroundColor Yellow
Write-host "B $($Secondary.Item($item))" -ForegroundColor Yellow
$r = Read-Host "Which key do you want to KEEP [AB]?"
if ($r -eq "A") {
$Secondary.Remove($item)
}
elseif ($r -eq "B") {
$Primary.Remove($item)
}
Else {
Write-Warning "Aborting operation"
Return
}
} #else prompt
}
}

#join the two hash tables
$Primary+$Secondary

} #end Join-Hashtable

The function takes two hash tables and attempts to join them together. Because I might be removing duplicate keys, I didn't want to change the original hashtables so I first create clones.


$Primary = $First.Clone()
$Secondary = $Second.Clone()

If I didn't, removing a key from $Primary would also remove it from the original hashtable. Next, I check to see if any of the keys from the first hashtable are also in the second hashtable.


$duplicates = $Primary.keys | where {$Secondary.ContainsKey($_)}

If there are none, I simply add the two hashtables together and write the result to the pipeline. But if there are duplicates, and there could be more than one, the function will prompt you for which one to keep and then remove the item from the other hashtable.


Write-Host "Duplicate key $item" -ForegroundColor Yellow
Write-Host "A $($Primary.Item($item))" -ForegroundColor Yellow
Write-host "B $($Secondary.Item($item))" -ForegroundColor Yellow
$r = Read-Host "Which key do you want to KEEP [AB]?"
if ($r -eq "A") {
$Secondary.Remove($item)
}
elseif ($r -eq "B") {
$Primary.Remove($item)
}

Once the duplicates are removed, I can go ahead and add the two together. But since I might not always want to be prompted, I added a -Force parameter which will keep any duplicates from the first hashtable without any prompting. So now, I can combine hashtables like this:


PS C:\> join-hashtable $a $b
Duplicate key Color
A Green
B Red
Which key do you want to KEEP [AB]?: a

Name Value
---- -----
Name Jeff
Color Green
Year 2020
Computer HAL
Count 3
Enabled True

Or force the first hashtable.


PS C:\> join-hashtable $a $b -Force

Name Value
---- -----
Name Jeff
Color Green
Year 2020
Computer HAL
Count 3
Enabled True

But $a and $b are never modified. Now to combine things.


$os = get-wmiobject win32_operatingsystem |
Select Caption,@{Name="computername";Expression={$_.CSName}},
OSArchitecture,ServicePackMajorVersion | ConvertTo-HashTable

$cs = Get-WmiObject win32_computersystem |
Select Manufacturer,Model,TotalPhysicalMemory |
ConvertTo-HashTable

New-object -TypeName PSObject -Property (Join-Hashtable $os $cs)

TotalPhysicalMemory : 8577851392
Manufacturer : TOSHIBA
computername : SERENITY
OSArchitecture : 64-bit
Model : Qosmio X505
Caption : Microsoft Windows 8 Pro
ServicePackMajorVersion : 0

I took two different WMI classes and created a single object. Yes, I realize there are any number of ways of accomplishing the same task. In this case I could have also simply run $os+$cs because I knew ahead of time there would be no conflicting keys. Here's a variation for PowerShell 3.0.


$exclude = "__*","Scope","Path","Options","ClassPath","*Properties","Qualifiers"

$os = get-wmiobject win32_operatingsystem |
Select * -ExcludeProperty $exclude | ConvertTo-HashTable -NoEmpty

$cs = get-wmiobject win32_computersystem |
Select * -ExcludeProperty $exclude | ConvertTo-HashTable -NoEmpty

$final = [pscustomobject](Join-Hashtable $os $cs -Force)

This gets all properties from the 2 WMI classes, except for the exceptions, that have a value. I then combine the two and create a new object. If there are any conflicts the $os hashtable will "win".

I'm sure there are other situations where you might want to convert objects to hashtables, or join hashtables. I hope you'll let me know how you use these tools. In the meantime, download Join-Hashtable. It should work in PowerShell 2.0 or 3.0.

3 thoughts on “Join PowerShell Hash Tables”

Comments are closed.