Evotec Services sp. z o.o., ul. Drozdów 6, Mikołów, 43-190, Poland

PowerShell – Comparing advanced objects

Compare PowerShell

Two years ago, I wrote a blog post on how you can compare two or more objects visually in PowerShell that works on Windows, Linux, or macOS. I've been using that for a while, but it had a specific flaw. Comparing more advanced objects that you often see (for example, returned by Graph API, two config files) wasn't working correctly, constantly throwing errors. The reason for this was that having nested hashtables arrays require more advanced logic. Today I've updated my module to use the ConvertTo-FlatObject function, which allows the Compare-MultipleObjects function to compare suitably more advanced objects hopefully. Of course, it should not throw errors anymore. I wrote a blog post about ConvertTo-FlatObject functionality yesterday. Feel free to use it separately from PSWriteHTML.

Defining the problem

Let's take two or more objects with the same structure but different data. It's pretty easy to compare it if the object is one-dimensional, but things are not that obvious once you try to reach more complicated objects. Below, I have two objects that look the same at first, but I've changed a few values.

$Object1 = [PSCustomObject] @{
    "Name"    = "Przemyslaw Klys"
    "Age"     = "30"
    "Address" = @{
        "Street"  = "Kwiatowa"
        "City"    = "Warszawa"

        "Country" = [ordered] @{
            "Name" = "Poland"
        }
        List      = @(
            [PSCustomObject] @{
                "Name" = "Adam Klys"
                "Age"  = "32"
            }
            [PSCustomObject] @{
                "Name" = "Justyna Klys"
                "Age"  = "33"
            }
            [PSCustomObject] @{
                "Name" = "Justyna Klys"
                "Age"  = 30
            }
            [PSCustomObject] @{
                "Name" = "Justyna Klys"
                "Age"  = $null
            }
        )
    }
    ListTest  = @(
        [PSCustomObject] @{
            "Name" = "Sława Klys"
            "Age"  = "33"
        }
    )
}
$Object2 = [PSCustomObject] @{
    "Name"    = "Przemyslaw Klys"
    "Age"     = "30"
    "Address" = @{
        "Street"  = "Kwiatowa"
        "City"    = "Warszawa"

        "Country" = [ordered] @{
            "Name" = "Gruzja"
        }
        List      = @(
            [PSCustomObject] @{
                "Name" = "Adam Klys"
                "Age"  = "32"
            }
            [PSCustomObject] @{
                "Name" = "Pankracy Klys"
                "Age"  = "33"
            }
            [PSCustomObject] @{
                "Name" = "Justyna Klys"
                "Age"  = 30
            }
            [PSCustomObject] @{
                "Name" = "Justyna Klys"
                "Age"  = $null
            }
        )
    }
    ListTest  = @(
        [PSCustomObject] @{
            "Name" = "Sława Klys"
            "Age"  = "33"
        }
    )
}

Having two objects, we now use the Compare-MultipleObjects function with the FlattenObject switch, which first flattens an advanced object to a single dimension and then does property to property comparison.

Compare-MultipleObjects -Objects $Object1, $Object2 -FlattenObject | Format-Table

As you can see FlattenObject switch converted all nested objects into a single dimension. It converted nested hashtables arrays into properties on the top level. I've also added a new parameter called ObjectsName, which allows overriding the name of columns. It no longer needs to work with column names such as Source, 1, 2, 3, and instead, use your names. Notice I'm also using a Summary switch which allows for quick comparison where values are the same and where those are different.

Compare-MultipleObjects -Objects $Object1, $Object2 -FlattenObject -Summary -ObjectsName 'Object1', 'Object2' | Format-Table *
Comparing Two Objects Visually using PSWriteHTML

After updating Compare-MultipleObjects without much change on the PSWriteHTML module, we now get a working comparison (no more errors – hopefully). To make it more obvious, I've added some colors for the Status column where it currently says True, False, or $null and gives it colors (Green, Red, Yellow).

