Compare all properties of two objects in Windows PowerShell
I've always said that the Compare-Object cmdlet, which has been around forever in Windows PowerShell, is one of the most flexible and useful cmdlets out there. If you're an IT Professional of any kind, whether you work with Exchange Server or manage file servers, Compare-Object can make you look like a genius. In the past, I've used it for a number of random tasks, from comparing Active Directory group memberships to detecting changed files/folders on a file server.
The Compare-Object cmdlet really shines when you're comparing collections of like objects, and, although it allows you to also compare based on specific object properties, that sometimes leaves a little to be desired. Take for instance, the comparison of two Active Directory user objects. It is fairly common for an administrator to come across scenarios where you need to compare two Active Directory users and find the specific differences. Usually, you're troubleshooting something strange that happens for one user but not the other. Often times it may come down to how particular attributes have been configured on the user object. So, Compare-Object to the rescue, right? Not so fast.
[ps]
$ad1 = Get-ADUser amelia.mitchell -Properties *
$ad2 = Get-ADUser carolyn.quinn -Properties *
Compare-Object $ad1 $ad2
[/ps]
Running this code gives you the following output. Not exactly helpful. Of course, we know that the identity of the reference object and difference object will not be the same.
InputObject SideIndicator
----------- -------------
CN=carolyn.quinn,OU=Users,DC=contosocorp,DC=us =>
CN=amelia.mitchell,OU=Users,DC=contosocorp,DC=us <=
To take the comparison one step further, you can use the -Property parameter. This allows you to compare one or more specific parameters. More helpful than the last example, but it assumes you know where the differences between the objects lie.
[ps]
$ad1 = Get-ADUser amelia.mitchell -Properties *
$ad2 = Get-ADUser carolyn.quinn -Properties *
Compare-Object $ad1 $ad2 -Property title,department
[/ps]
Okay, this is definitely more helpful. We can see by the output below that the user objects have different title and department attributes. However, we had to specify the attributes we wanted to compare. If we wanted to compare all attributes, this would be a very long command or a very manual process to compare each attribute.
title department SideIndicator
----- ---------- -------------
Finance Associate Finance =>
Accountant II Accounting <=
So, with a little extra logic, we can do this pretty easily. First, we define the Compare-ObjectProperties function. That function will take any two source objects and get a unique list of all of the property names of both objects we're comparing. This is necessary because objects aren't always going to have the same set of attributes. When that is the case, we want to see where one has a null value and the other is populated. With the list of unique property names, our function can iteratively process them through Compare-Object and only return the properties that are different.
[ps]
Function Compare-ObjectProperties {
Param(
[PSObject]$ReferenceObject,
[PSObject]$DifferenceObject
)
$objprops = $ReferenceObject | Get-Member -MemberType Property,NoteProperty | % Name
$objprops += $DifferenceObject | Get-Member -MemberType Property,NoteProperty | % Name
$objprops = $objprops | Sort | Select -Unique
$diffs = @()
foreach ($objprop in $objprops) {
$diff = Compare-Object $ReferenceObject $DifferenceObject -Property $objprop
if ($diff) {
$diffprops = @{
PropertyName=$objprop
RefValue=($diff | ? {$_.SideIndicator -eq '<='} | % $($objprop))
DiffValue=($diff | ? {$_.SideIndicator -eq '=>'} | % $($objprop))
}
$diffs += New-Object PSObject -Property $diffprops
}
}
if ($diffs) {return ($diffs | Select PropertyName,RefValue,DiffValue)}
}
$ad1 = Get-ADUser amelia.mitchell -Properties *
$ad2 = Get-ADUser carolyn.quinn -Properties *
Compare-ObjectProperties $ad1 $ad2
[/ps]
Running the above code produces the following output. This is definitely what we're looking for.
PropertyName RefValue DiffValue
------------ -------- ---------
CanonicalName contosocorp.us/Users/amelia.mitchell contosocorp.us/Users/carolyn.quinn
CN amelia.mitchell carolyn.quinn
Created 2/16/2017 9:31:12 AM 2/16/2017 9:31:15 AM
createTimeStamp 2/16/2017 9:31:12 AM 2/16/2017 9:31:15 AM
Department Accounting Finance
DisplayName Amelia Mitchell Carolyn Quinn
DistinguishedName CN=amelia.mitchell,OU=Users,DC=contosocorp,DC=us CN=carolyn.quinn,OU=Users,DC=contosocorp,DC=us
dSCorePropagationData {2/16/2017 9:53:26 AM, 2/16/2017 9:31:12 AM,... {2/16/2017 9:53:25 AM, 2/16/2017 9:31:15 AM, 12/31/1600 6:00:0...
employeeType FullTime Contractor
GivenName Amelia Carolyn
Manager CN=christopher.walsh,OU=Users,DC=contosocorp,DC=us
MemberOf {CN=Group8842,OU=Groups,DC=contosocorp,DC=us... {CN=Group8896,OU=Groups,DC=contosocorp,DC=us...
Modified 4/24/2017 7:51:46 AM 4/24/2017 7:51:56 AM
modifyTimeStamp 4/24/2017 7:51:46 AM 4/24/2017 7:51:56 AM
Name amelia.mitchell carolyn.quinn
ObjectGUID e3436c96-ac59-4b0a-a2cd-cea586676089 27abcbb9-57f8-4e4d-8f87-a5ee0e3d6ddd
objectSid S-1-5-21-827217070-865584383-1086049070-1208 S-1-5-21-827217070-865584383-1086049070-1249
Office LON NYC
PasswordLastSet 2/16/2017 9:31:12 AM 2/16/2017 9:31:15 AM
physicalDeliveryOfficeName LON NYC
pwdLastSet 131317326721610425 131317326757222285
SamAccountName amelia.mitchell carolyn.quinn
SID S-1-5-21-827217070-865584383-1086049070-1208 S-1-5-21-827217070-865584383-1086049070-1249
sn Mitchell Quinn
Surname Mitchell Quinn
Title Accountant II Finance Associate
UserPrincipalName amelia.mitchell@contosocorp.us carolyn.quinn@contosocorp.us
uSNChanged 372797 372798
uSNCreated 17224 17511
whenChanged 4/24/2017 7:51:46 AM 4/24/2017 7:51:56 AM
whenCreated 2/16/2017 9:31:12 AM 2/16/2017 9:31:15 AM
One of the best things about this function is that you're not limited to comparing Active Directory user objects. You can use any two objects you want, and they don't even have to be the same type of object! Try for yourself using the example below, which compares a file object to a service. Granted, there probably aren't many use cases for such a scenario, but you never know. The capability is there nonetheless.
[ps]
$file = Get-Item -Path C:\Windows\System32\notepad.exe
$service = Get-Service -Name WinRM
Compare-ObjectProperties $file $service
[/ps]
Happy PowerShell'ing. See you next time.
Comments
- Anonymous
June 16, 2017
Hi Jamie,Could you help expand this to compare each object within an array when the arrays are seperate lengths? I'm trying to compare file names and sizes on a local to a remote directory.Thanks in advance!- Anonymous
June 16, 2017
*different lengths- Anonymous
June 16, 2017
Hi Adam, I think in your case you should just be able to use the native Compare-Object cmdlet.$folder_remote = Get-ChildItem \myserver\myshare\myfolder$folder_local = Get-ChildItem C:\myfolderCompare-Object $folder_remote $folder_local -Property Name,Length
Then you would just have to process the output of Compare-Object according to whatever it is you're trying to accomplish.
- Anonymous
- Anonymous
- Anonymous
August 26, 2017
Hi Jamie,This is beautiful stuff, thanks! This is exactly what i was looking for.I added the parameter 'IncludeEqual', because having a function that returns nothing kinda scares me... i like being able to debug and seeing all the results (like the original Compare-Object).Here's my update:Function Compare-ObjectProperties { Param( [PSObject]$ReferenceObject, [PSObject]$DifferenceObject, [Switch]$IncludeEqual ) $objprops = $ReferenceObject | Get-Member -MemberType Property,NoteProperty | % Name $objprops += $DifferenceObject | Get-Member -MemberType Property,NoteProperty | % Name $objprops = $objprops | Sort | Select -Unique $diffs = @() foreach ($objprop in $objprops) { $diff = Compare-Object $ReferenceObject $DifferenceObject -Property $objprop if ($diff) { $diffprops = @{ PropertyName=$objprop RefValue=($diff | ? {$_.SideIndicator -eq ''} | % $($objprop)) DiffIndicator='' } $diffs += New-Object PSObject -Property $diffprops } elseif ($IncludeEqual) { $diffprops = @{ PropertyName=$objprop RefValue=$ReferenceObject."$objprop" DiffValue=$DifferenceObject."$objprop" DiffIndicator='==' } $diffs += New-Object PSObject -Property $diffprops } } if ($diffs) {return ($diffs | Select PropertyName,RefValue,DiffValue,DiffIndicator)} }