Comparing Installed Hotfixes on Servers
Posted by: Andy Schneider in Compare-Object, Hot Fix, PowershellThis 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.
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:
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)] [parameter(Mandatory=$true,Position=3)] $server1HotFix = get-hotfix -computer $server1 -Credential $credential | select HotfixId $comparedHotfixes = compare-object $server2HotFix $server1HotFix -IncludeEqual $result = @(); foreach ($c in $comparedHotfixes) { |
This code is also available on the TechNet Code Gallery and up on PoshCode
Entries (RSS)
[...] Comparing installed hotfixes on server (dealing with the side indicator) [...]
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
thanks
Are you on PS V2 ?
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 : http://dmitrysotnikov.wordpress.com/2008/06/06/compare-object-gotcha/
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…