Separating software installation from logon makes both run more smoothly
We had always used a logon script to update virus definition files on the desktops in our organization. The process worked flawlessly until we decided to use the logon script to update the virus scan software to the next version. Only a few minutes after we changed the script, we started to receive complaints from our users, some of whom were waiting more than 5 minutes for the startup processes to be completed before they got control of their systems. Detecting the installed version, uninstalling it, and installing the new version over the network—along with other logon script functions—caused logon times that were just too long. To fix the problem, I split the logon script into two parts: one that would copy new files to the desktops and one that would run as a background process on the desktops to install any new programs. Now users don't have to wait for installation to finish before they get control of their system.
Doing the Two-Step
Typically, a logon script is a one-step process that executes all the commands in the script before the user gets control of the desktop. However, if you try to put too many commands in the logon script, you can prolong the logon process to the point at which it's unacceptable for users.
To reduce the logon time, I decided to split installation into two independent steps: Copy files and install files. I used our existing logon script to copy installation and configuration files and created a separate script for installation. As Figure 1 shows, the logon script and the installation script run as independent processes.
Dividing logon and installation into two independent steps provides a couple of benefits: First, it shortens the logon script execution time because the logon script no longer installs software. The user no longer has to wait for all installations to finish before being able to use the system; installations can proceed in the background while the user is working on the system. Second, installations can be carried out when the user isn't connected to the network because the logon script has copied the installation files to the user's local disk.
The logon script runs when the user's system is connected to the network and the user logs on by using the domain ID. Each time the logon script runs, it copies installation and configuration files, if any, to the user's local disk. The logon script also sets the HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run registry subkey by running the regedit.exe command as follows:
%systemroot%\regedit.exe /s c:\installfiles\setrun.reg
Setrun.reg, which Figure 2 shows, adds a new Installer registry value entry for the installer script under the Run subkey.
Executing the Regedit command through the logon script restores or adds the value if the user or an administrator accidentally deletes it. And running Regedit with the /s command switch ensures that the command is executed silently (i.e., without requiring user confirmation). See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/setupapi/setup/run_and_runonce_registry_keys.asp to learn more about the Run and RunOnce subkeys.
The Logon Script—Step 1
The first step is to create a logon script (loginscr.cmd) that copies the installation files from the specified location to the local disk. The xCopy command, which Callout B in Listing 1 shows, copies all files and folders from the specified source path (set in the RemoteInstallFolderPath environment variable) to the destination path (set in the LocalInstallFolderPath environment variable). The /s switch copies the entire tree, and the /d switch without a specified date copies only the newer files. (For more information about xCopy and how to use it, type
at a command prompt, or see Windows Help.)
Redirecting the xCopy command's output to a log file (install.log) captures all the command's success and failure messages. The log file is time stamped, as callout A in Listing 1 shows. The command to set the registry entry for the installation script, which I discussed earlier, is at callout C in Listing 1.
By default, when a user logs on to a system—whether a server or a workstation—the logon script set for that user is executed. You might want to include logic in your logon script to limit software installation to workstations running a specific OS. The article "Build a Logon Script to Automate Deployment," April 2005, Instant Doc ID 45503, explains how to detect the various OSs to limit installation to Windows NT Workstation or later systems.
The Installation Script–Step 2
Loginscr only copies files; the Installer.vbs installation script, which Listing 2 shows, installs the software packages copied to the desktop by the logon script. Installer installs the software based on instructions in an input text file that's copied to the workstation along with the software packages.
Installer begins by declaring constants for the log folder path, log filename, installation folder path, and name of the installation input file, as callout A in Listing 2 shows. These constants are used throughout the rest of the script.
After declaring the constants, the script creates an instance of the Microsoft Scripting Runtime Library's FileSystemObject object that's used to open and read the input text file. The script also creates an instance of the Windows Script Host (WSH) Shell object WScript.Shell, as callout B in Listing 2 shows. WScript.Shell provides the Run method used later in the script to execute the commands.
A user-friendly method of displaying messages is to use a Microsoft Internet Explorer (IE) window. A default installation of IE 4.0 or later installs the InternetExplorer.Application COM object, which exposes methods, properties, and events. The Navigate method allows navigation to a resource identified by a URL; the Busy property retrieves a Boolean value that indicates whether the IE object is engaged in a navigation or download operation. You can learn more about the InternetExplorer.Application object by visiting http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/webbrowser/reference/objects/internetexplorer.asp.
The code at callout C in Listing 2 instantiates the IE object (that is, stores it in the oIe variable). Next, the script uses the Navigate method to create a new, blank IE window, positions the window at the top left corner of the computer screen, sets it to 400×400 pixels, and specifies no toolbar or status bar. The script then enters a Do...Loop statement (aka Do While loop), sleeping for 200 milliseconds while the IE object completely loads the blank document window. By default, IE's Visible property is set to False. The script sets it to True (1), at callout D in Listing 2, so the user can see the window.
After constructing the window, the script can write information to it by using the Write method. Because this method is called multiple times during script execution, I wrapped it in its own subroutine, DocWrite, which is called at callout F in Listing 2. (DocWrite and Installer's other subroutines are included with the complete code on the Web but aren't shown in Listing 2.) The code at callout F also writes a Start of Script execution message to the display window and to a log file.
The code at callout E in Listing 2 calls the CheckAndCreateFolder subroutine, which uses the FileSystemObject's FolderExists method to confirm that the log folder exists. If the folder doesn't exist, the subroutine creates a new folder by calling FileSystemObject's CreateFolder method. (Note: You can also use the subroutine to check for other folders simply by passing the folder path as a parameter to the subroutine.)
The script's input file, Installerinput.txt, must exist before the script can use it, so the script calls FileSystemObject's FileExists method to check for the file's presence at callout G in Listing 2. Then, if the file exists, the script uses a Do While loop to loop through the file line by line. The loop continues to the end of the file, as marked by the TextStream object's AtEndOfStream property. If the file doesn't exist, the script displays and logs a File not found error, then ends.
Installerinput lists the software packages to be installed by Installer. For each package, the file contains a line that has comma-separated pieces of information. Figure 3 shows a sample Installerinput file. Installer uses VBScript's Split method to store the Installerinput data in an array of strings (sRLine). The elements in the array are arranged in the following order:
- The first element in the sRLine array is the name of the package executable, such as hotfix1.exe.
- The second element is the path to the file, such as C:\installfiles\hotfixes.
- The third element is the restart flag value.
- The fourth element is a value passed to the Run method to set the command window style.
- The fifth element is a true or false value for the Run command. A true value halts the script until the external program executed by the Run command ends.
- The sixth element is an installation message that the script displays to the user and writes to the log file.
- The last element is used to determine whether the package should be installed.
The script, prior to running the command, checks the array's last element, as callout H in Listing 2 shows. A value of 0 causes the installation to be skipped, and a value of 1 causes the command to be run.
The script uses the Run method of the WScript.Shell object (instantiated at the beginning of the script) to run the command. The Run method supports one required parameter, strCommand, and two optional parameters: intWindowStyle and bWaitOnReturn. The strCommand parameter specifies the command to run. The intWindowStyle parameter is an integer value that specifies the window style. The bWaitOnReturn parameter is a Boolean value that tells the Run command whether it should wait for the command to finish before continuing to the next statement in the script. The statement at callout I in Listing 2 passes these three parameters to the Run method.
A software installation sometimes requires the system to be rebooted after the installation is finished. Therefore, if the runRestart flag is set to S (i.e., restart by the script), the code at callout K in Listing 2 calls the Shutd subroutine, which invokes shutdown.exe (part of the Microsoft Windows 2000 Server Resource Kit, copied to the user's workstation by the logon script) to force shutdown and restart the system. (The other acceptable values for runRestart are P, which instructs the script that the program being installed will restart the system, and N, which instructs the script not to restart the system.)
As you've seen, the script logs information to the specified output text file. For example, as callout J in Listing 2 shows, the script writes shutdown information to the log file before calling the Shutd function. Similarly, the script writes the information about the command being executed to the log file. To write the information to the log, the script calls the WriteLog subroutine and passes it the message to be written and the output file name (mainlog.txt).
I frequently use WriteLog in my scripts to write data to a text file. If the file doesn't exist, WriteLog creates it and gives it the name passed as the parameter. If the file does exist, WriteLog opens it in the append mode. In either case, WriteLog then inserts the message passed to the subroutine as a new line. To manipulate the text files, WriteLog uses the FileExists, OpenTextFile, CreateTextFile, and WriteLine methods.
After thoroughly testing our two-step logon and software installation solution, we rolled it out to our production environment. And indeed it worked—we had a solution that made the users happy because logon speeds went back to normal and made management happy because users stopped complaining and antivirus protection and other software could be updated as necessary. As an additional advantage, the two-step procedure reduced the load on the network because installation took place on the local disk and not over the network.