I don't know if anyone else will find this worthwhile, but I had a need to automate some patch installs via WSUS during the guirunonce of an automated build. Relying on automatic updates to do it wasn't timely enough, and I wanted to control the reboots and notify people when everything was done.
I started with guidance from some existing code, but that didn't produce enough feedback to people who were monitoring the process. A batch of 20+ patches can look like a hung script. Plus occaisionally someone may want to bypass the updates for a special test machine. Microsoft provides asynchronous calls in the WUA API, but the instructions for how to use them is as clear as mud. Thanks to my roommate Randy for coming up with the idea of using a dummy .wsc file to trick the api into launching the "begin" methods.
The formatting of the code may look familiar as I started out with Glenn Barnas's WSUS Automation functions. Plus you can find vbscript versions all over Microsoft's script center. I attributed Glenn in the comments, but the similarities end pretty early. As I mentioned the trick is to get an object from a dummy wsc file that implements a simple "Invoke" method with the right parameters and a dispid of 0. If you put code in the invoke method of the wsc file, it will invoke every time Windows Update makes a callback during the Search, Download, and Install. You can use this to do some truely asynchronous stuff, but I just wanted an object that I could poll for a progress percentage counter and that I could use to cancel the job if the user hits a key.
The code below are meant to be 2 separate files. The function block I put in a .udf file and call from my main script. The second block for the wsc xml is in a file called WUACB.WSC in the current dir. That needs to be a separate file, but the location is up to you. Just modify the function code to point to the right place. (Full path to the wsc file is required.)
Here's the function code:
Code:
;;======================================================================
;;
;;FUNCTION GetUpdatesAsync(OPTIONAL $wuInstall, OPTIONAL $verbose)
;;
;;ACTION Download and optionally install windows updates
;;
;;AUTHOR Harry Meier with thanks to Glenn Barnas and Randy Miller
;;
;;History 8/18/07 - ver 1.0 Complete
;;
;;SYNTAX GetUpdatesAsync(Bool Install, Bool Verbose)
;;
;;PARAMETERS Install OPTIONAL, install downloaded updates. Default False
;; verbose OPTIONAL, turn on verbose output. Default False
;;
;;REMARKS Both verbose and nonverbose allow for canceling the process
;; Currently set for managed WSUS server only:
;; Set the ServerSelection on line 89 to 0 to use current machine default
;; or 2 for Internet windows update only
;;
;;RETURNS Array of downloaded updates or Array of installed updates and status
;; If no needed updates found a one item array is returned
;; 0 : 0 - Boolean indicating no install
;; If updates downloaded only, array returns:
;; 0 : 0 - Boolean indicating no install
;; 1+ : string - Remaining entries list the downloaded update titles
;; If installation is performed, array returns:
;; 0 : 1 - Boolean indicating install was performed
;; 1 : string - Installation Result
;; 2 : b - Boolean indicating reboot is required
;; 3+ : string - Remaining entries list the installed update titles
;;
;;DEPENDENCIES WUACB.wsc Windows Script Component file located in the current directory.
;; $=SetOption("ASCII","ON") and $=SetOption("WrapAtEOL","ON") prefered for verbose mode
;;
;;
;;TESTED WITH WXP, W2K3
;;
;;EXAMPLE
/*
$=SetOption("ASCII","ON")
$=SetOption("WrapAtEOL","ON")
ReDim $aTmp
$aTmp = GetUpdatesAsync(1,1)
If UBound($aTmp) > 2
If $aTmp[2]
? "Reboot required!" ?
Sleep 2
Else
? "Reboot not required!" ?
Sleep 2
EndIf
Else
? "Nothing to do" ?
EndIf
*/
Function GetUpdatesAsync(OPTIONAL $wuInstall, OPTIONAL $verbose)
Dim $, $I, $C
ReDim $aTmp[1]
$aTmp[0] = 0
FLUSHKB
If Not $verbose
? "Searching, Downloading, and Installing Updates. This may take some time."
? "Press 'c' Key to Cancel"
EndIf
Dim $wscPath
Dim $objUpdateSession, $objCollection, $objSearcher, $objSearchJob, $objSearchResults, $colUpdates
Dim $objSearchCompletedCallback, $searchState
; Set path to the Windows Script Component file
$wscPath = @CURDIR + "WUACB.WSC"
; Get Dummmy Script Object For Search Callbacks
$objSearchCompletedCallback = GetObject("script:" + $wscPath)
; Initialize an Update Session
$objUpdateSession = CreateObject("Microsoft.Update.Session")
; Create a collection to hold updates
$objCollection = CreateObject("Microsoft.Update.UpdateColl")
; Init a Searcher Object
$objSearcher = $objUpdateSession.CreateupdateSearcher()
; Set Server Selection to 0 for default, 1 for Managed Only, 2 for Internet Only
$objSearcher.ServerSelection = 1
If $verbose
? "Searching for Updates - Press 'c' Key to Cancel" ?
EndIf
; Search for Non-Installed Software Updates
$objSearchJob = $objSearcher.BeginSearch("Type='Software' and IsInstalled=0",
$objSearchCompletedCallback, $searchState)
While $objSearchJob.IsCompleted=False
If $verbose
"."
EndIf
If kbhit()
get $key
if $key = "c"
; Cleanup and quit if cancel button pressed
$objSearchJob.RequestAbort()
$objSearchJob.CleanUp()
$objSearchResults = $objSearcher.EndSearch($objSearchJob)
Return
endif
endif
Sleep 2
Loop
; Finalize Search
$objSearchResults = $objSearcher.EndSearch($objSearchJob)
$colUpdates = $objSearchResults.Updates
$C = $objSearchResults.Updates.Count
; Only continue if search found updates needed
If $C > 0
Dim $objDownloadProgressChangedCallback, $objDownloadCompletedCallback, $downloadState
Dim $objDownloader, $objDownloadJob, $objDownloadResult, $objProgress
Dim $strTotStatus, $strUpdNum, $strUpdPct
Dim $update
; Get Dummmy Script Objects For Download Callbacks
$objDownloadProgressChangedCallback = GetObject("script:" + $wscPath)
$objDownloadCompletedCallback = GetObject("script:" + $wscPath)
; Initialize a Downloader
$objDownloader = $objUpdateSession.CreateUpdateDownloader()
; Tell downloader to get the updates found in the search
$objDownloader.Updates = $colUpdates
; Set download priority to 3 - High, 2 - Normal, 1 - Low
$objDownloader.Priority = 3
If $verbose
? "Downloading Updates - Press 'c' Key to Cancel" ?
EndIf
$objDownloadJob = $objDownloader.BeginDownload($objDownloadProgressChangedCallback,
$objDownloadCompletedCallback, $downloadState)
If $verbose
; Display initial progress of download
$objProgress = $objDownloadJob.GetProgress()
? "Total: " + $objProgress.PercentComplete + "%%"
? "Downloading Update - " + $objDownloadJob.Updates($objProgress.CurrentUpdateIndex).Title
? $objProgress.CurrentUpdatePercentComplete + "%%"
$strTotStatus = $objProgress.PercentComplete
$strUpdNum = $objProgress.CurrentUpdateIndex
$strUpdPct = $objProgress.CurrentUpdatePercentComplete
EndIf
While $objDownloadJob.IsCompleted=False
If kbhit()
get $key
if $key = "c"
; Cleanup and quit if cancel button pressed
$objDownloadJob.RequestAbort()
$objDownloadJob.CleanUp()
$objDownloadResult = $objDownloader.EndDownload($objDownloadJob)
Return
endif
endif
Sleep 2
If $verbose
; Display download progress every 2 seconds if any changes detected
$objProgress = $objDownloadJob.GetProgress()
If $strUpdPct <> $objProgress.CurrentUpdatePercentComplete
$strUpdPct = $objProgress.CurrentUpdatePercentComplete
? $objProgress.CurrentUpdatePercentComplete + "%%"
If $strUpdNum <> $objProgress.CurrentUpdateIndex
If $strTotStatus <> $objProgress.PercentComplete
$strTotStatus = $objProgress.PercentComplete
? "Total: " + $objProgress.PercentComplete + "%%"
EndIf
$strUpdNum = $objProgress.CurrentUpdateIndex
? "Downloading Update - " +
$objDownloadJob.Updates($objProgress.CurrentUpdateIndex).Title
EndIf
EndIf
EndIf
Loop
; Finalize Download
$objDownloadResult = $objDownloader.EndDownload($objDownloadJob)
; build list of downloaded updates in $aTmp
ReDim $aTmp[$C]
$aTmp[0] = 0
If $verbose
? ? "Updates Successfully Downloaded:" ?
EndIf
For $I = 0 To $C - 1
$update = $colUpdates.Item($I)
If $update.IsDownloaded
; Save update names for return array (overwritten later if install is true)
$aTmp[$I + 1] = $Update.Title
If $verbose
; Display List of downloaded updates
" " + $Update.Title ?
EndIf
EndIf
Next
; Perform Install if specified
If $wuInstall
Dim $objInstaller, $objInstallJob, $objInstallResult, $updatesToInstall
Dim $objInstallProgressChangedCallback, $objInstallCompletedCallback, $installState
; Get Dummmy Script Objects For Install Callbacks
$oInstallProgressChangedCallback = GetObject("script:" + $wscPath)
$oInstallCompletedCallback = GetObject("script:" + $wscPath)
ReDim $aTmp[$C + 2]
$aTmp[0] = 1
; create collection of updates to install (only install successfully downloaded updates)
$updatesToInstall = CreateObject("Microsoft.Update.UpdateColl")
For $I = 0 To $C - 1
$update = $colUpdates.Item($I)
If $update.IsDownloaded
$ = $updatesToInstall.Add($update)
EndIf
Next
; Init Installer Object
$objInstaller = $objUpdateSession.CreateUpdateInstaller()
$C = $updatesToInstall.Count
$objInstaller.Updates = $updatesToInstall
If $verbose
? ? "Installing updates - Press 'c' Key to Cancel" ?
EndIf
$objInstallJob = $objInstaller.BeginInstall($objInstallProgressChangedCallback,
$objInstallCompletedCallback, $installState)
sleep 1
If $verbose
; Display initial progress of Install
$objProgress = $objInstallJob.GetProgress()
? "Total: " + $objProgress.PercentComplete + "%%"
? "Installing Update - " +
$objInstallJob.Updates($objProgress.CurrentUpdateIndex).Title
? $objProgress.CurrentUpdatePercentComplete + "%%"
$strTotStatus = $objProgress.PercentComplete
$strUpdNum = $objProgress.CurrentUpdateIndex
$strUpdPct = $objProgress.CurrentUpdatePercentComplete
EndIf
While $objInstallJob.IsCompleted=False
If kbhit()
get $key
if $key = "c"
; Cleanup and quit if cancel button pressed
$objInstallJob.RequestAbort()
$objInstallJob.CleanUp()
$objInstallResult = $objInstaller.EndInstall($objInstallJob)
Return
endif
endif
Sleep 2
If $verbose
; Display installation progress every 2 seconds if any changes detected
$objProgress = $objInstallJob.GetProgress()
If $strUpdPct <> $objProgress.CurrentUpdatePercentComplete
$strUpdPct = $objProgress.CurrentUpdatePercentComplete
? $objProgress.CurrentUpdatePercentComplete + "%%"
If $strUpdNum <> $objProgress.CurrentUpdateIndex
If $strTotStatus <> $objProgress.PercentComplete
$strTotStatus = $objProgress.PercentComplete
? "Total: " + $objProgress.PercentComplete + "%%"
EndIf
$strUpdNum = $objProgress.CurrentUpdateIndex
? "Installing Update - " +
$objInstallJob.Updates($objProgress.CurrentUpdateIndex).Title
EndIf
EndIf
EndIf
Loop
; Finalize installation and get results
$objInstallResult = $objInstaller.EndInstall($objInstallJob)
$aTmp[1] = GetOperationResultCode($objInstallResult.ResultCode)
$aTmp[2] = $objInstallResult.RebootRequired
; Output results of install
If $verbose
? ? "***** Installation Results *****"
? "Installation Result : " + $aTmp[1]
? "Reboot Required : " + Var_Bool($aTmp[2])
? "Installed Updates: (Update Name : Result)"
EndIf
For $I = 0 to $C - 1
If $verbose
? " " + $updatesToInstall.Item($I).Title + ' : ' +
GetOperationResultCode($objInstallResult.GetUpdateResult($I).ResultCode)
EndIf
$aTmp[$I + 3] = $updatesToInstall.Item($I).Title + ':' +
GetOperationResultCode($objInstallResult.GetUpdateResult($I).ResultCode)
Next
EndIf
Else
? "No Updates Needed"
EndIf
Sleep 2
$GetUpdatesAsync = $aTmp
EndFunction
Function GetOperationResultCode($intResultCode)
Dim $tmp
; Interpret result code per Windows Update Agent API
Select
Case $intResultCode = 0
$tmp = "Not Started"
Case $intResultCode = 1
$tmp = "Still In Progress (Cleanup Failed Please Reboot)."
Case $intResultCode = 2
$tmp = "Successful"
Case $intResultCode = 3
$tmp = "Succeeded With Errors (See WindowsUpdate.log for details)"
Case $intResultCode = 4
$tmp = "Failed (See WindowsUpdate.log for details)"
Case $intResultCode = 5
$tmp = "Aborted"
Case 1
$tmp = "Unhandled Result Code"
EndSelect
$GetOperationResultCode = $tmp
EndFunction
Function Var_Bool($variant_bool)
Dim $tmp
; Interpret Variant Boolean Value
Select
Case $variant_bool = -1
$tmp = "True"
Case $variant_bool = 0
$tmp = "False"
Case 1
$tmp = "Unknown"
EndSelect
$Var_Bool = $tmp
EndFunction
And Here's the WSC xml:
Code:
<?xml version="1.0"?>
<component>
<?component error="true" debug="true"?>
<registration
description="WUACB"
progid="WUACB.WSC"
version="1.00"
classid="{8b8b762d-bcfc-49a9-b88c-7b939816ed03}"
>
</registration>
<public>
<method name="Invoke" dispid="0">
</method>
</public>
<script language="VBScript">
<![CDATA[
function Invoke(Job,CallbackArgs)
end function
]]>
</script>
</component>