Compare Objects in PowerShell for Differences and Similarities

The Compare-Object cmdlet in PowerShell is a powerful tool for identifying the differences and similarities between two sets of objects. Whether you are comparing files, configurations, or any other data, Compare-Object provides a flexible way to analyze and understand variations. This article will guide you through using Compare-Object effectively to Compare Objects in various scenarios.

Syntax

Compare-Object [-ReferenceObject] <psobject> [-DifferenceObject] <psobject> [-SyncWindow <int32>] [-Property <string[]>] [-CaseSensitive] [-Culture <string>] [-ExcludeDifferent] [-IncludeEqual] [-PassThru]

Description

The Compare-Object cmdlet is designed to compare objects. It takes two sets of objects as input: a reference object set and a difference object set. The cmdlet then analyzes these sets to highlight where they differ and, optionally, where they are the same.

When comparing, Compare-Object first attempts to find a suitable method for comparing entire objects. If a direct comparison method isn’t available, it defaults to using the ToString() method of each object and compares the resulting strings. For more granular control, you can specify one or more properties to focus the comparison on. In this case, Compare-Object only compares the values of the specified properties.

The output of Compare-Object is crucial for understanding the comparison results. It uses a SideIndicator to denote the origin of each object or property value:

  • <=: Indicates that the object or property value is present only in the reference object set.
  • =>: Indicates that the object or property value is present only in the difference object set.
  • ==: (When the -IncludeEqual parameter is used) Indicates that the object or property value is present in both the reference and difference object sets.

It’s important to note that Compare-Object will generate a terminating error if either the reference or difference objects are null ($null).

Examples

Example 1 – Basic File Content Comparison

This example demonstrates a fundamental use case: comparing the content of two text files to identify differences. Let’s assume we have two files:

  • Testfile1.txt: Contains “dog”, “squirrel”, “bird” (each on a new line).
  • Testfile2.txt: Contains “cat”, “bird”, “racoon” (each on a new line).

The following PowerShell command compares these files and shows only the differing lines:

$objects = @{
    ReferenceObject = (Get-Content -Path C:TestTestfile1.txt)
    DifferenceObject = (Get-Content -Path C:TestTestfile2.txt)
}
Compare-Object @objects

Output:

InputObject SideIndicator
----------- -------------
cat         =>
racoon      =>
dog         <=
squirrel    <=
bird

In this output, lines unique to Testfile2.txt (the difference object, indicated by =>) and lines unique to Testfile1.txt (the reference object, indicated by <=) are displayed. Lines common to both files are omitted for clarity in showcasing differences when you compare objects.

Example 2 – Finding Common Content Using ExcludeDifferent

Building upon the previous example, we can use the -ExcludeDifferent parameter to isolate the content that is identical in both files. As of PowerShell 7.1, using -ExcludeDifferent implicitly includes equal items, showing only what’s common.

$objects = @{
    ReferenceObject = (Get-Content -Path C:TestTestfile1.txt)
    DifferenceObject = (Get-Content -Path C:TestTestfile2.txt)
}
Compare-Object @objects -ExcludeDifferent

Output:

InputObject SideIndicator
----------- -------------
bird        ==

This output clearly shows that “bird” is the only line present in both Testfile1.txt and Testfile2.txt, demonstrating how to compare objects and find common elements.

Example 3 – Understanding PassThru for Object Type Preservation

By default, Compare-Object wraps its output in a PSCustomObject, adding properties like InputObject and SideIndicator. However, the -PassThru parameter alters this behavior. It preserves the original object type and instead adds a NoteProperty named SideIndicator to the original objects.

Let’s observe the difference in output types:

$a = $True
Compare-Object -IncludeEqual $a $a

(Compare-Object -IncludeEqual $a $a) | Get-Member

Output (without -PassThru):

InputObject SideIndicator
----------- -------------
True        ==


   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
InputObject NoteProperty System.Boolean InputObject=True
SideIndicator NoteProperty string SideIndicator===

Now, let’s use -PassThru:

Compare-Object -IncludeEqual $a $a -PassThru

(Compare-Object -IncludeEqual $a $a -PassThru) | Get-Member

Output (with -PassThru):

True


   TypeName: System.Boolean

