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 1or2on unsorted collections produces mismatches for items that are identical but appear in different positions. This is almost never the right behaviour. Only reduce-SyncWindowwhen 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-Csvoutput. However, if you select specific columns withSelect-Objectafter-PassThru, you must explicitly includeSideIndicatorin 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.


