Comparing Installed Hotfixes on Servers

This week I had the need to scan several systems and see which hotfixes were installed on which machines and also figure out which hotfixes I needed to install to make sure all machines were identical. Now that PowerShell V2 has been RTM’d in windows 7 and is included in the Release Candidate of the Windows Management Framework, I am going to go with V2 features. However, this script could be ported to V1 if need be.

As I was thinking about this problem, there were a few tools in the toolkit I thought I could use. The first is the new cmdlet, get-hotfix. This is just a wrapper of Win32_QuickFixEngineering but its nice to have it abstracted up to the cmdlet level. The second tool I thought of using is compare-object. I have known about this cmdlet but haven’t had a real opportunity to use it much. It’s actually very powerful but it does take a bit of neuron firing to wrap your head around how it works.

The way I set this is up allows me to compare two servers.  I also have a credential parameter that is used to access servers so I can do my part in supporting the principle of Least Privilege and not be logged in with a Domain Admin account all the time.

So I pull the list of installed hotfixes from each server and select only the HotfixId property. This will make using the compare-object cmdlet a bit easier. If we do a get-member on compare-object we see that it outputs a PSObject with 2 noteproperties, a InputObject and a SideIndicator.

image

From the help on compare-object we find this description of the SideIndicator property:

The result of the comparison indicates whether a property value appeared only in the object from the Reference set (indicated by the <= symbol), only in the object from the Difference set (indicated by the => symbol) or, if the IncludeEqual parameter is specified, in both objects (indicated by the == symbol).

Well that does the job but frankly its output is difficult to easily interpret at first glance. The beauty of PowerShell is that if you really don’t like the way something works, you can easily work around it. In this case, I created a new array of custom objects that have three properties: KB, the name of the first server, and the name of the second server. (Lines 19 and 22)

Then I foreach’d (the new verb of the day) through the collection of compared hotfixes and switched on the “SideIndicator” property. I did this to make the output more clear so users of the script would not have to interpret all the arrows and equal signs generated by compare-object.

Here is a sample of the output:

image

I used Write-Host to generate some text output but I also get back an object that I can slice and dice later on.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
Function Compare-InstalledHotfix {
param (
[parameter(Mandatory=$true,Position=0)]
$server1,

[parameter(Mandatory=$true,Position=1)]
$server2, 

[parameter(Mandatory=$true,Position=3)]
[Management.Automation.PSCredential]
$credential
)

$server1HotFix = get-hotfix -computer $server1 -Credential $credential | select HotfixId
$server2HotFix = get-hotfix -computer $server2 -Credential $credential | select HotfixId

$comparedHotfixes = compare-object $server2HotFix $server1HotFix -IncludeEqual

$result = @();

foreach ($c in $comparedHotfixes) {
    $kbinfo = "" | select KB,$server1,$server2
    $kbinfo.KB = $c.InputObject.HotfixId
    switch ($c.SideIndicator)
    {
    "==" {
            write-host -ForegroundColor Green "Both servers have $($c.InputObject.HotfixId)"
            $kbinfo.($server1) = $true
            $kbinfo.($server2) = $true
            $result += $kbinfo
         }
        
    "=>" {
            write-host -ForegroundColor Yellow "$server1 has $($c.InputObject.HotfixId) but $server2 doesn't"
            $kbinfo.($server1) = $true
            $kbinfo.($server2) = $false
            $result += $kbinfo
          }
         
    "<="  {
            write-host -ForegroundColor Magenta "$server2 has $($c.InputObject.HotfixId) but $server1 doesn't"
            $kbinfo.($server1) = $false
            $kbinfo.($server2) = $true
            $result += $kbinfo
          }
    } # End Switch
  } # End foreach
   $result
 } # End Function

This code is also available on the TechNet Code Gallery and up on PoshCode

Comments (9) -

Hi, great script - however as a newbie to powershell I cannot get it to run. I get the below error "Parameter declarations are a comma-separated list of variable names with optional initializer expres
At E:\compare.ps1:3 char:41
+ [parameter(Mandatory=$true,Position=0)]  <<<<"

Aside from that, the thing is that how can I run this without specifying the fact that I want to run the function? I thought I would need to put "Compare-InstalledHotfix" at the end of the script so that when I run "compare.ps1" it knows I want to execute the function "Compare-InstalledHotfix" - excuse my dumbness in this Smile

thanks

Nice Script and article.  Mayby with a switch you van make the output return objects.

By the way .. may i add this script to my Powershelltips ? offcourse with name and website

Sure thing! Thanks Bernard!

Hello,

Thanks a lot for this fantastic script. Very usefull !

However the compare-object cmdlet can't works without specifying the -SyncWindow parameter.

The value of this parameter must be set to half of the size of the smaller collection, so i put this code before the compar-object line :

$count1 = $server1HotFix.length
$count2 = $server2HotFix.length
if($count1 -lt $count2){
  $MySynWindow = [Math]::Round($count1/2)
}
else{
  $MySynWindow = [Math]::Round($count2/2)
}

$comparedHotfixes = compare-object $server2HotFix $server1HotFix -IncludeEqual -SyncWindow $MySynWindow


Then it seems that there is on more problem with the comparison :

$server1HotFix = get-hotfix -computer $server1 -Credential $credential | select HotfixId

I have used this instead :

$server1HotFix = get-hotfix -computer $server1 -Credential $credential | ?{$_.HotfixId -notlike "*File 1*"} | %{$_.hotfixid}

and then :

$c.InputObject       instead of       $c.InputObject.hotfixid      each times it is used in the foreach statement

... Then, now it works fine !

When I say "can’t works without specifying the -SyncWindow parameter"... in fact it works but not for a list of hotfix greater than 11...

Source : dmitrysotnikov.wordpress.com/.../

And the result is not good if I use "select Hotfixid", that the reason why I used "%{$_.hotfixid}".

Hey Matthew,

Thanks for the note about -syncWindow. I hadn't seen that before. However, I am still not sure what the concern was that made you add  | ?{$_.HotfixId -notlike “*File 1*”} | %{$_.hotfixid}

I specifically used select-object to create a new PSOBject with just the HotFix property and nothing else so I was just comparing collections of HotFixID.  

Thanks for your great feedback !

Hello Andy,

Yes sure, Iwould prefered that : using PSOBject is a better solution ... But I don't know why, the result is not the same (I made several tests).

It's OK with "string" comparison but not with collections (our servers have more than 100 KBs installed).

I'd like to understand why...

Comments are closed