$Object1,$Object2 | Out-HtmlView -Compare -HighlightDifferences -Filtering -WordBreak break-all

Of course, this is not what you wanted. It no longer throws errors but skips comparing nested objects. You wanted a complete comparison, not just properties that are easy, right? This is where the FlattenObject switch comes into play. Both Out-HtmlView and New-HTMLTable now have it as an option.

I've added more nested objects and properties in the following test to make it more complicated and show the differences.

$Object1 = [PSCustomObject] @{
    "Name"    = "Przemyslaw Klys"
    "Age"     = "30"
    "Address" = @{
        "Street"  = "Kwiatowa"
        "City"    = "Warszawa"

        "Country" = [ordered] @{
            "Name" = "Poland"
        }
        List      = @(
            [PSCustomObject] @{
                "Name" = "Adam Klys"
                "Age"  = "32"
            }
            [PSCustomObject] @{
                "Name" = "Justyna Klys"
                "Age"  = "33"
            }
        )
    }
    ListTest  = @(
        [PSCustomObject] @{
            "Name" = "Justyna Klys"
            "Age"  = "33"
        }
    )
}
$Object2 = [PSCustomObject] @{
    "Name"    = "Przemyslaw Klys"
    "Age"     = "30"
    "Address" = @{
        "Street"  = "Kwiatowa"
        "City"    = "Warszawa"
        
        "Country" = [ordered] @{
            "Name" = "Gruzja"
        }
        List      = @(
            [PSCustomObject] @{
                "Name" = "Adam Klys"
                "Age"  = "32"
            }
            [PSCustomObject] @{
                "Name" = "Pankracy Klys"
                "Age"  = "33"
            }
            [PSCustomObject] @{
                "Name" = "Justyna Klys"
                "Age"  = 30
            }
            [PSCustomObject] @{
                "Name" = "Justyna Klys"
                "Age"  = $null
            }
        )
    }
    ListTest  = @(
        [PSCustomObject] @{
            "Name" = "Sława Klys"
            "Age"  = "33"
        }
    )
    MoreProperties = $true
}

As you can notice, I'm using the new switch FlattenObject, which will convert nested objects to a single dimension.

$Object1,$Object2 | Out-HtmlView -Compare -HighlightDifferences -Filtering -WordBreak break-all -FlattenObject

The flattening of an object causes the properties to have a bit different naming. Every nested object property is joined with its parent name property using a dot. This influences the length of properties and changes a bit original object but allows for comparison to be appropriately displayed in a single dimension (table). Of course, if it's comparing nested arrays, it's the order that matters. This should make it pretty easy to compare two or more objects, even when those are complicated. For example, comparing two Intune policies (before and after)

$Object19 = Get-Content -Raw -LiteralPath "$PSScriptRoot\MDM_iOS_General_Policies.json" | ConvertFrom-Json
$Object20 = Get-Content -Raw -LiteralPath "$PSScriptRoot\TEMP_WW_MDM_iOS_General_Policies.json" | ConvertFrom-Json
$Object19,$Object20 | Out-HtmlView -Compare -HighlightDifferences -Filtering -WordBreak break-all -ExcludeProperty "*odata*","#microsoft*" -FlattenObject -CompareNames "MDM_iOS_General_Policies", "TEMP_MDM_iOS_General_Policies" -SkipProperties

What matters is – they should be similar objects (to some degree).

Using PSWriteHTML as PowerShell Module

For easy use and installation, PSWriteHTML is available from PowerShellGallery. Installing is as easy as typing a simple command in the PowerShell window.

Install-Module PSWriteHTML -AllowClobber -Force

PSWriteHTML covers New-HTMLTable and Out-HtmlView functionality. If you are interested in ConvertTo-FlatObject or Compare-MultipleObjects, you need to install the PSSharedGoods PowerShell module.

Install-Module PSSharedGoods -AllowClobber -Force

Sources as always on GitHub.

Related Posts