PowerShell’s Compare-Object and Hash Tables: Fun With Dot Notation

Screenshot example of the Compare-Object CmdLet.

If you already know exactly what PowerShell’s Compare-Object does, then whoop-de-freaking doo! I guess you are a master PowerShell h@xx0r.

. . . I guess I’ll see you next week, but you should make sure it’s not your Dunning-Kruger Effect making you think you know what it does. AMIRITE?

Oooooooohhhh! Self blog-post link burn!

Anyway, I grew up in a world where diff (or tools like it) made things very easy. The Compare-Object PowerShell CmdLet seems like an easy tool to accomplish a diff, but with hash tables . . . it’s complicated . . . as you will see . . .

As for any disclaimers, my usual approach is to give you working prod-level code, but I decided to go with a more simple on-the-fly example using computer/vm specs so that we can get the concept down here.

What are Hash Tables?

Before we can go deeper on what Compare-Object does, let’s make sure we know what a hash table is. A PowerShell hash table is an array with key value pairs, like an Excel spreadsheet. It has field names (keys), with data for each (values).

In the World of IT Infrastructure, how often do we deal with this in PowerShell or any modern programming language for that matter?

. . . Uh . . . how about . . . like . . . all the freaking time!

Furthermore, it is very common to compare two or more hash tables to see if data has changed or is different in one or the other. It is also very common to have a robot take action(s) as a result.

But at what level do I need to compare? Am I looking for a character that has changed? A value to be altered? Am I simply looking for an element to be added or subtracted? Maybe all of the above?

The Official (Abridged) Definition: Compare-Object

The relevant part of the definition is as follows:

The result of the comparison indicates whether a property value appeared only in the reference object (<=) or only in the difference object (=>). If the IncludeEqual parameter is used, (==) indicates the value is in both objects.

What does that mean? What it really means, is that you will need to be very specific about what you are comparing.

Single Elements vs. Key Value Pairs

If you are comparing single elements in an array, like:

cpu
ram
network
storage

. . . . versus . . . .

cpu
vram
network
storage

vram is the odd one out – and Compare-Object will find this using the default options -ReferenceObject and -Difference-Object:

PS > $SPEC01 = "cpu","ram","network", "storage"
PS > $SPEC02 = "cpu","vram","network", "storage"
PS > Compare-Object -ReferenceObject $SPEC01 -DifferenceObject $SPEC02

InputObject SideIndicator
----------- -------------
vram        =>
ram         <=

It will also tell you if there is an element that is in one array and not another. Say I need to compare $SPEC01 with:

cpu
ram
network
storage
iscloud

It will tell you what array element “has been added” in this case:

PS > $SPEC01 = "cpu","ram","network", "storage"
PS > $SPEC03 = "cpu","ram","network", "storage","iscloud"
PS > Compare-Object -ReferenceObject $SPEC01 -DifferenceObject $SPEC03

InputObject SideIndicator
----------- -------------
iscloud     =>

But when it comes to key/value pairs, otherwise known as “Property Values” in Powershell, Compare-Object, by default, will only compare the existence of the key value pairs using these defaults, not a change in their data. Read on for details.

Scenario: Auto-Updating Inventory

This Compare-Object business came up because I need to take a hash table pulled from OneView and I need to write the key value pairs into our NetBox DCIM for automated inventory writes. I can do this one of two ways:

  1. Just update the associated NetBox Device every time I pull data from OneView, to the tune of every 3-4 hours.
  2. Only update the device if the record has changed every 3-4 hours.

The first one is easier to code, higher risk, and resource-intensive, but is done all the time. The second is harder to code, but, let’s be honest here, it’s the right way to do it.

So I spent about 3 hours on a Friday trying to figure out why Compare-Object kept coming up with nothing. Let’s have two hash-table arrays, this time with key value pairs:

cpu = "3.2Ghz"
ram = "256GB"
network = "10G"
storage = "6TB"

and

cpu = "3.2Ghz"
ram = "512GB"
network = "10G"
storage = "6TB"

Note that the value of ram is 256GB in the first and 512GB in the second. Note that if Compare-Object doesn’t find any differences, there is no output:

PS > $SPEC04 = @{ cpu = "3.2Ghz" ; ram = "256GB" ; network = "10G" ; storage = "6TB" }
PS > $SPEC05 = @{ cpu = "3.2Ghz" ; ram = "512GB" ; network = "10G" ; storage = "6TB" }
PS > Compare-Object -ReferenceObject $SPEC04 -DifferenceObject $SPEC05
PS >

