PowerShell Compare-Object Internals: Advanced Diffing Techniques
─□✕

PowerShell Compare-Object Internals: Advanced Diffing Techniques

PowerShell Tips Editor 5 min read
PowerShell Compare-Object Internals: Advanced Diffing Techniques

Most PowerShell users discover Compare-Object when they need a quick diff between two arrays and move on without exploring further. That is a missed opportunity. Under the hood, Compare-Object uses a sort-merge algorithm influenced by a tunable -SyncWindow parameter, supports multi-property comparison for structured objects, and offers a -PassThru mode that returns original objects rather than wrapper structs. Understanding these internals turns Compare-Object from a one-trick diff tool into a reliable change-detection engine for configuration management.

Quick Answer

Feed two collections to Compare-Object -ReferenceObject $baseline -DifferenceObject $current. Items only in the reference set get <=; items only in the difference set get =>; items in both get == when you add -IncludeEqual. Use -Property for structured object comparison and -PassThru to get original objects back.

SideIndicators Explained: <=, =>, ==

The SideIndicator property on each output object tells you which collection the item came from. <= means the item exists only in -ReferenceObject (think: something that was removed or is missing from current state). => means the item exists only in -DifferenceObject (something new or unexpected). == appears only when you add -IncludeEqual and represents items present in both.

$baseline = @("ServiceA", "ServiceB", "ServiceC")
$current  = @("ServiceA", "ServiceC", "ServiceD")

Compare-Object -ReferenceObject $baseline -DifferenceObject $current

# Filter to only new additions
Compare-Object -ReferenceObject $baseline -DifferenceObject $current |
    Where-Object { $_.SideIndicator -eq "=>" }
InputObject SideIndicator
----------- -------------
ServiceB    <=
ServiceD    =>

In a config-drift context: <= means a required item was removed; => means an unexpected item was added.

The -SyncWindow Parameter and Its Effect on Output Order

By default -SyncWindow is set to [Int32]::MaxValue, which tells the algorithm to search the entire remaining collection when matching objects, producing order-independent results. Lowering it to a small integer limits how far ahead the algorithm looks for a match. When two identical items appear far apart in unsorted collections, a low -SyncWindow causes them to be reported as mismatches. Unless you are working with very large ordered streams and need to preserve memory, leave -SyncWindow at its default.

Comparing Objects on Multiple Properties with -Property

Without -Property, Compare-Object uses each object’s ToString() representation for comparison, which collapses all properties into a single string. For structured objects — AD users, firewall rules, scheduled tasks — you need to specify exactly which properties define equality.

$baselineServices = Get-Service | Select-Object Name, StartType
$currentServices  = Get-Service | Select-Object Name, StartType

# Simulate a change for demonstration
$currentServices[0].StartType = "Disabled"

Compare-Object `
    -ReferenceObject  $baselineServices `
    -DifferenceObject $currentServices `
    -Property Name, StartType
Name     StartType SideIndicator
----     --------- -------------
wuauserv Disabled  =>
wuauserv Automatic <=

When -Property is used, only the specified properties appear in output — the full original object is not preserved. Use -PassThru if you need the complete object downstream.

Using -PassThru to Return Original Objects Instead of DifferenceInfo

-PassThru changes what Compare-Object emits. Instead of the wrapper object with InputObject and SideIndicator, you get the original objects back — with SideIndicator added as a NoteProperty. This is essential when you want to pipe differing objects directly into other cmdlets without unwrapping them first.

$ref  = Get-ChildItem "C:\Config\Baseline" -File
$diff = Get-ChildItem "C:\Config\Current"  -File

# Get actual FileInfo objects for files that changed
$changedFiles = Compare-Object $ref $diff -Property Name, Length -PassThru |
    Where-Object { $_.SideIndicator -eq "=>" }

# Copy changed files to an archive directory
$changedFiles | Copy-Item -Destination "C:\Config\Archive\" -Force

Note that SideIndicator is attached as a NoteProperty on the object — it will appear in output but is not a native property of the original type.

Including Equal Objects with -IncludeEqual

Adding -IncludeEqual includes objects present in both collections in the output with a == SideIndicator. This is useful for generating a full audit report that shows both compliant and non-compliant items in one pass, which you can then group or filter downstream.

Building a Change-Detection Pipeline for Config Files

The practical application of all these features is a configuration drift detector. Hash a set of config files at known-good state, store the hashes, and compare periodically against the live system. A non-empty result means something changed.

function Get-FileHashInventory {
    param([string]$Path)
    Get-ChildItem $Path -File -Recurse |
        Get-FileHash -Algorithm SHA256 |
        Select-Object @{N="Name";E={ $_.Path }}, Hash
}

$baseline = Import-Csv "F:\baselines\config-hashes.csv"
$current  = Get-FileHashInventory -Path "C:\Windows\System32\drivers\etc"

$drift = Compare-Object $baseline $current -Property Name, Hash |
    Where-Object { $_.SideIndicator -ne "==" }

if ($drift) {
    Write-Warning "$($drift.Count) configuration file(s) have drifted from baseline."
    $drift | Format-Table -AutoSize
} else {
    Write-Output "No configuration drift detected."
}

Common Errors

  • SyncWindow set too low causes false positives: Setting -SyncWindow 1 or 2 on unsorted collections produces mismatches for items that are identical but appear in different positions. This is almost never the right behaviour. Only reduce -SyncWindow when you are explicitly comparing ordered streams and performance is a concern.
  • -PassThru loses the SideIndicator when piped to Export-Csv: SideIndicator is added as a NoteProperty and is included in Export-Csv output. However, if you select specific columns with Select-Object after -PassThru, you must explicitly include SideIndicator in the property list or it will be dropped.

Related Cmdlets / See Also

Wrapping Up

Once you understand SideIndicators, -SyncWindow, -Property, and -PassThru, Compare-Object becomes a powerful building block for configuration drift detection, compliance auditing, and change reporting. The sort-merge algorithm handles the heavy lifting — you just need to feed it the right inputs and filter on the right indicator values.

Send-Item -To