Powershell 5, Is there an easy way to compare property values from array of hashtables

Viewed 368

I am trying to find a way to compare 2 arrays of hashtables to see if there is a match for certain property.

If there will be a match I need a certain value to happen and if there is no match, then other action needs to happen.

In theory nested loop should do the job but I would have to figure out what array is longer.

Example in code would be:

$hashtable1 = @{}
$hashtable1.name = "xxx"
$hashtable1.surname =@()
$hashtable1.surname += "bbb"

$hashtable2 = @{}
$hashtable2.name = "aaa"
$hashtable2.surname =@()
$hashtable2.surname += "ccc"

$hashtable3 = @{}
$hashtable3.name = "bbb"
$hashtable3.surname = @()
$hashtable3.surname += "xxx"

$A = @($hashtable1; $hashtable2; $hashtable3)

$hashtable4 = @{}
$hashtable4.name = "yyy"
$hashtable4.surname =@()
$hashtable4.surname += "bbb"

$hashtable5 = @{}
$hashtable5.name = "xxx"
$hashtable5.surname = @()
$hashtable5.surname += "ccc"

$hashtable6 = @{}
$hashtable6.name = "ddd"
$hashtable6.surname = @()
$hashtable6.surname += "xxx"

$hashtable7 = @{}
$hashtable7.name = "ddd"
$hashtable7.surname = @()
$hashtable7.surname += "xxx"

$Array1 = @($hashtable1; $hashtable2; $hashtable3)
$Array2 = @($hashtable4; $hashtable5; $hashtable6; $hashtable7)

In this case, I would need to find out that: hashtable1.name is the same as hashtable5.name and then take certain action.

I was wondering if there is a better way.

Note that I have been asking a similar question yesterday in here:

Powershell Compare 2 Arrays of Hashtables based on a property value

That solution worked for a bit different scenario but this time, I really need to compare parameters of hashtable in a arrays against each other.

I am on PowerShell 5 on windows 10.

Thanks, Aster

3 Answers

You can test :

$commonNames = (Compare-Object $Array1.name $Array2.name -IncludeEqual -ExcludeDifferent).inputObject

In addition to JPBlanc's helpful answer. I think it may be useful to return the objects that match. If you unroll the name property then only the name property will be returned. Luckily Compare-Object has some parameters to help with this. In particular:

-Property : Instructs Compare-Object which property on the underlying objects to compare.

-PassThru : Returns the original objects with an added property "SideIndicator" At which point you can still determine which objects had equivalent name properties.

This might end up looking something like:

Compare-Object $Array1 $Array2 -Property Name -PassThru -IncludeEqual | 
Where-Object{ $_.SideIndicator -eq '==' }

HOWEVER:

This will not filter well because HashTables are by definition collections Name/Value pairs, so as PowerShell unrolls the arrays for comparison it will try to filter on the Name column/property in the Name/Value pair, which isn't what you want. Instead you want to filter on the name key's value is referenced by "Name", very confusing. You can get around this by formally converting the hash tables to objects, and PowerShell makes that quite easy:

$Array1 = @([PSCustomObject]$hashtable1; [PSCustomObject]$hashtable2; [PSCustomObject]$hashtable3)
$Array2 = @([PSCustomObject]$hashtable4; [PSCustomObject]$hashtable5; [PSCustomObject]$hashtable6; [PSCustomObject]$hashtable7)

Compare-Object $Array1 $Array2 -Property Name -PassThru -IncludeEqual | 
Where-Object{ $_.SideIndicator -eq '==' }

Now if this is suitable you can clean this up a bit:

$Object1 = [PSCustomObject]@{
    $Object1.name    = "xxx"
    $Object1.surname = @("bbb")
}

$Object2 = [PSCustomObject]@{
    $Object2.name    = "aaa"
    $Object2.surname = @("ccc")
}

$Object3 = [PSCustomObject]@{
    $Object3.name    = "bbb"
    $Object3.surname =  @("xxx")
}

$Object4 = [PSCustomObject]@{
    $Object4.name    = "yyy"
    $Object4.surname = @("bbb")
}

$Object5 = [PSCustomObject]@{
    $Object5.name    = "xxx"
    $Object5.surname =  @("ccc")
}

$Object6 = [PSCustomObject]@{
    $Object6.name    = "ddd"
    $Object6.surname =  @("xxx")
}

$Object7 = [PSCustomObject]@{
    $Object7.name    = "ddd"
    $Object7.surname =  @("xxx")
}

$Array1 = @($Object1; $Object2; $Object3)
$Array2 = @($Object4; $Object5; $Object6; $Object7)

Compare-Object $Array1 $Array2 -Property Name -PassThru -IncludeEqual | 
Where-Object{ $_.SideIndicator -eq '==' }

The above example simply says you might as well start with objects, which can, as before , be created by simply casting a hash table to a [PSCustomObject]. However, in this case were casting the Hash literal instead of the variable. Notice you do not need to use += to append the surname value.

Note: I realize that the hashtables you provided might have been a simple demo construction. If the input is really coming from somewhere else we may have to code a little differently to follow the same pattern in concept.

You can also return the hashtable itself upon match instead of just the .name property like this:

Compare-Object $Array1 $Array2 -IncludeEqual -ExcludeDifferent -Property @{Expression = {$_['name']}} -PassThru

The value of the -Property parameter can be a new calculated property.
By using the -PassThru parameter, Compare-Object omits the PSCustomObject wrapper around the compared objects and returns the differing or equal objects, unchanged.

Result:

Name                           Value                                                                                                                                     
----                           -----                                                                                                                                     
name                           xxx                                                                                                                                       
surname                        {bbb}

P.S. to create the arrays you can simplify using

$Array1 = $hashtable1, $hashtable2, $hashtable3
$Array2 = $hashtable4, $hashtable5, $hashtable6, $hashtable7

so you don't need the @()

Related