I KNOW, RIGHT?

Why is that? Well, you have to get more specific about what you are comparing amongst these hash tables.

Here’s a nice side-lesson: Get-Member. This is one of my most-used CmdLets, especially if I am working with something new.

If you do a Get-Member on a hash table, you will get the ability to pull out keys, values, or what’s called a “NoteProperty”:

PS > $SPEC04 | Get-Member

   TypeName: System.Collections.Hashtable

Name              MemberType            Definition
----              ----------            ----------
Add               Method                void Add(System.Object key, System.Object value), void IDictionary.Add(System.Object key, System.Object value)
Clear             Method                void Clear(), void IDictionary.Clear()
Clone             Method                System.Object Clone(), System.Object ICloneable.Clone()
Contains          Method                bool Contains(System.Object key), bool IDictionary.Contains(System.Object key)
ContainsKey       Method                bool ContainsKey(System.Object key)
ContainsValue     Method                bool ContainsValue(System.Object value)
CopyTo            Method                void CopyTo(array array, int arrayIndex), void ICollection.CopyTo(array array, int index)
Equals            Method                bool Equals(System.Object obj)
GetEnumerator     Method                System.Collections.IDictionaryEnumerator GetEnumerator(), System.Collections.IDictionaryEnumerator IDictionary.GetEnumerator(), System.Collections.…
GetHashCode       Method                int GetHashCode()
GetObjectData     Method                void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context), void ISerializable.…
GetType           Method                type GetType()
OnDeserialization Method                void OnDeserialization(System.Object sender), void IDeserializationCallback.OnDeserialization(System.Object sender)
Remove            Method                void Remove(System.Object key), void IDictionary.Remove(System.Object key)
ToString          Method                string ToString()
Item              ParameterizedProperty System.Object Item(System.Object key) {get;set;}
Count             Property              int Count {get;}
IsFixedSize       Property              bool IsFixedSize {get;}
IsReadOnly        Property              bool IsReadOnly {get;}
IsSynchronized    Property              bool IsSynchronized {get;}
Keys              Property              System.Collections.ICollection Keys {get;}
SyncRoot          Property              System.Object SyncRoot {get;}
Values            Property              System.Collections.ICollection Values {get;}

Note the highlighted “Keys” and “Values” Properties. So now, alter your command to include the dot notation that refers to those properties, and the difference(s) should light right up. This time I am going to compare the Values specifically:

PS > $SPEC04 = @{ cpu = "3.2Ghz" ; ram = "256GB" ; network = "10G" ; storage = "6TB" }
PS > $SPEC04.Values
6TB
3.2Ghz
256GB
10G
PS > $SPEC05 = @{ cpu = "3.2Ghz" ; ram = "512GB" ; network = "10G" ; storage = "6TB" }
PS > Compare-Object -ReferenceObject $SPEC04.Values -DifferenceObject $SPEC05.Values

InputObject               SideIndicator
-----------               -------------
{512GB, 10G, 6TB, 3.2Ghz} =>
{256GB, 10G, 6TB, 3.2Ghz} <=

Now we are talking.

What can you do with this?

Here’s another side lesson: sometimes it’s what you learn on day 2 of intro to programming that will be your answer. Here is a very simple example:

$SPEC04 = @{ cpu = "3.2Ghz" ; ram = "256GB" ; network = "10G" ; storage = "6TB" }
$SPEC05 = @{ cpu = "3.2Ghz" ; ram = "512GB" ; network = "10G" ; storage = "6TB" } # Make this the same to alter the result of the script.
$Comparison = Compare-Object -ReferenceObject $SPEC04.Values -DifferenceObject $SPEC05.Values
    If ($Comparison) {
        Write-Host "Robot Does All the Things."
    }
    Else {
        Write-Host "Robot Does Nothing."
    }

Therefore, you can see if there are differences and take action. This is, of course, not the only way to do this, and I am open others. Your homework would be to loop through the values of each array and increment a counter by one if there are differences, or see what specifically has changed in order to take action. That’s Intro to Programming day 3. 😀

Hit me up on twitter @RussianLitGuy or email me at bryansullins@thinkingoutcloud.org. I would love to hear from you!

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s