Name          MemberType Definition
----          ---------- ----------
CompareTo     Method     int CompareTo(System.Object obj), int CompareTo(bool value), int IComparable.CompareTo(Syst
Equals        Method     bool Equals(System.Object obj), bool Equals(bool obj), bool IEquatable[bool].Equals(bool ot
GetHashCode   Method     int GetHashCode()
GetType       Method     type GetType()
GetTypeCode   Method     System.TypeCode GetTypeCode(), System.TypeCode IConvertible.GetTypeCode()
ToBoolean     Method     bool IConvertible.ToBoolean(System.IFormatProvider provider)
ToByte        Method     byte IConvertible.ToByte(System.IFormatProvider provider)
ToChar        Method     char IConvertible.ToChar(System.IFormatProvider provider)
ToDateTime    Method     datetime IConvertible.ToDateTime(System.IFormatProvider provider)
ToDecimal     Method     decimal IConvertible.ToDecimal(System.IFormatProvider provider)
ToDouble      Method     double IConvertible.ToDouble(System.IFormatProvider provider)
ToInt16       Method     short IConvertible.ToInt16(System.IFormatProvider provider)
ToInt32       Method     int IConvertible.ToInt32(System.IFormatProvider provider)
ToInt64       Method     long IConvertible.ToInt64(System.IFormatProvider provider)
ToSByte       Method     sbyte IConvertible.ToSByte(System.IFormatProvider provider)
ToSingle      Method     float IConvertible.ToSingle(System.IFormatProvider provider)
ToString      Method     string ToString(), string ToString(System.IFormatProvider provider), string IConvertible.To
ToType        Method     System.Object IConvertible.ToType(type conversionType, System.IFormatProvider provider)
ToUInt16      Method     ushort IConvertible.ToUInt16(System.IFormatProvider provider)
ToUInt32      Method     uint IConvertible.ToUInt32(System.IFormatProvider provider)
ToUInt64      Method     ulong IConvertible.ToUInt64(System.IFormatProvider provider)
TryFormat     Method     bool TryFormat(System.Span[char] destination, [ref] int charsWritten)
SideIndicator NoteProperty string SideIndicator===

As shown, without -PassThru, the output is a PSCustomObject. With -PassThru, the output remains a System.Boolean object, but with the added SideIndicator property. This is useful when you need to compare objects and maintain their original type while still knowing the comparison result.

Example 4 – Comparing Simple Objects by Property

Compare-Object isn’t limited to file content; it can also compare objects based on specific properties. Here, we compare two strings based on their Length property:

$objects = @{
    ReferenceObject = 'abc'
    DifferenceObject = 'xyz'
    Property = 'Length'
}
Compare-Object @objects -IncludeEqual

Output:

Length SideIndicator
------ -------------
     3 ==

Even though the strings themselves are different, their Length property is the same, and -IncludeEqual shows this equality when we compare objects by property.

Example 5 – Comparing Complex Objects and Specifying Properties

When dealing with complex objects, such as process objects, Compare-Object can effectively highlight differences when you specify relevant properties.

First, let’s retrieve two process objects representing different instances of PowerShell:

Get-Process pwsh

$a = Get-Process -Id 11168
$b = Get-Process -Id 17600

$a.ToString()
$b.ToString()

Example Output of Get-Process pwsh (output will vary):

 NPM(K)    PM(M)      WS(M) CPU(s)    Id SI ProcessName
 ------    -----      ----- ------    -- -- -----------
    101   123.32     139.10  35.81  11168  1 pwsh
     89   107.55      66.97  11.44  17600  1 pwsh

Example Output of $a.ToString() and $b.ToString():

System.Diagnostics.Process (pwsh)
System.Diagnostics.Process (pwsh)

If we compare objects $a and $b without specifying properties, Compare-Object might consider them equal because their ToString() representations are the same:

Compare-Object $a $b -IncludeEqual

Output:

InputObject                 SideIndicator
-----------                 -------------
System.Diagnostics.Process (pwsh) ==

However, to see the actual differences, we should specify properties like ProcessName, Id, and CPU:

Compare-Object $a $b -Property ProcessName, Id, CPU

Output (output will vary based on process activity):

ProcessName SideIndicator Id    CPU
----------- ------------- --    ---
pwsh        =>            17600 11.4375
pwsh        <=            11168 36.203125

By specifying properties, we get a detailed comparison, showing the differences in Id and CPU usage, even though the ProcessName is the same, demonstrating effective object comparison.

Example 6 – Comparing Objects Implementing IComparable

If an object implements the IComparable interface, Compare-Object leverages this for comparison. It can even handle comparisons between different types by attempting type conversion to the ReferenceObject type.

Consider comparing a string and a TimeSpan object:

Compare-Object ([TimeSpan]"0:0:1") "0:0:1" -IncludeEqual

Output:

InputObject SideIndicator
----------- -------------
00:00:01    ==

In this case, the string "0:0:1" is converted to a TimeSpan, resulting in equality.

However, reversing the order can lead to a different outcome:

Compare-Object "0:0:1" ([TimeSpan]"0:0:1")

Output:

InputObject SideIndicator
----------- -------------
00:00:01    =>
0:0:1       <=

Here, the TimeSpan is converted to a string for comparison, leading to them being considered different string representations. This highlights the importance of understanding type handling when you compare objects of different types.

Parameters

-CaseSensitive

This parameter makes the comparison case-sensitive. By default, comparisons are case-insensitive.

Compare-Object -ReferenceObject "HELLO" -DifferenceObject "hello" -CaseSensitive

-Culture

Specifies the culture to be used for comparisons, affecting string comparisons and sorting.

Compare-Object -ReferenceObject "Strasse" -DifferenceObject "Straße" -Culture "de-DE"

-DifferenceObject

Specifies the set of objects that will be compared against the ReferenceObject. This is the set from which differences are identified.

Compare-Object -ReferenceObject @(1,2,3) -DifferenceObject @(3,4,5)

-ExcludeDifferent

When used, this parameter ensures that only characteristics that are equal in both object sets are displayed. Differences are discarded, focusing the output on commonalities when you compare objects.

Compare-Object -ReferenceObject @(1,2,3) -DifferenceObject @(2,3,4) -ExcludeDifferent -IncludeEqual

-IncludeEqual

This parameter includes matches (equal objects or property values) in the output, in addition to differences. Equal items are marked with the == SideIndicator.

Compare-Object -ReferenceObject @(1,2,3) -DifferenceObject @(2,3,4) -IncludeEqual

-PassThru

As discussed in Example 3, -PassThru returns the original objects with a SideIndicator note property instead of wrapping them in PSCustomObject.

Compare-Object -ReferenceObject @(1,2,3) -DifferenceObject @(2,3,4) -PassThru

-Property

Allows you to specify one or more properties to use for comparison. This is crucial for complex objects where you need to compare objects based on specific attributes. The property can be a simple property name or a calculated property (script block or hash table).

Compare-Object -ReferenceObject (Get-Process pwsh) -DifferenceObject (Get-Process powershell) -Property CPU, WorkingSet

Compare-Object -ReferenceObject @{Name="A"; Value=1} -DifferenceObject @{Name="A"; Value=2} -Property @{Expression={$_.Value}; Label="Value"}

-ReferenceObject

Specifies the baseline set of objects for comparison. Differences are reported relative to this set.

Compare-Object -ReferenceObject @("apple", "banana", "orange") -DifferenceObject @("banana", "grape", "apple")

-SyncWindow

This parameter optimizes comparison performance for large collections by limiting the number of adjacent objects Compare-Object inspects when searching for a match. The default is [Int32]::MaxValue (entire collection). Reducing SyncWindow can speed up comparisons but might decrease accuracy in very large, unsorted collections.

Compare-Object -ReferenceObject (1..1000) -DifferenceObject (500..1500) -SyncWindow 100

Inputs

Compare-Object accepts PSObject input through the pipeline for the DifferenceObject parameter.

Outputs

By default, Compare-Object produces no output if the ReferenceObject and DifferenceObject are identical.

When differences are found, or when -IncludeEqual is used, Compare-Object outputs PSCustomObject instances. Each object represents an item from either the ReferenceObject or DifferenceObject sets, along with a SideIndicator property (<=, =>, or ==).

With -PassThru, the cmdlet outputs the original objects with an added SideIndicator note property, preserving the original object type.

Notes

PowerShell aliases for Compare-Object include compare and diff.

When using -PassThru, the console display might not immediately show the SideIndicator property due to default formatting for certain object types. However, the property is indeed added to the object.

Related Links

This enhanced article provides a comprehensive guide on how to use Compare-Object effectively to compare objects in PowerShell, complete with detailed explanations, varied examples, and SEO optimization for the keyword “compare object”.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *