
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:
- Just update the associated NetBox Device every time I pull data from OneView, to the tune of every 3-4 hours.
- 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!