PowerShell

PowerShell – Comparing advanced objects

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.

This post was last modified on April 10, 2022 14:32

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

7 days ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

5 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

7 months ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

8 months ago

How to Efficiently Remove Comments from Your PowerShell Script

As part of my daily development, I create lots of code that I subsequently comment…

8 months ago

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, "PowerShell: Creating an "empty" PSCustomObject" on X…

9 months ago