Inventorying Windows XP Service Packs - Part 3 - Scripting the Rollout

By The Microsoft Scripting Guys

Doctor Scripto at work

Doctor Scripto's Script Shop talks about practical system administration scripting problems, often from the experiences of our readers, and develops scripts to solve them. Most of our examples in the Script Repository perform one simple task; in this column, by contrast, we'll put those pieces together into more complex scripts. They won't be comprehensive or bullet-proof, but they will show how to build scripts from reusable code modules, handle errors and return codes, get input and output from different sources, run against multiple machines, and do other things you might want to do in your production scripts.

We hope you will find these columns and scripts useful – please let us know what you think of them, what solutions you've come up with for these problems, and what you'd like to see covered in the future.

This column is the last of a series of three, but far from the last of Doctor Scripto's Script Shop. You may find it helpful to start by reading the first two columns in the series:

(Note: Many thanks to Patrick Lanfear and Tom Yaguchi for Dr. Scripto's new look.)

On This Page

From Inventorying To Deploying: Scripting the Rollout
Scripting to the Rescue
Preparing To Install Service Pack 2
Creating a Network Share for Installation Files
Running the Installer
The Play-By-Play
For more information

From Inventorying To Deploying: Scripting the Rollout

What if, just what if, after taking an inventory of Windows XP service packs on your network, you could actually install Service Pack 2 on all the clients where it wasn't yet installed? Why, you'd have to be crazy to even dream of scripting something like that! The Gordian intricacies involved and the technical wizardry demanded make even grizzled veterans of many IT campaigns blanch and turn away in horror. Merely contemplating such a feat would give the bravest systems engineer a case of the night sweats.

And yet … And yet you've come so far already on this winding road of WMI and ADSI and ADO. What if you could use what you've learned so far to finish the job? What if …

Dr. Scripto, as usual, has been letting his mind drift towards the unthinkable. Not surprisingly, he started writing a wee bit of code, just to see what would be involved. And somehow that "wee bit" morphed into a fleet of gargantuan SUVs (sport utility VBScripts) that hog all the parking spaces in c:\scripts and keep crashing Visual Notepad. (Note: If you're now searching the Web for Visual Notepad, could we also interest you in a used bridge in excellent condition?) It's hard to tell exactly how it's affecting him, but dark circles have appeared under his eyes, which were already dark circles, and we catch him staring blankly into space more often now.

Let's be brutally frank here: if you have Systems Management Server (SMS) or Windows Server Update Services (WSUS) available, use it! If your organization uses Group Policy, go for it and don't look back! These are Microsoft's recommended solutions for deploying updates like Service Pack 2. They were designed and optimized to solve exactly this sort of problem.

But what if SMS, WSUS and Group Policy are not options?

Scripting to the Rescue

Scripting, as Dr. Scripto likes to say, is a lot like duct tape. It ain't pretty or elegant, but it sure comes in handy sometimes when you need to stick things together.

Dr. Scripto has been so wrapped up in his coding that we didn't have the heart to take the wind out of his sails. But in fact, it's no secret that it is entirely possible to use scripts to install Windows XP Service Pack 2 on multiple machines. Two related documents with code samples already exist out there explaining a couple of methods that you can use:

The Application Compatibility Guide sample scripts use mainly batch files and the Windows Script Host object model to accomplish this task, while the Supplemental Scripts use mainly the Windows Firewall object model and WMI. Both these methods are complex, and involve rebooting the remote machine twice. So in this column we decided to show you another, simpler, method, which can also be used for remote installation of other applications.

Here's the nut of the problem: if you install SP2 with the default settings, you won't be able to run scripts or tools against it remotely. In the SP2 default settings, Windows Firewall is turned on and the firewall's default settings lock out all attempts at remote administration using Remote Procedure Call (RPC), Distributed Component Object Model (DCOM), or named pipes.

To understand how Windows Firewall blocks remote administration and the Catch-22 this can create for system administrators who automate their networks with scripts, you might want to start by reading another Scripting Guys column, I Married Bigfoot. Oh: and Service Pack 2 Made My Computers Disappear. Never say that the Scripting Guys do not provide one-stop shopping for all your Service Pack 2 scripting needs.

Where were we before that shameless bit of self-promotion? Oh, yes, your installation script needs to find a way to change the default firewall settings, either before or after installation.

Preparing To Install Service Pack 2

The Application Compatibility Guide sample scripts and the Supplemental Scripts all run the unmodified download executable for SP2, and then change the settings after installation. This solution uses an ingenious algorithm, but it requires jumping through some hoops. Unfortunately, the Scripting Guys are a little stiff at the joints and our hoop-jumping is kind of pathetic (we pulled a lot of muscles writing those Supplemental Scripts). So we looked around for a less athletic approach.

Fortunately, there are other solutions. One possibility is changing the firewall settings by extracting the contents of the SP2 download to a network share on a file server and modifying a firewall configuration file included with the download. Then the customized bits on this share can be used for installation to each remote machine. For details on how to do this, see Scenario 2 in the Guide for Installing and Deploying Microsoft® Windows® XP Service Pack 2 and Deploying Windows Firewall Settings for Microsoft Windows XP with Service Pack 2 (WF_XPSP2.doc). The only difficulty with this approach is that after changing the configuration file, you must digitally code-sign it or the update executable will fail. For organizations that do not do much code-signing, this could be a bit of a hassle.

We settled on a method that seems to work regardless of whether or not you extract the installation files prior to running the update. Turns out you can configure the Windows Firewall registry settings with a script prior to installing SP2, to enable remote administration or disable the firewall, and the installation does not overwrite the settings.

Another issue: there are pros and cons to running the SP2 executable from a central share versus copying it to each remote box and running it locally. The executable file is a heavyweight, tipping the scales at 266 megabytes, and can generate significant network traffic either way. The hard disk space required on the clients, though, is substantially less when a central share is used. For installing from a central network share (as the script in this column does), figure a little over a gigabyte of peak disk usage during installation on the target computer; for installing from a local executable, a bit more than 1.5 gigabytes. For more details, see Microsoft Knowledge Base article 837783, "The hard disk space requirements for Windows XP Service Pack 2."

Another factor to consider is the likelihood that all client computers that need to be upgraded will be online when the installation is performed. If many of the clients are laptops intermittently connected to the network, you may want to copy the installation executable out to each computer when they are connected and run it locally on each computer. We're not going to show how to script that in this column, but the Application Compatibility Guide and Supplemental Scripts take this approach in Scenario 2. Guide for Installing and Deploying Microsoft® Windows® XP Service Pack 2 also describes how to do this manually in "Scenario 1: Installing the service pack so that computers use local service pack source files."

For this column, we're going to rely on what the Guide for Installing and Deploying calls "Scenario 2: Installing the service pack so that computers use shared, remote, service pack source files." This scenario describes the manual steps for this method.

The script in this column assumes, furthermore, that the clients on which you're installing Service Pack 2 are already running Windows XP, which is known as a standalone installation. If you want to integrate Windows XP and Service Pack 2 installation, installing them at the same time on computers not yet running Windows XP, see "The Integrated Installation" in the Guide for Installing and Deploying.

While we're at it, let's not forget the human engineering important in any deployment of a service pack, scripted or not:

  • Perform the updates at night or at a time when the fewest users are expected to be working on their computers.

  • Notify users in advance that they must not be working on their computers at install time because the installation will run automatically, forcing shutdown of all applications and then restarting the computer when it's done.

  • Require users to save all work and back up their files before the update (or run your regular backup program for each machine before beginning the update).

  • Whether or not you temporarily disable anti-virus software is a decision you must make based on the configuration of your network. If virus scanning is enabled, it can add up to an hour to the installation time on older machines.

Creating a Network Share for Installation Files

Before we dig into the script code, we have a little more preparatory work to do. To prepare the Service Pack 2 installation, we need to set up a shared distribution folder on the administrative workstation or a file server that will be permanently available. The share can be on the same machine as the script. But it may be more efficient to set up a separate file server specifically for this purpose if many machines are to be updated. In this script, we've called the share \\<server>\xpsp2\, but you can call it whatever you want as long as you also modify the path in the script.

This share must be permanent to ensure that all of the files a client might need to replace in the future remain available.

If you move the shared distribution folder, see the Microsoft Knowledge Base article "Files and Folders Are Added to Your System After Service Pack Is Installed" for more information.

Once we've created this share, we need to place the Service Pack 2 installer, Xpsp2.exe or WindowsXP-KB835935-SP2-ENU.exe, onto it.

For full details, see Scenario 2 of Guide for Installing and Deploying Microsoft® Windows® XP Service Pack 2.

Running the Installer

The download file is named WindowsXP-KB835935-SP2-ENU.exe (U.S. English version) while the same file on the SP2 CD is named Xpsp2.exe. We will use the latter filename, but this discussion applies equally to both.

You could run Xpsp2.exe without arguments to manually update individual computers with the default settings, but users would have to respond to installation dialog boxes. Here we want to do a scripted, unattended installation on remote clients. Although it's best to run the script at a time when the fewest users are logged on, we still want the installation to run silently so the user interface is hidden on the client, and to close any open applications. At the end of the installation, we want to make sure that the computer is rebooted so that Service Pack 2 takes effect.

To meet these requirements, one possibility would be to use the following command-line switches with Xpsp2.exe:

xpsp2.exe /q /f /forcerestart

/q - Uses quiet mode (the same as unattended mode, but with the user interface hidden from view). If you use this option, no prompts appear on screen during the installation process.

/f - Forces other apps to close at shutdown.

/forcerestart - Restarts the computer after the installation is completed.

This is the command line the script uses. Incidentally, if you are unpacking the files to a share and changing and signing the firewall configuration file, these switches also work for Update.exe, the executable you would run in this case.

You may want to use different combinations of switches, depending on the installation environment. The section "Command-line Options for XPsp2.exe and Update.exe" in Guide for Installing and Deploying Microsoft® Windows® XP Service Pack 2 lists all the available command-line options.

The Play-By-Play

Before we reveal Dr. Scripto's latest masterpiece, we want to whisper a little advice: RUN THIS ON A TEST NETWORK FIRST!!! Shocking as it may be, the Scripting Guys can't guarantee that what ran in our testbed will work like a charm the first time on your production network. As a matter of fact, you can't run this script at all without changing some values in the change block at the beginning of the script (and in the hosts.txt input file, if you use it). REPEAT: DON'T RUN ANYTHING ON YOUR PRODUCTION NETWORK UNTIL YOU'VE TESTED IT THOROUGHLY!!!

Why are we disturbing the decorum of an otherwise sedate web page with all those capital letters? Because we care deeply about our readers and believe that learning to script should earn them big raises, not get them fired. Networks are a little like snowflakes: there are probably no two identical ones out there. (I know, I know, they're not really very much like snowflakes.) As brilliant as the Scripting Guys can be on their lucid days, they can't anticipate all the possible permutations of network configurations out there. We want you to trust our advice, but not too much: as they used to say about arms control during the Cold War, "Trust, but verify."

That was the bad news. Here's the good news: some of the functions in this script, with minor modifications, can be used to remotely install or run other applications, not just Windows XP Service Pack 2. So as you work with the components of this script, you're building up a library of reusable code.

Oh, sorry, here's a little more bad news, but not too bad. This script is only one example of how you might want to automate the SP2 installation process. We're mentioning alternatives along the way and, depending on how you run your network, you will probably want to try some of them. The example we're proposing is just a template, a runway from which your creativity can take off and soar. And no, as a matter of fact we're not from Marketing, we just write that way.

As with all the scripts in this column and most of the scripts on the TechNet Script Center, this script must be run under Cscript.exe, the command-line script host. You can run:

cscript xpsp2-deploy.vbs

To set Cscript as the default on your machine, eliminate the logo lines when each script runs, and save these settings, you can run:

wscript \\h:cscript \\nologo \\s

Main body of script

This script begins with a choice: you can get input to the script either by reading a list of computers from a text file (the computers you want to update to SP2) or by using ADO to find the Windows XP clients that are not yet running SP2. You can choose the method you want to use by flipping the value of a variable (blnAD) to True or False in the change block at the top of the script.

If blnAD is set to False, be sure the name and path of the text file match the string assigned to strInputFile. The text file must contain a list of names of computers, one name per line, that are accessible on the network, running Windows XP and ready to be updated with Service Pack 2.

If blnAD is set to True, be sure that strContainer is assigned the name of the Active Directory container that you want to search for Windows XP machines not yet updated to Service Pack 2.

All the other variables that must be set to work on a particular network or computer are contained within the change block. Be sure to change these to appropriate values for your workstation and network before running the script.

The values for strInputFile and strOutputFile in the sample script assume that you have created a directory, c:\scripts\xpsp2. Of course, you can change the paths and names of either file if you wish.

You must also set the time variable, strHrMin, to an appropriate time to schedule the updates. The time parameter for the Create method of Win32_ScheduledJob uses a 24-hour clock for the characters of the string that refer to the time. So in the example for strHrMin in the script, "1637" resolves to 16:37 hours or 4:37 p.m. Naturally, you will most likely want to set this to a time late at night when few users are likely to be logged on.

With a little string manipulation, the script could be modified to stagger the installation times for each machine by incrementing strHrMin for each loop through the list of machines. This would ease the load on the file server from which the installations are run. You could even have the installations spill over from one day to the next, although this would involve manipulating another parameter of the Create method not used in this example.

The logic in the main body of the script loops through the array of computer names obtained from the text file or Active Directory container. On each computer, it:

  • Pings the host to see if it is online. If the ping is successful, it …

  • Attempts to connect to WMI on the machine. If it can bind to WMI, it …

  • Checks to make sure there is sufficient free disk space for the upgrade. If there is enough free disk space, it …

  • Creates new registry subkeys and writes entries and values to them that will enable remote administration on the Windows Firewall after installation. If the registry is successfully modified, it …

  • Runs Xpsp2.exe with the appropriate command-line switches from the central file share.

The script outputs the results to the display and also logs them to a comma-separated values (CSV) log file. To build the string which is finally passed to the sub-routine that writes the string to the log file, the script uses code like the following line, which takes the contents of strData and appends another literal string onto it:

strData = strData & "ERROR: Ping failed"

In Part 2 of this series, we talked about using global variables to make data available to procedures to read and change. This script takes a different tack: it uses unique variable names with local scope in the main body of the script and in each function or subroutine. All data must therefore be passed as parameters to procedures, and the functions must return data to the main body of the script. This method of passing data to and from procedures makes clearer what data is being sent to and returned from each part of the script.

GetADList function

This function takes the name of an Active Directory container as a parameter and returns an array of the names of machines to be updated. It is very similar to the function in the simpler ADO script in the section "Much ADO About Searching Active Directory" of Inventorying Windows XP Service Packs – Part 2.

ReadTextFile function

This is very similar to the function of the same name in Inventorying Windows XP Service Packs – Part 1. It takes the name of a text file containing a list of computers to be updated to Service Pack 2 and returns an array of the computer names.

PingHost function

To determine whether a computer is accessible on the network before trying to connect to WMI and perform a task on it, the PingHost function uses the Exec method of the Windows Script Host Shell object to call Ping.exe. VBScript provides string-handling functions that enable the PingHost function to parse the resulting StdOut stream to see if the remote computer responded. PingHost returns 0 if the ping succeeded, 1 if it failed, and 2 if an error occurred calling the Exec method.

CheckFreeDiskSpace function

CheckFreeDiskSpace uses the FreeSpace property of the Win32_LogicalDisk class to ensure that enough space is free on the hard disk to install Service Pack 2. If you find that a value other than one gigabyte is more accurate for the peak disk space used during installation, you can modify the value of the SP2_FREE_SPACE constant.

This function assumes that drive C: is the system drive on all clients. If any of the machines to be updated has a system drive other than C:, you could add a function that checks for the system drive and returns the drive letter, which could then be used by CheckFreeDiskSpace. You can get that information from the SystemDrive property of the Win32_OperatingSystem class.

If this function finds enough free disk space to install Service Pack 2, it returns 0; if disk space is insufficient, the function returns 1. If an error is encountered in querying the Win32_LogicalDisk class, the function returns 2.

EnableRemoteAdmin function

Next, we need to customize the installation by configuring Windows Firewall registry settings so that after installation the machine will continue to allow you to run scripts remotely against it.

In this script, we'll take advantage of the WMI class StdRegProv to create and configure these settings in the registry prior to installing Service Pack 2. The installation executable respects the settings for remote administration that it finds in the registry.

We pass EnableRemoteAdmin the name of the machine on which to change the settings. We must do this because the function has to bind to WMI on each machine in order to use the StdRegProv class. Although we have already connected to WMI's root\cimv2 namespace, StdRegProv is in the root\default namespace so we need to do it again.

EnableRemoteAdmin creates the two required subkeys for the DomainProfile and StandardProfile settings with the CreateKey method. It's necessary to create the subkeys because before Service Pack 2 is installed they do not yet exist in the registry. Windows Firewall has two sets of settings: Domain for when the computer is connected to its home domain, Standard for when it's not. If the subkeys are successfully created, then EnableRemoteAdmin sets the value for the Enabled entry to 1 and the RemoteAddresses entry to "*" using the SetDWORDValue and SetStringValue methods, respectively. These settings enable remote administration on all addresses for both profiles.

Here's an example of the code for the DomainProfile:

blnCreateSubKey1 = objReg.CreateKey(HKLM, strKeyPath1)
  If blnCreateSubKey1 = 0 Then
    intReturn1 = objReg.SetDWORDValue(HKLM, strKeyPath1, _
     strEntryName1, intValue1)
    If intReturn1 <> 0 Then
      WScript.Echo "  ERROR: Unable to set " & strEntryName1
      EnableRemoteAdmin = 1
      Exit Function
    End If
    intReturn2 = objReg.SetStringValue(HKLM, strKeyPath1, _
     strEntryName2, strValue2)
    If intReturn2 <> 0 Then
      WScript.Echo "  ERROR: Unable to set " & strEntryName2
      EnableRemoteAdmin = 1
      Exit Function
    End If
  Else
    WScript.Echo "  ERROR: Unable to create registry subkey:" & VbCrLf & _
     "HKLM" & " " & strKeyPath1
    EnableRemoteAdmin = 1
    Exit Function
  End If

If you wanted to disable the firewall by default, you could change the entries and values to those that set the firewall mode to disabled. See Deploying Windows Firewall Settings for Microsoft Windows XP with Service Pack 2 (WF_XPSP2.doc) for details.

If EnableRemoteAdmin is successful it returns 0; otherwise, it returns 1.

ScheduleJob function

If all the preliminary verifications have passed successfully, this function schedules a task on each local machine that runs the Service Pack 2 installation executable from a remote share, using the Create method of Win32_ScheduledJob.

Here is the code that creates the job:

Set objNewJob = objWMIService.Get("Win32_ScheduledJob")
intCreated = objNewJob.Create(strCL, strTime, , , , , JobID)

ScheduleJob takes as in parameters two string variables:

  • The first, strCL, contains the command-line string to run Xpsp2.exe, including the path to the executable and arguments to run it unattended.

  • The second, strTime, contains a string representing the time at which to run the job.

Other parameters not passed in here can specify the day or days on which to run the job and recurrence.

If it succeeds in creating a job, the Create method passes an out parameter back to the function containing the job ID of the scheduled task that has been created. ScheduleJob returns either the job ID of the Xpsp2.exe process in the case of success, or -1 in the case of failure.

HandleError function

The HandleError function takes no parameters because it can access VBScript Err objects created elsewhere in the script. It constructs a string from the Number, Source, and Description properties of this object and passes the string back as the return value. This function is similar to the one in the main script in Inventorying Windows XP Service Packs – Part 2.

WriteCSVFile subroutine

WriteCSVFile takes two parameters, the name of the file to which to write the script output, and a string containing information about the installation on each machine. If the installation job was successfully created, the string contains the job ID; if not, it contains an error message.

This function uses the FileSystemObject of Script Runtime to open and write the data to a comma-separated values (CSV) file, which can be opened in a spreadsheet.

Hosts.txt file

The input file read by the ReadTextFile function is simply a text file with the name of an accessible computer to be updated to Service Pack 2 on each line. Remember that, as with the Active Directory container for the GetADList function, you must have administrative privileges on all hosts.

client1
client2
client3
client4

Script code

OK, take a deep breath now. Don't panic: many Russian novels are longer than this script. Get another cup of coffee, tea, chai or cha and get comfortable. Here it is:

'Deploy Windows XP Service Pack 2 on multiple clients.

'******************************************************************************
'Change block: Change these values to reflect local machine and network.
'Set blnAD to True to run against an Active Directory Container.
'Set blnAD to False to run against a list in a text file.
blnAD = False
'If blnAD = True, must specify
strContainer = "dc=na,dc=fabrikam,dc=com"
'If blnAD = False, must specify text file with names of XP clients to upgrade.
strInputFile = "c:\scripts\xpsp2\hosts.txt"
'UNC path to share with Service Pack 2 executable followed by switches
'If using xpsp2.exe or WindowsXP-KB835935-SP2-ENU.exe on a share.
strCmdLine = "\\server\xpsp2\xpsp2.exe /q /f /forcerestart"
'If using extracted installation files on a share.
'strCmdLine = "\\server\xpsp2\i386\update\update.exe /q /f /forcerestart"
'Time (24-hour clock) today to run first scheduled job.
strHrMin = "1637"
strRunTime = "********" & strHrMin & "00.000000-420"
'Add date to name of log file.
strDate = Replace(Date, "/", "-")
strOutputFile = "c:\scripts\xpsp2\xpsp2-" & strDate & ".csv"
'******************************************************************************

On Error Resume Next

strData = ""
'Get list of XP clients in container to update to SP2.
If blnAD = True Then
  arrHosts = GetADList(strContainer)
Else
  arrHosts = ReadTextFile(strInputFile)
End If

'Loop through list of hosts.
For Each strHost in arrHosts

  WScript.Echo VbCrLf & "Host Name: " & strHost
  strData = strData & VbCrLf & strHost & ","

'Ping client to check connection.
  intPing = PingHost(strHost)
  If intPing = 0 Then
'Connect to WMI on client.
    Set objWMIService = GetObject("winmgmts:" _
     & "{impersonationLevel=Impersonate}!\\" & strHost & "\root\cimv2")
    If Err = 0 Then
'Check for sufficient disk space on target machine for SP2.
      intFree = CheckFreeDiskSpace()
      If intFree = 0 Then
'Enable remote administration.
        intEnabled = EnableRemoteAdmin(strHost)
        If intEnabled = 0 Then
'Run SP2 installation.
          intJobID = ScheduleJob(strCmdLine, strRunTime)
          If intJobID = -1 Then
            strData = strData & "ERROR: Unable to create job."
          Else
            strData = strData & "SUCCESS: Job" & _
             intJobID & " created."
          End If
        Else
          strData = strData & "ERROR: Unable to enable remote " & _
           "administration."
        End If
      ElseIf intFree = 1 Then
        strData = strData & "ERROR: Insufficient free disk space."
      Else
        strData = strData & "ERROR: Unable to determine free disk space."
      End If
    Else
      WScript.Echo "  ERROR: Unable to connect to WMI provider on host."
      strMessage = HandleError
      strData = strData & "ERROR: Unable to connect to WMI provider " & _
       "on host." & strMessage
    End If
  ElseIf intPing = 1 Then
    WScript.Echo "  ERROR: Ping failed."
    strData = strData & "ERROR: Ping failed"
  Else
    strData = strData & "ERROR: Unable to execute ping."
  End If

Next

strData = strData & VbCrLf

'Open output .csv file and write results to it.
WriteCSVFile strOutputFile, strData
WScript.Echo VbCrLf & "Data written to " & strOutputFile

'******************************************************************************

'Use ADO to get list of XP clients in container to update to SP2.
Function GetADList(strCntnr)

On Error Resume Next

'Local constants and variables
Const ADS_SCOPE_SUBTREE = 2
intPageSize = 1000
strQuery = _
 "SELECT CN, operatingSystemVersion, operatingSystemServicePack " _
 & "FROM 'LDAP://" & strCntnr & "' " _
 & "WHERE objectCategory='computer' " _
 & "AND operatingSystemVersion = '5.1 (2600)' " _
 & "AND NOT operatingSystemServicePack = 'Service Pack 2'"

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = ("ADsDSOObject")
objConnection.Open "Active Directory Provider"
If Err <> 0 Then
  WScript.Echo "ERROR: Unable to connect to AD Provider with ADO."
  strMessage = HandleError
  WScript.Echo strMessage
  WScript.Quit
End If
objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = intPageSize
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.CommandText = strQuery
Set objRecordSet = objCommand.Execute
If Err <> 0 Then
  WScript.Echo "ERROR: Unable to execute ADO query."
  strMessage = HandleError
  WScript.Echo strMessage
  WScript.Quit
End If
objRecordSet.Sort = "CN"
objRecordSet.MoveFirst 

Do Until objRecordSet.EOF
  strUpdateList = strUpdateList & objRecordSet.Fields("CN").Value & ","
  objRecordSet.MoveNext 
Loop 

If strUpdateList = "" Then
  WScript.Echo "ERROR: No hosts found for update to Service Pack 2."
  WScript.Quit
End If

arrUpdateList = Split(strUpdateList, ",")

GetADList = arrUpdateList

End Function

'******************************************************************************

'Get list of XP clients from text file to update to SP2.
Function ReadTextFile(strFileName)

On Error Resume Next

Const FOR_READING = 1
Dim arrLines()

Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FileExists(strFilename) Then
  Set objTextStream = objFSO.OpenTextFile(strFilename, FOR_READING)
Else
  WScript.Echo "ERROR: Input text file " & strFilename & " not found."
  WScript.Quit
End If

If objTextStream.AtEndOfStream Then
  WScript.Echo "ERROR: Input text file " & strFilename & " is empty."
  WScript.Quit
End If

Do Until objTextStream.AtEndOfStream
  intLineNo = objTextStream.Line
  ReDim Preserve arrLines(intLineNo - 1)
  arrLines(intLineNo - 1) = objTextStream.ReadLine
Loop

objTextStream.Close

ReadTextFile = arrLines

End Function

'******************************************************************************

'Ping client to check network connection.
Function PingHost(strTarget)

On Error Resume Next

Set objShell = CreateObject("WScript.Shell")
Set objExec = objShell.Exec("ping -n 2 -w 1000 " & strTarget)
If Err <> 0 Then
  Wscript.Echo "  ERROR: Unable to execute ping."
  strMessage = HandleError
  PingHost = 2
  Exit Function
End If
strPingResults = LCase(objExec.StdOut.ReadAll)
If InStr(strPingResults, "reply from") Then
  PingHost = 0
Else
  PingHost = 1
End If

End Function

'******************************************************************************

'Check for sufficient free disk space on client.
Function CheckFreeDiskSpace()

On Error Resume Next

Const HARD_DISK = 3
Const DEVICEID = "C:"
Const SP2_FREE_SPACE = 1000000000 'One gigabyte in bytes
Const MB = 1048576 'Megabyte factor

Set colDisks = objWMIService.ExecQuery _
 ("SELECT * FROM Win32_LogicalDisk WHERE DriveType = " _
 & HARD_DISK & " AND DeviceID = '" & DEVICEID & "'")

If Err <> 0 Then
  Wscript.Echo "  ERROR: Unable to determine free disk space."
  strMessage = HandleError
  CheckFreeDiskSpace = 2
  Exit Function
End If

For Each objDisk in colDisks
  intFreeSpace = objDisk.FreeSpace
Next

If intFreeSpace > SP2_FREE_SPACE Then
  CheckFreeDiskSpace = 0
Else
  CheckFreeDiskSpace = 1
End If

End Function

'******************************************************************************

'Set registry entries to enable remote administration.
Function EnableRemoteAdmin(strTarget)

On Error Resume Next

'Set constants and variables.
Const HKLM = &H80000002
Const ENABLED = 1
Const DISABLED = 0
Const ALL = "*"
Const LOCAL_SUBNET = "LocalSubnet"
strKeyPath1 = "SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters" & _
 "\FirewallPolicy\DomainProfile\RemoteAdminSettings"
strKeyPath2 = "SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters" & _
 "\FirewallPolicy\StandardProfile\RemoteAdminSettings"
strEntryName1 = "Enabled"
strEntryName2 = "RemoteAddresses"
intValue1 = ENABLED
strValue2 = ALL

'Connect with WMI service and StdRegProv class.
Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
 strTarget & "\root\default:StdRegProv")

If Err = 0 Then

  blnCreateSubKey1 = objReg.CreateKey(HKLM, strKeyPath1)
  If blnCreateSubKey1 = 0 Then
    intReturn1 = objReg.SetDWORDValue(HKLM, strKeyPath1, _
     strEntryName1, intValue1)
    If intReturn1 <> 0 Then
      WScript.Echo "  ERROR: Unable to set " & strEntryName1
      EnableRemoteAdmin = 1
      Exit Function
    End If
    intReturn2 = objReg.SetStringValue(HKLM, strKeyPath1, _
     strEntryName2, strValue2)
    If intReturn2 <> 0 Then
      WScript.Echo "  ERROR: Unable to set " & strEntryName2
      EnableRemoteAdmin = 1
      Exit Function
    End If
  Else
    WScript.Echo "  ERROR: Unable to create registry subkey:" & VbCrLf & _
     "HKLM" & " " & strKeyPath1
    EnableRemoteAdmin = 1
    Exit Function
  End If

  blnCreateSubKey2 = objReg.CreateKey(HKLM, strKeyPath2)
  If blnCreateSubKey2 = 0 Then
    intReturn3 = objReg.SetDWORDValue(HKLM, strKeyPath2, _
     strEntryName1, intValue1)
    If intReturn3 <> 0 Then
      WScript.Echo "  ERROR: Unable to set " & strEntryName1
      EnableRemoteAdmin = 1
      Exit Function
    End If
    intReturn4 = objReg.SetStringValue(HKLM, strKeyPath2, _
     strEntryName2, strValue2)
    If intReturn4 <> 0 Then
      WScript.Echo "  ERROR: Unable to set " & strEntryName2
      EnableRemoteAdmin = 1
      Exit Function
    End If
  Else
    WScript.Echo "  ERROR: Unable to create registry subkey:" & VbCrLf & _
     "HKLM" & " " & strKeyPath1
    EnableRemoteAdmin = 1
    Exit Function
  End If

Else

  WScript.Echo "  ERROR: Unable to connect to WMI class StdRegProv."
  strMessage = HandleError
  EnableRemoteAdmin = 1
  Exit Function

End If

EnableRemoteAdmin = 0

End Function

'******************************************************************************

'Schedule SP 2 upgrade on client from remote share.
Function ScheduleJob(strCL, strTime)

On Error Resume Next

Set objNewJob = objWMIService.Get("Win32_ScheduledJob")
intCreated = objNewJob.Create(strCL, strTime, , , , , JobID)

If intCreated = 0 Then
  Wscript.Echo "  SUCCESS: Job " & JobID & " created."
  ScheduleJob = JobID
Else
  Wscript.Echo "  ERROR: Unable to create job." & _
   vbCrLf & "  Return value: " & intCreated
  ScheduleJob = -1
End If

End Function

'******************************************************************************

'Handle errors.
Function HandleError

intNumber = Err.Number
strDescription = Err.Description
strSource = Err.Source

WScript.Echo "    Number: " & intNumber & VbCrLf & _
 "    Description: " & strDescription & VbCrLf & _
 "    Source: " & strSource
strError = VbCrLf & ",,Number: " & intNumber & VbCrLf & _
 ",,Description: " & strDescription & VbCrLf & _
 ",,Source: " & strSource

HandleError = strError

Err.Clear

End Function

'******************************************************************************

'Write or append data to text file.
Sub WriteCSVFile(strFileName, strOutput)

On Error Resume Next

Const FOR_APPENDING = 8

'Open text file for output.
Set objFSO = CreateObject("Scripting.FileSystemObject")
If objFSO.FileExists(strFileName) Then
  Set objTextStream = objFSO.OpenTextFile(strFileName, FOR_APPENDING)
Else
  Set objTextStream = objFSO.CreateTextFile(strFileName)
End If

'Write data to file.
objTextStream.WriteLine "Windows XP Service Pack 2 Installation"
objTextStream.WriteLine "Run " & Now
objTextStream.WriteLine "--------------------------------------"
objTextStream.WriteLine  strOutput
objTextStream.WriteLine "--------------------------------------"
objTextStream.WriteLine

objTextStream.Close

End Sub

Command-line output

Here's an example of some hypothetical output the script might display:

C:\scripts\xpsp2>xpsp2-deploy.vbs

Host Name: client1
  ERROR: Ping failed.

Host Name: client2
  ERROR: Unable to create job.
  Return value: 3

Host Name: client3
  SUCCESS: Job 3 created.

Host Name: client4
  SUCCESS: Job 29 created.

Data written to c:\scripts\xpsp2\xpsp2-4-13-2005.csv

CSV file

Here's the same output in a CSV file opened in Excel:

Excel spreadsheet with script output

What happened?

Aren't we forgetting something here? Aha, you noticed. Given that this script merely creates a job that kicks off the installation process, how do we find out whether the installations were successful on each machine? There are a variety of ways to script this that involve techniques such as handling events. At the moment Dr. Scripto is sleeping it off on his desk, drained from the effort of writing this script, but maybe we can convince him to take this on in a future column.

A simple approach, after giving the installations a reasonable time to complete (about 20 minutes on our testbed), would be to run the script from either Part 1 or Part 2 of this series to check which hosts are now running Service Pack 2. You'd use the Part 1 script, which uses WMI, if you don't use Active Directory on your network, or the Part 2 script, which uses ADO and ADSI, if you are working on an Active Directory container.

Another tack would be to check the event logs on the remote machines. You can do this with the WMI class Win32_NTLogEvent. When Service Pack 2 is installed, an event with an ID of 19 and a source of Windows Update Agent is written to the System Event log. A different event is probably written if Service Pack 2 is not successfully installed. Unfortunately, the event ID and source are not unique, so you might have to check all event 19s for evidence of Service Pack 2.

If neither of the previous suggestions appeals to you, there's yet another place to find information on the installation: Xpsp2.exe creates a log file for the installation, in %systemroot% on each local machine. The name is Svcpack.log, according to SP2 documentation. You could probably write a script that collects these log files and copies them back to your workstation. For information on this file, see Guide for Installing and Deploying Microsoft® Windows® XP Service Pack 2.

If you figure out a good way to track the installations that you want to share with the Scripting Guys, drop us a line at scripter@microsoft.com (in English, if possible).

For more information

Windows XP Service Pack 2

Windows Firewall