Compare-Object Weirdness? Or Business as Usual?

I came across this forum question on the Hey, Scripting Guy! forum the other day and couldn’t help but take up the challenge of finding out and providing (hopefully) a good explanation as to why the following code snippet did not work in the way the poster was thinking it should work:

$test1 = get-service -Name W32Time
Stop-service w32time 
$test2 = get-service -Name W32Time
compare-object -referenceobject $test1 -differenceobject $test2 

The expected output was something showing that there was a change with the W32Time service. However, that was not the case and in fact, nothing was returned giving the illusion that nothing was different between these two objects.

image

If you run the Compare-Object command this way using the –Property parameter and listing Name and Status, it works just fine.

compare-object -referenceobject $test1 -differenceobject $test2 `
-Property Name,Status

image

So, why doesn’t the other way work?

I believe this is by design. When you do not specify a property to compare between objects, it decides on its own what it will compare and show the results accordingly. Based on some research and experimenting, I can see that it actually converts the objects to a string and then continues comparing the string objects if no properties are given.

I am going to plug in the Compare-Object code snippet into Trace-Command and take a look at the output to see if I can determine what the issue is. Trace-Command is a cool low-level trace tool that is available with PowerShell to track down issues with using commands, much like I am doing here.

Start doing some tracing

In order to get an idea about what is different, I will be using both versions of the Compare-Object code with and without the –Property parameter.

First using the –Property parameter

trace-command ETS {compare-object -referenceobject $test1 `
-differenceobject $test2 –Property Name,Status} –pshost

Looking at the resulting output, it appears that it is performing the compare without any issues.

image

Now, without the –Property parameter

trace-command ETS {compare-object -referenceobject $test1 `
-differenceobject $test2} –pshost

image

Well, that is interesting, it says that the current object is not comparable. Note how it takes the object and converts it into a string format System.ServiceProcess.ServiceController prior to starting the compare similar to this:

$test1.ToString()
$test2.ToString()

image

Based on this, Compare-Object sees no difference in this because both string objects are now the same name, therefore nothing is returned after running the command. Think of it as something like this:

image

Can you spot the difference?

Now if there was an added service or one removed, it would show up in the compare-object as being different, but it would be pretty hard to figure out which one was different.

Lets look at one other example, this time using Get-Process just to see how using Compare-Object can work in a different case.

This time I am going to get a baseline of the current processes and then start notepad up and take another snapshot of the current processes and perform a comparison of each collection.

$processes_before = get-process
notepad
$processes_after  = get-process 
compare-object -referenceobject $processes_before `
-differenceobject $processes_after

image

But wait, how is this possible? Shouldn’t it be the same way for the process collection as it was with the Services collection? Well, yes and no. The objects are converted to a string format much like the Service object, but in this case there is more to the string object this time around than just the name of the object.

$processes_before[0].ToString()
$processes_after[0].ToString()

image

As you can see, not only does it list the process object, but also the name of the process. Because of this,there will be a notepad object in one collection and not in the other collection.

Just for fun, lets run both this command along with a command using the –Property parameter just to see what we get.

First using the –Property parameter

trace-command ETS {compare-object -referenceobject $ processes_before `
-differenceobject $processes_after –Property Name,ID} –pshost

image

I chose this these properties on purpose as they are static and will not change unless I actually remove the process. The output is pretty large, but if you run it you can see that it has no problem comparing these properties specified.

Now, without the –Property parameter

trace-command ETS {compare-object -referenceobject $processes_before `
-differenceobject $processes_after} –pshost

image

Here is again will see that it cannot compare this object as is and will convert it to a string prior to comparison. But in this case, the object has the one thing that the service object did not, a name with it that can provide a more accurate comparison of the object. Keep in mind that this only works as designed in this instance when you are only caring about tracking processes by count (baseline of running processes compared to another baseline performed later).

image

This will not work in other cases where you want to see changes in handles or other properties without the use of the –Property parameter.

Getting back to the services

Ok, so how can we use Compare-Object without specifying the –Property parameter to see if a service had stopped since the last baseline? Well, there is one way that I found that seems to work rather well, but still has some manual checking after a difference is found. For this you have to use the ConvertTo-CSV command to make this work out.

$test1 = get-service -Name W32Time | ConvertTo-Csv
Stop-service w32time
$test2 = get-service -Name W32Time | ConvertTo-Csv
Compare-Object -ref $test1  -diff $test2

image

You will have to do some manual inspection to validate what that difference is which was registered. Of course, you could also write some code to automate this process to find the difference.

ConvertFrom-Csv $test1
ConvertFrom-Csv $test2

image

In this case, we can see that the service was stopped after the initial baseline.

This all works because the string object is different between the baseline and the run afterwards.

image

Not exactly pleasant to the eyes, but it does work when using Compare-Object.

I did write a small code snippet that performs a more automated approach to this but I must stress that it is not a finished product in many ways. This is merely showing one example at achieving a more automated way of reviewing which property (or properties) have changed.

$t1 = ConvertFrom-Csv $test1
$t2 = ConvertFrom-Csv $test2
$t1 | gm -type noteproperty | Select -Expand Name | ForEach {
    If (-NOT ($t1.$_ -eq $t2.$_)) {
        New-Object PSObject -Property @{
            ServiceName = $t1.Name
            PropertyName = $_
            PreviousState = $t1.$_
            CurrentState = $t2.$_
        }
    }    
}

image

There you have it, a quick and dirty check to locate the changed properties.

So, the bottom line is if you want to compare the properties of two objects, you should use the –Property parameter to get the most accurate results.

About Boe Prox

Microsoft Cloud and Datacenter MVP working as a SQL DBA.
This entry was posted in powershell and tagged , , , . Bookmark the permalink.

4 Responses to Compare-Object Weirdness? Or Business as Usual?

  1. Hi,

    Very nice post!
    I came across it while searching for the answer “What’s wrong with Compare-Object?”
    However, I found Comparing txt files to be very convenient:

    PS> $userDisabled = Get-ADObject -Identity (get-mailbox -Filter {Name -like “userdisable
    *”}).DistinguishedName -Properties *
    PS> $userDisabled = Get-ADObject -Identity (get-mailbox -Filter {Name -like “userdisable
    *”}).DistinguishedName -Properties *
    PS C:\Users\ysamorodov-a\Documents> Compare-Object -ReferenceObject (Get-Content .\userEnabled.txt) -DifferenceObject (Get-Content .\userDisabled.txt)

    Kind regards,
    Yuriy

  2. Pingback: Powershell: Tipps & Tricks Using Compare-Object « MS Tech BLOG

  3. LucD says:

    A very informative post.

    When I encountered this ToString default behavior for complex objects before, I overwrote the method. That would also work in a compare I suppose.
    $services = Get-Service | Add-Member -Force -Name ToString -MemberType ScriptMethod `
    -PassThru -Value {$this.DisplayName + ‘ ‘ + $this.Status}
    Agreed, you still have to specify the properties you want to see in the code block of the new ToString method.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s