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.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

About the Author

PowerShell.org Announcer

This is the official account for PowerShell.org and sponsor announcements.