When you need to install a hotfix outside of your normal patching cycle and outside of the usual patching tool (be that WSUS, SCCM...whatever) a good way is to do it semi-manually running wusa.exe.
However, if you have 20+ hosts, it might be a bit inconvenient to RDP to each and execute wusa or double-click on the msu file.
So why not use PS remoting or winrs? Let's give it a go (I copied the msu file to each host into their c:\temp folder prior to running winrs):
gc c:\hostlist.txt | %{winrs -r:$_ wusa.exe c:\temp\windows6.1-kbXXXXXXX.msu /passive /quiet /forcerestart}
But then you will get this:
Windows update could not be installed because of error 2147942405 "Access is denied.
Whaaaat? Why? WHY? A bit of googling will get you here: https://support.microsoft.com/en-us/help/2773898/windows-update-standalone-installer-wusa-returns-0x5-error-access-denied-when-deploying-.msu-files-through-winrm-and-windows-remote-shell
Ok, I will need to extract the msu and then run dsim on each host. It's no big deal, you can run the 2 commands in a remote PS session, or run 2 lines of winrs...etc.. But here is an alternative solution, psexec:
"host1,host2,host3".split(",") | %{start-proces psexec.exe -arg "-s \\$_ wusa.exe c:\temp\windows6.1-kbXXXXXXX.msu /passive /quiet /forcerestart"}
However, you can't just run psexec as it gets hung on wusa.exe, you need to kick it off with 'start-process' and then pass the rest of the arguments to it which will be for psexec which then executes wusa.exe under SYSTEM context.
Hope this helps.
t
Powershell one-liners and short scripts for real-life problems on large and complex Windows networks.
Showing posts with label hotfix. Show all posts
Showing posts with label hotfix. Show all posts
29 April, 2017
27 April, 2013
List details of installed hotfixes remotely - OS
Hotfixing again. In one of the previous articles, I wrote about how to enumerate the list of installed patches on remote hosts and then find out the differences. The only caveat is that Get-Hotfix cmdlet (which basically uses WMI class Win32_QuickFixEngineering) doesn't contain too much information on the particular hotfix itself apart from the KB number.
However, the Windows Update Agent (WUA) has an API which can be access from PowerShell via COM (Microsoft.Update.Session) and can be called remotely to get almost all fields of an installed patch that you can see on the GUI:
But then it doesn't give you some useful data, e.g. who installed that fix, which can be found in the data read from WMI:
Fortunately, you can get the data from the WUA API and WMI as well and merge them easily in PowerShell.
First things first, getting the list of fixes from WUA and store it in $HFDetails:
$HFobj = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$srv))
$objSearcher= $HFobj.CreateUpdateSearcher()
$allupdates = $objSearcher.GetTotalHistoryCount()
$HFDetails = $objSearcher.QueryHistory(0,$allupdates)
Then get the list of fixes from WMI:
Get-HotFix -ComputerName $srv | %{
Create an object with properties we are interested in and want to record from the two data sets:
$obj = "" | select ComputerName,HotfixID,InstalledOn,InstalledBy,Title,Description
The HotfixID, InstalledOn, InstalledBy fields come from the WMI data so I can add them to my object straight away:
$obj.HotfixID = $_.Hotfixid
$obj.InstalledOn = $_.Installedon
$obj.InstalledBy = $_.Installedby
Let's stop here a bit. There's a way to define a psobject with the list of properties and give them value at the same time, so why would I define it in one line and then add value to each attribute afterwards. The only reason is the order of attributes. When you define a psobject with a hash table, the order it displays the properties afterwards is random:
$obj = New-Object PSObject -Property @{ComputerName=$srv;HotfixID=$_.HotfixID...
Anyway, so we have 2 things we want from the WUA API. The title of the patch and the Description of it, which basically means finding the KB number in the WUA dataset and read the title and the Description:
$obj.Description = ($HFDetails | ?{$_.Title -imatch $obj.HotfixID}).description
$obj.Title = ($HFDetails | ?{$_.Title -imatch $obj.HotfixID}).Title
The full script is below (can be combined with the hotfix comparing script), taking the list of hosts from the pipe and recording the ComputerName as well. Sample output:
Full script:
May the Force...
t
However, the Windows Update Agent (WUA) has an API which can be access from PowerShell via COM (Microsoft.Update.Session) and can be called remotely to get almost all fields of an installed patch that you can see on the GUI:
![]() |
| An installed hotfix shown on the GUI of WUA |
But then it doesn't give you some useful data, e.g. who installed that fix, which can be found in the data read from WMI:
![]() |
| An installed hotfix listed by Get-Hotfix or Win32_QuickFixEngineering |
Fortunately, you can get the data from the WUA API and WMI as well and merge them easily in PowerShell.
First things first, getting the list of fixes from WUA and store it in $HFDetails:
$HFobj = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$srv))
$objSearcher= $HFobj.CreateUpdateSearcher()
$allupdates = $objSearcher.GetTotalHistoryCount()
$HFDetails = $objSearcher.QueryHistory(0,$allupdates)
Then get the list of fixes from WMI:
Get-HotFix -ComputerName $srv | %{
Create an object with properties we are interested in and want to record from the two data sets:
$obj = "" | select ComputerName,HotfixID,InstalledOn,InstalledBy,Title,Description
The HotfixID, InstalledOn, InstalledBy fields come from the WMI data so I can add them to my object straight away:
$obj.HotfixID = $_.Hotfixid
$obj.InstalledOn = $_.Installedon
$obj.InstalledBy = $_.Installedby
Let's stop here a bit. There's a way to define a psobject with the list of properties and give them value at the same time, so why would I define it in one line and then add value to each attribute afterwards. The only reason is the order of attributes. When you define a psobject with a hash table, the order it displays the properties afterwards is random:
$obj = New-Object PSObject -Property @{ComputerName=$srv;HotfixID=$_.HotfixID...
Anyway, so we have 2 things we want from the WUA API. The title of the patch and the Description of it, which basically means finding the KB number in the WUA dataset and read the title and the Description:
$obj.Description = ($HFDetails | ?{$_.Title -imatch $obj.HotfixID}).description
$obj.Title = ($HFDetails | ?{$_.Title -imatch $obj.HotfixID}).Title
The full script is below (can be combined with the hotfix comparing script), taking the list of hosts from the pipe and recording the ComputerName as well. Sample output:
Full script:
$hostlist = @($Input)
foreach($srv in $hostlist){
$HFobj = $HFDetails = $allupdates = $objSearcher = $null
$HFobj = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session",$srv))
$objSearcher= $HFobj.CreateUpdateSearcher()
$allupdates = $objSearcher.GetTotalHistoryCount()
$HFDetails = $objSearcher.QueryHistory(0,$allupdates)
Get-HotFix -ComputerName $srv | %{
$obj = "" | select ComputerName,HotfixID,InstalledOn,InstalledBy,Title,Description
$obj.ComputerName=$srv
$obj.HotfixID = $_.Hotfixid
$obj.InstalledOn = $_.Installedon
$obj.InstalledBy = $_.Installedby
$obj.Description = ($HFDetails | ?{$_.Title -imatch $obj.HotfixID}).description
$obj.Title = ($HFDetails | ?{$_.Title -imatch $obj.HotfixID}).Title
$obj
}
}
May the Force...
t
20 January, 2013
Compare hotfixes on two computers - OS
In an enterprise environment, inevitably, you will find high availability systems. A typical solution to reach a relatively high availability is to setup a cluster. A cluster usually consists of two or more identical computers (nodes), ideally in different physical locations and they are capable of running exactly the same service, some of them have shared disks to be able to run database engines...etc. If one of the nodes in a cluster goes down for whatever reason, another one can pick up the services and run them (a service can be an IP address, a network name, database engine or any custom service which supports clustering).
In the definition of these systems, there's is a very important word: identical nodes. The computers which are part of a cluster of some sort should be very similar. Ideally same hardware model, same operating system, same applications installed, same OS, network, disk...etc configuration. And not least, same hotfixes installed.
Because Microsoft hotfixes are supposed to be installed at least once a month, a quick tool is always helpful which can tell you the differences between the list of installed hotfixes on two computers. You can get the list of Microsoft patches installed to a host with one line:
$hotfixes = gwmi -query "select HotFixID from Win32_quickfixengineering where hotfixID like 'KB%'" -computer $fsrv | select hotfixid
If you want to compare two lists, PowerShell offers you and easy way with Compare-Object, however, to make the output a bit better and more readable, you can feed the data into an object collection and then make it as the output of the script (see compareHotfixes function in the script below).
Example output:
Some of the interesting parts of the script:
function gethotfixes ([string]$fsrv){
writelog 0 "$fsrv, reading hotfixes data from Win32_quickfixengineering......" "nonew"
$hotfixes = gwmi -query "select HotFixID from Win32_quickfixengineering where hotfixid like 'KB%'" -computer $fsrv | select hotfixid
$strHotfixes = $hotfixes | %{$_.hotfixid.tostring()}
writelog 1 "[done]" "extend"
return $strHotfixes
}
This function reads the list of hotfixes from a remote host (has some logging as well) and returns the results in $strHotfixes variable.
#### Function for comparing hotfixes between 2 hosts
function compareHotfixes ($fobjColl, $fullreport){
writelog 0 "Comparing hotfixes between the 2 hosts......" "nonew"
# compare the list of hotfixes from the 2 hosts
if($fullreport) {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500 -IncludeEqual} #we need the equals for the host details output
else {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500}
# going through the output of compare-object's output and feed the data into an object collection
foreach ($c in $comparedHotfixes) {
$fsObj = new-Object -typename System.Object
$hotfixId = $c.InputObject
switch ($c.SideIndicator)
{
"=>" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "Missing"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
}
"<=" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "Missing"
}
"==" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
}
}
if($fsObj.item){
$script:ReturnObjColl += $fsObj
}
}
writelog 1 "[done]" "extend"
}
This is the main part of the script where we take the list of hotfixes on each node (first and second element of the $fobjcoll object collection, which is basically an array of objects and we take the hotfixes property of them: $fobjColl[0].hotfixes, $fobjColl[1].hotfixes.) and then we use the compare-object command to compare the lists. Then there's a bit of data processing with a switch case.
if($hostlistlength -eq 2){
foreach ($srv in $hostlist) {
$sObjHotfixes = new-Object -typename System.Object
$sObjHotfixes | add-Member -memberType noteProperty -name ComputerName -Value $srv
$sObjHotfixes | add-Member -memberType noteProperty -name hotfixes -Value ""
$sObjHotfixes.hotfixes = gethotfixes $srv
$objColl += $sObjHotfixes
}
}
The script checks how any nodes were specified, if 2 then we are good to go. Then we just go through the list and create and object for each which will contain the name of the computer and the hotfixes enumerate from it.
Run it like this:
PS C:\> "host1","host2" | compare-hotfixes.ps1
Clipboard friendly code:
Have a look and let me know what you think.
May the Force...
t
In the definition of these systems, there's is a very important word: identical nodes. The computers which are part of a cluster of some sort should be very similar. Ideally same hardware model, same operating system, same applications installed, same OS, network, disk...etc configuration. And not least, same hotfixes installed.
Because Microsoft hotfixes are supposed to be installed at least once a month, a quick tool is always helpful which can tell you the differences between the list of installed hotfixes on two computers. You can get the list of Microsoft patches installed to a host with one line:
$hotfixes = gwmi -query "select HotFixID from Win32_quickfixengineering where hotfixID like 'KB%'" -computer $fsrv | select hotfixid
If you want to compare two lists, PowerShell offers you and easy way with Compare-Object, however, to make the output a bit better and more readable, you can feed the data into an object collection and then make it as the output of the script (see compareHotfixes function in the script below).
Example output:
Some of the interesting parts of the script:
#### Function for enumerating hotfixes
function gethotfixes ([string]$fsrv){
writelog 0 "$fsrv, reading hotfixes data from Win32_quickfixengineering......" "nonew"
$hotfixes = gwmi -query "select HotFixID from Win32_quickfixengineering where hotfixid like 'KB%'" -computer $fsrv | select hotfixid
$strHotfixes = $hotfixes | %{$_.hotfixid.tostring()}
writelog 1 "[done]" "extend"
return $strHotfixes
}
This function reads the list of hotfixes from a remote host (has some logging as well) and returns the results in $strHotfixes variable.
#### Function for comparing hotfixes between 2 hosts
function compareHotfixes ($fobjColl, $fullreport){
writelog 0 "Comparing hotfixes between the 2 hosts......" "nonew"
# compare the list of hotfixes from the 2 hosts
if($fullreport) {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500 -IncludeEqual} #we need the equals for the host details output
else {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500}
# going through the output of compare-object's output and feed the data into an object collection
foreach ($c in $comparedHotfixes) {
$fsObj = new-Object -typename System.Object
$hotfixId = $c.InputObject
switch ($c.SideIndicator)
{
"=>" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "Missing"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
}
"<=" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "Missing"
}
"==" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
}
}
if($fsObj.item){
$script:ReturnObjColl += $fsObj
}
}
writelog 1 "[done]" "extend"
}
This is the main part of the script where we take the list of hotfixes on each node (first and second element of the $fobjcoll object collection, which is basically an array of objects and we take the hotfixes property of them: $fobjColl[0].hotfixes, $fobjColl[1].hotfixes.) and then we use the compare-object command to compare the lists. Then there's a bit of data processing with a switch case.
if($hostlistlength -eq 2){
foreach ($srv in $hostlist) {
$sObjHotfixes = new-Object -typename System.Object
$sObjHotfixes | add-Member -memberType noteProperty -name ComputerName -Value $srv
$sObjHotfixes | add-Member -memberType noteProperty -name hotfixes -Value ""
$sObjHotfixes.hotfixes = gethotfixes $srv
$objColl += $sObjHotfixes
}
}
The script checks how any nodes were specified, if 2 then we are good to go. Then we just go through the list and create and object for each which will contain the name of the computer and the hotfixes enumerate from it.
Run it like this:
PS C:\> "host1","host2" | compare-hotfixes.ps1
Clipboard friendly code:
param ( [string] $log = "",
[switch] $fullreport = $false)
if (!$log){
$date = get-date -uformat "%Y-%m-%d-%H-%M-%S"
$log = "c:\temp\$scriptname-$date.log"
write-host -foregroundcolor 'yellow' "No logfile is specified, $log will be used."
}
$logfile = New-Item -type file $log -force
##################################################### Functions #####################################################
# ================== Logging and reporting functions ==================
#### Function for creating log entries in a logfile and on standard output
function writeLog ([int]$type, [string]$message, [string]$modifier) { #usage: writeLog 0 "info or error message"
# $modifier: <nonew | extend>
# Value nonew: writes the output the the console and the logfile without carriage return
# Value extend: writes the message to the output without date and
# both values can be used e.g. for writting to the console and logfile and put a status message at the end of the line as a second step
$date = get-date -uformat "%Y/%m/%d %H:%M:%S"
if($modifier -eq "extend"){
switch ($type) {
"0" {$color = "Green"}
"1" {$color = "Yellow"}
"2" {$color = "Red"}
}
}
else{
switch ($type) {
"0" {$message = $date + ", INF, " + $message; $color = "Green"}
"1" {$message = $date + ", WAR, " + $message; $color = "Yellow"}
"2" {$message = $date + ", ERR, " + $message; $color = "Red"}
}
}
if($modifier -eq "nonew"){
write-host $message -ForegroundColor $color -NoNewLine
$bytes = [text.encoding]::ascii.GetBytes($message)
$bytes | add-content $logfile -enc byte
}
else{
write-host $message -ForegroundColor $color
Add-Content $logfile $message
}
}
#### Function for enumerating hotfixes
function gethotfixes ([string]$fsrv){
writelog 0 "$fsrv, reading hotfixes data from Win32_quickfixengineering......" "nonew"
$hotfixes = gwmi -query "select HotFixID from Win32_quickfixengineering where hotfixid like 'KB%'" -computer $fsrv | select hotfixid
$strHotfixes = $hotfixes | %{$_.hotfixid.tostring()}
writelog 1 "[done]" "extend"
return $strHotfixes
}
#### Function for comparing hotfixes between 2 hosts
function compareHotfixes ($fobjColl, $fullreport){
writelog 0 "Comparing hotfixes between the 2 hosts......" "nonew"
# compare the list of hotfixes from the 2 hosts
if($fullreport) {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500 -IncludeEqual} #we need the equals for the host details output
else {$comparedHotfixes = compare-object $fobjColl[0].hotfixes $fobjColl[1].hotfixes -SyncWindow 500}
# going through the output of compare-object's output and feed the data into an object collection
foreach ($c in $comparedHotfixes) {
$fsObj = new-Object -typename System.Object
$hotfixId = $c.InputObject
switch ($c.SideIndicator)
{
"=>" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "Missing"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
}
"<=" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "Missing"
}
"==" {
$fsObj | add-Member -memberType noteProperty -name "Item" -Value $hotfixId
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[0].ComputerName) -Value "OK"
$fsObj | add-Member -memberType noteProperty -name $($fobjColl[1].ComputerName) -Value "OK"
}
}
if($fsObj.item){
$script:ReturnObjColl += $fsObj
}
}
writelog 1 "[done]" "extend"
}
##################################################### Body #####################################################
writelog 0 "Syntax: $($MyInvocation.MyCommand.Path)$($myinvocation.line.substring(($myInvocation.InvocationName).length ))"
writelog 0 "Invoked by: $([Security.Principal.WindowsIdentity]::GetCurrent().Name)"
$hostlist = @($Input)
$objColl = $script:ReturnObjColl = @()
$hostlistlength = $hostlist.length
if($hostlistlength -eq 2){
foreach ($srv in $hostlist) {
$sObjHotfixes = new-Object -typename System.Object
$sObjHotfixes | add-Member -memberType noteProperty -name ComputerName -Value $srv
$sObjHotfixes | add-Member -memberType noteProperty -name hotfixes -Value ""
$sObjHotfixes.hotfixes = gethotfixes $srv
$objColl += $sObjHotfixes
}
}
compareHotfixes $objColl $fullreport
$script:ReturnObjColl
Have a look and let me know what you think.
May the Force...
t
Subscribe to:
Posts (Atom)



