tucuxi.org: software engineering, photography and troubleshooting

Well, I've been working all morning on a VBScript to automatically install updates in people's logon scripts at work. Unfortunately, this is not easily done without the help of VBScript and the Windows Scripting Host (WSH). Please note that this procedure requires users to have Local Administrator privileges on their workstations. If this is not the case, you are probably better served by using one of the following technologies:

And to answer any emails I might get on those three topics; No, I do not know much about these technologies, do not ask me. Just search for it.

The first step is to setup a repository of hotfixes on the network, accessible to all users. All Windows Hotfixes are now distributed with a filename in the form:

WindowsVVVV-KB######-PPP-LLL.exe
VVVV:   Windows version - XP, 2000 etc.
######: Knowledge Base Article Number
PPP:    Platform/Architecture - Usually x86
LLL:    Language - In my case, ENU

Once we have these hotfixes, they are to be placed into a directory tree, as follows:

Once this is done, we can move onto working with the script. The first step is to check what OS we are running, and what Service Pack. This can be done through WMI, the Windows Management Instrumentation. Since this's being written for the WSH, all we need do is construct a GetObject call, to winmgmts:\\COMPUTERNAME\root\cimv2. Since this script will be executed on the local computer, we can replace COMPUTERNAME with a single period, giving us:

   Set objWMI = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Now that we have our management object, we execute a query using a syntax that's remarkably SQL-like. I haven't looked into WMI enough to tell if it actually is a SQL variant, but it's close enough for my needs. The Win32_OperatingSystem WMI object tells us all we need to know about a Windows box, and can be constructed thusly:

   Set colItems = objWMI.Execquery("SELECT * FROM Win32_OperatingSystem",, 48)

This returns a collection of items, which is rather annoying, given that we're looking at one machine, but easily coded around. Simply use a For each ... loop, and grab out what we need:

   For Each objItem in colItems
      verBig = Left(objItem.Version, 3)
      ServPack = objItem.ServicePackMajorVersion
      OS = objItem.OSType
   Next

Now all that's left to do is grab out the OS Type and version, and return accordingly. The OS types for Win95 and Win98 are defined in the WMI documentation for Win32_OperatingSystem, and are 16 and 17 respectively. NT-based Windows are a little more difficult to find, but solved with a Select Case statement:

   Select Case OS
      Case WMI_OS_NT
         Select Case verBig
            Case "5.0"
               OSType = osWin2000
            Case "5.1"
               OSType = osWinXP
            Case "5.2"
               OSType = osWin2003
            Case "4.0"
               OSType = osWinNT4
               ServPack = 0
      End Select
.......

You may note that the Service Pack is set to 0 for Windows NT4, 95 and 98. This is due to a limitation of the WMI objects, which return a NULL service pack for these OSes. And, hell, if you're using this script to manage NT4 systems, you should probably be running SP6a anyways. Just chuck the files under winnt4\sp0 in the directory hierarchy, if need be.

Once we have retrieved OS and Service Pack, we can then move onto the meat of the script.. installing hotfixes. The basic switches being used for hotfix installation are /z /q, and correspond to 'don't reboot', and 'quiet mode'. Unfortunately, Microsoft has not standardised the unattended installation switches for hotfixes, and these differ between 2000 and XP/2003. We use /z /q /m for Win2k; /z /q /u for WinXP/20003, and /z /q for all others.

We then look through the filesystem of the hotfixes for appropriate .exe files to install. If the filename matches the regular expression (from the function getHotfixID), then it is executed.

One item of note here is the use of Wscript.echo in various places for status updates - if using the 'wscript' interpreter, many a message box will be displayed. This is bad. Use the following commandline to execute the script:

cscript //Nologo \\dc\sysvol\domain.net.au\scripts\wu.vbs

This will use the 'cscript' interpreter, which displays all messages to stdout. A much better alternative to bombarding users with irrelevant messages.

Now that we have identified the updates to install, we check whether they are already installed, using the registry key

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\
  Uninstall\KB######\UninstallString

Using the WScript.Shell.RegRead() method, we check for the presence of an Uninstall application. This will detect all hotfixes that have been installed normally, but will not detect slipstreamed installs. This is not important here, but it is one caveat to note. Assuming the hotfix has not been installed, we now install it, and increment the nInstalled counter.

wshShell.run Hotfix.path & " " & Switches, 7, True

The 7 corresponds to a 'minimise and keep current focus' setting in WScript.Shell.Run(), and ensures that users are not bothered by updates. The third parameter specifies whether we should wait for a hotfix to complete installation before proceeding with the rest of the script. It's definitely a good idea to wait.

Now that all updates have been installed, if there has been more than one update installed, we run 'qchain'. This ensures that updates are performed safely, without worry of older hotfixes breaking newer ones. You can get qchain from Microsoft; just look for KB296861. Place qchain in the appropriate spot as indicated above.

If you have any questions, feel free to email the author.

The Script

' *******************
' * Windows Updates *
' *******************

Option Explicit

Const osUnknown = 0
Const osWin2000 = 1
Const osWinXP   = 2
Const osWin2003 = 3
Const osWinNT4  = 4
Const osWin95   = 5
Const osWin98   = 6
Const WMI_OS_95 = 16
Const WMI_OS_98 = 17
Const WMI_OS_NT = 18
Const TEMP_FOLDER = 2

Function getWinVer()
   Dim objWMI, objItem, colItems
   Dim strComputer, verBig, OS, OSType, ServPack

   ' Localhost.
   strComputer = "."

   Set objWMI = GetObject("winmgmts:\\" & strComputer &"\root\cimv2")
   Set colItems = objWMI.Execquery("SELECT * FROM Win32_OperatingSystem",, 48)
   Set objWMI = Nothing

   For Each objItem in colItems
      verBig = Left(objItem.Version, 3)
      ServPack = objItem.ServicePackMajorVersion 
      OS = objItem.OSType      
   Next

   Set colItems = Nothing

   OSType = osUnknown

   Select Case OS
      Case WMI_OS_NT
         Select Case verBig
            Case "5.0"
               OSType = osWin2000
            Case "5.1"
               OSType = osWinXP
            Case "5.2"
               OSType = osWin2003
            Case "4.0"
               OSType = osWinNT4
               ServPack = 0
         End Select

      Case WMI_OS_95
         OSType = osWin95
         ServPack = 0

      Case WMI_OS_98
         OSType = osWin98
         ServPack = 0
   End Select

   getWinVer = OSType
End Function

Function getServicePack()
   Dim objWMI, objItem, colItems
   Dim strComputer, verBig, OS, OSType, ServPack

   ' Localhost.
   strComputer = "."

   Set objWMI = GetObject("winmgmts:\\" & strComputer &"\root\cimv2")
   Set colItems = objWMI.Execquery("SELECT * FROM Win32_OperatingSystem",, 48)
   Set objWMI = Nothing

   For Each objItem in colItems
      verBig = Left(objItem.Version, 3)
      ServPack = objItem.ServicePackMajorVersion 
      OS = objItem.OSType      
   Next

   Set colItems = Nothing

   OSType = osUnknown

   Select Case OS
      Case WMI_OS_NT
         Select Case verBig
            Case "5.0"
               OSType = osWin2000
            Case "5.1"
               OSType = osWinXP
            Case "5.2"
               OSType = osWin2003
            Case "4.0"
               OSType = osWinNT4
               ServPack = 0
         End Select

      Case WMI_OS_95
         OSType = osWin95
         ServPack = 0

      Case WMI_OS_98
         OSType = osWin98
         ServPack = 0
   End Select

   getServicePack = ServPack
End Function

Function getHotfixID(HotfixFile)
   Dim regex, matches, n, match

   Set regex = new RegExp

   regex.IgnoreCase = True
   regex.Pattern = "^(|.+-)(KB|Q)([0-9]+)(-[^.]+|).exe$"

   getHotfixID = ""

   if regex.test(HotfixFile) then
      Set matches = regex.Execute(HotfixFile)
      Set match = matches(0)
      getHotfixID = match.SubMatches(2)
   end if
End Function

Function isHotfixInstalled(hotfixID, installed)
   Dim x

   isHotfixInstalled = false

   for x = 0 to ubound(installed)
      if installed(x) = hotfixID then
         isHotfixInstalled = true
      end if
   next
End Function

Function getMachineName() 
   Dim wshNet

   Set wshNet = CreateObject("WScript.Network")

   getMachineName = wshNet.ComputerName

   Set wshNet = Nothing
End Function

Function QFECheck(hotfixPath)
   Dim fso, wshShell, temp, file, l, regex, matches, match
   Dim installed, instOld, x

   installed = Array()

   Set fso = CreateObject("Scripting.FileSystemObject")
   Set wshShell = CreateObject("WScript.Shell")
   Set regex = new RegExp

   regex.pattern = "^(Q|KB)([0-9]+).*Current"
   regex.IgnoreCase = true

   temp = fso.GetSpecialFolder(TEMP_FOLDER)

   if fso.fileExists(hotfixPath & "\qfecheck.exe") then
      wshShell.run """" & hotfixPath & "\qfecheck.exe"" ""/l:" & temp & """", 7, True
      if fso.fileExists(temp & "\" & getmachinename() & ".log") then
         Set file = fso.OpenTextFile(temp & "\" & getmachinename() & ".log", 1, False, 0)
         
         While not file.AtEndOfStream
            l = file.readline
            if regex.test(l) then
               Set matches = regex.execute(l)
               Set match = matches(0)
               WScript.echo "lb: " & lbound(installed) & " ub: " & ubound(installed) & " " & match.submatches(1)
               instOld = installed
               Redim installed(ubound(installed)+1)
               for x = lbound(instOld) to ubound(instOld)
                  installed(x) = instOld(x)
               next
               installed(ubound(installed)) = match.submatches(1)
            end if
         wend

         Set file = Nothing
         fso.DeleteFile temp & "\" & getmachinename() & ".log"
      else
         WScript.echo "Could not load qfecheck log"
      end if
   else
      WScript.echo "Cannot find qfecheck"
      WScript.quit 0
   end if

   QFECheck = installed
End Function

Sub displayUpdates(inst)
   Dim s, x

   s = "Installed updates:"
   for x = lbound(inst) to ubound(inst)
      s = s & " " & inst(x)
   next   

   Wscript.echo s

End Sub

' *********************************
' * Body of the code starts here. *
' *********************************

Dim Switches, HotfixFolder, InstalledUpdates
Dim fso, fixFolder, hotfix, wshShell, hfixID, nInstalled

' Objects used in the script

Set fso = CreateObject("Scripting.FileSystemObject")
Set wshShell = CreateObject("WScript.Shell")

' Base setup for hotfixes
Switches = "/z /q"
HotfixFolder = "\\server\Software\Microsoft\Hotfixes"
nInstalled = 0

' Which OS are we running?

Select Case getWinVer()
   Case osWinNT4
      HotfixFolder = HotfixFolder & "\winnt4"

   Case osWin2000
      Switches = Switches & " /m"
      HotfixFolder = HotfixFolder & "\win2000"

   Case osWinXP
      Switches = Switches & " /u"
      HotfixFolder = HotfixFolder & "\winxp"

   Case osWin2003
      Switches = Switches & " /u"
      HotfixFolder = HotfixFolder & "\win2003"

End Select

HotfixFolder = HotfixFolder & "\sp" & getServicePack()

WScript.echo "We are installing hotfixes from " & HotfixFolder

' Run qfecheck
installedUpdates = QFECheck(hotfixFolder)

displayUpdates installedUpdates

if fso.FolderExists(HotfixFolder) then
   WScript.echo "Parsing hotfixes... "

   Set fixFolder = fso.GetFolder(HotfixFolder)

   For each hotfix in fixFolder.Files
      if (lcase(hotfix.name) <> "qchain.exe") then
      if (lcase(hotfix.name) <> "qfecheck.exe") then
         if lcase(right(hotfix.name, 4)) = ".exe" then
            hfixID = gethotfixid(hotfix.name)
            if hfixID <> "" then
               if isHotfixInstalled(hfixID, installedUpdates) then
                  WScript.Echo "Not Installing " & hfixID & ": Already installed"
               else
                  WScript.Echo "Installing " & hfixID & "..."
                  wshShell.run Hotfix.path & " " & Switches, 7, True
                  nInstalled = nInstalled + 1
               end if
            end if
         end if
      end if
      end if
   Next

   Set fixFolder = Nothing

   if nInstalled > 1 then
      if fso.FileExists(HotfixFolder & "\qchain.exe") then
         WScript.echo "Running qchain..."
         wshShell.run HotfixFolder & "\qchain.exe", 7, True
      end if
   end if
else
   WScript.echo "Sorry, no hotfixes today."
end if

Set wshShell = Nothing
Set fso = Nothing

tucuxi.org content is copyright © 2001-2007.

social network