More often than not, we tend to use scripts for small, repetitive jobs. If we have time, we sometimes put these scripts or portions of them in subprocedures and functions within libraries for wider use. However, we can also use scripts to build a large solution. Many of the authors who write for this newsletter construct such solutions. Before you tackle such a project, you need to understand how to build a workable and maintainable system for the future, rather than a mass of spaghetti-type code that will be difficult to understand in 6 months.
This article begins a series in which I'll show you how to design and build a large, complex enterprise solution entirely by using scripts. I'll provide many useful scripts and techniques and draw on scripts from previous articles to maximize code reuse. To begin, let's look at the project requirements and design so that you can follow the entire solution and understand my approach.
Imagine that your company wants to streamline updates to client computers. If you're already using a computer management-and-distribution application such as Microsoft Systems Management Server (SMS) or third-party software such as Hewlett-Packard's HP OpenView or IBM's Tivoli Software, then you already have a deployment mechanism. However, if you don't have such a workhorse at your disposal, Microsoft Software Update Services (SUS—http://www.microsoft.com/windows2000/windowsupdate/sus/default.asp) is a free solution that can help you apply crucial fixes and security patches to Windows 2000 Service Pack 2 (SP2) and later clients and servers. Unfortunately, SUS doesn't help you update products such as Microsoft Exchange Server or Microsoft SQL Server or the variety of user files, application files, Task Scheduler jobs, and log files that you must install and maintain over the course of a computer's lifetime.
In addition to SUS, you need a smart solution that lets you create an update once and specify which computers will get the update. For example, the solution should let you install a finance update on finance systems, a marketing update on marketing systems, a Europe update on European systems, and a global update on all systems. The solution should also be flexible enough to deploy scripts and execute them on any client just moments after you finish writing the script. New or rebuilt computers that join the domain should automatically bring themselves up-to-date by applying patches and running the correct scripts in the correct order. To meet these requirements, you can build a centrally managed corporate update system.
Corporate Update System
I wrote a corporate update system many years ago that has evolved over time through use in a variety of organizations. It consists of a central script repository that contains scripts that clients (i.e., workstations, servers) can execute across the enterprise. Active Directory (AD) groups let you target certain scripts at all clients, certain groups of clients, or individual clients.
The administrator incrementally assigns each script a number and at the same time chooses whether to create a corresponding configuration text file that specifies which clients should execute the script. Clients keep track of the number of the last script they executed, and a scheduled task checks with the central script repository at 5-minute intervals to see whether a new script is available. If so, the clients execute the script to bring themselves up-to-date. The great advantage of this centralized management is that if you make a mistake in a script or update-installation routine, you can use the same update mechanism to quickly release a fix.
To kick things off, you need to create the scheduled task that checks for new scripts every 5 minutes and deploy that task to every PC in your enterprise so that each client can ensure it's up-to-date. Although you can add this scheduled task to your existing client OS build procedure for new PCs, this approach doesn't help you apply the scheduled task to existing PCs.
To ensure consistent deployment across the enterprise, I've created a series of scripts as part of the corporate update system known as the client targetter scripts. Each client that's processed successfully is automatically added to a special AD group, indicating that the client is now centrally managed. The client targetter scripts run at regular intervals to check for all PCs in the enterprise that aren't part of this AD group, attempt to install the necessary files onto these nonmember clients, and if successful, add these clients to the AD group. The client targetter scripts will continue to attempt to install the necessary files on any client that fails to receive the files and record multiple failures for manual administrative intervention.
As I mentioned earlier, this automation is made easier by placing previously created scripts in subprocedures and functions within libraries. However, the corporate update system requires a large number of constants and therefore also stores the constants in the libraries for ease of management. Web Table 1 (http://www.winscriptingsolutions.com, InstantDoc ID 26360) provides a summary of the four parts of the corporate update system: client installation scripts; central script repository and update scripts; subprocedure, function, and constant libraries; and client files.
Central Script Repository
The central script repository should have a simple and effective structure that you can easily manage. You place the incrementally numbered script updates in the root central script repository folder (Web Table 2 provides a description of some of the folder path locations that I've used for the corporate update system). You should also assign each update a number starting with 1 (however, this requirement isn't always necessary; I'll discuss when to make these assignments in detail in an upcoming article about tips and tricks for the corporate update system). For my simple example, I created two updates named 1.wsf and 2.wsf. Although I used Windows Script Host (WSH) and VBScript to create all my corporate-update-system scripts, you can use other languages, such as JScript or Perl.
You can create a text file that corresponds with the update script and insert in the text file the name of every client you want to target with your script. However, if you have thousands of clients, you could spend hours creating and managing such a text file, and you'd probably end up writing scripts just to keep the text file up-to-date. A better approach is to create a corresponding text file (e.g., 1.txt, 2.txt) that stores AD groups containing the computer objects representing the clients that the update needs to target. When no text file exists, the update applies itself to all computers in the enterprise.
I advocate placing user objects and computer objects into AD groups so that you can easily target selected sets of users or computers. For example, if you want to target the finance department's computers, you can create a Finance Clients group and add all of finance's PCs to that group. The same principle can apply to any group you have. You can even target AutoCAD clients to send updates to PCs with AutoCAD installed. Establishing these groups takes time initially, but your efforts will pay off later when you can target updates to the groups that need them. You can add groups as you need them for your scripts—you don't need to create all the groups at the start—although planning for future groups is always a good idea.
In addition to the script and its text file, you might want to create a folder (e.g., numbered 1, 2, 3) to hold the files the script needs (e.g., patches to apply, files to copy). Now that you understand the overall project and the organization of the central script repository, let's look at our first script example.
Script Template and Sample Scripts
Listing 1 provides a template for all corporate-update-system scripts. The template uses standard Windows Script (.wsf) file format and the WSH XML <script> element to import two libraries—GenericLibrary.vbs and CUSLibrary.vbs at the start of the .wsf file. Listing 2 presents an excerpt of GenericLibrary.vbs, and Listing 3 presents an excerpt of CUSLibrary.vbs. (Both excerpts show only the components needed for this month's example; I'll get into more detail about these libraries in future articles. The complete versions of both libraries are available on the Windows Scripting Solutions Web site at http://www.winscriptingsolutions.com, InstantDoc ID 26360.) Not all scripts need to access these libraries, so I delete the XML <script> element import lines from the top of my .wsf file as necessary.
The template in Listing 1 also includes a block of text (separated by asterisks to help with visibility in an editor) that you can use to store the script name, description, and any inputs and outputs the script uses; an Option Explicit statement, which forces the script to declare the variables it uses; and the standard On Error Resume Next command, which ensures that fatal errors don't cause the script to fail. The next four comment lines (i.e., 'Constants, 'Variables, 'Code, 'Subroutines and Functions) remind me of the order that I should place items into the script. You can modify the template as you see fit.
1.wsf. Listing 4 shows 1.wsf, a simple update script. Keep in mind that the script runs on a client but is stored centrally. The script refers to an executable called patch core app.exe in a folder numbered 1—\\ScriptServer\Scripts\CUS\Central Script Repository\1. Because I want every client in the organization to run this script, I haven't created a 1.txt file.
The script begins by referencing CUSLibrary.vbs and GenericLibrary.vbs. CUSLibrary.vbs contains the CUS_SCRIPT_REPOSITORY constant, which represents the path to the central script repository. GenericLibrary.vbs contains two Shell::Run constants. The declaration block toward the beginning of the script says that the script will return a 0 for successful execution or a 1 for a failed execution. When the update is successful, the client can move on to the next update; when unsuccessful, the client needs to reapply the update.
The script includes the strUpdateFilesPath variable, for visibility purposes during maintenance, to store the path to the patch. This variable uses the imported CUS_SCRIPT_REPOSITORY constant to supply the path to the central script repository.
After the script places a WScript::Shell object into the wshShell variable, the Shell::Run method runs an external program; in this case, it runs the executable in the folder. The script passes three parameters to the Shell::Run method. The first parameter is the path to the executable that you want to run. The second parameter is a constant containing a value of 0 to tell the script to hide any windows that might appear on the user's screen when the executable runs on the user's computer. The third parameter is a constant holding the value of TRUE so that the script pauses execution until the executable has finished. The constants in the second and third parameters come from GenericLibrary.vbs.
The Shell::Run method stores a value that indicates the success (0) or failure (1) of the command's execution in the intResult variable. The Wscript::Quit method quits the script and returns the success or failure result so that the script running on the client that called the update knows whether the update succeeded or failed.
2.wsf. Listing 5 shows 2.wsf, a script for distributing a new DLL required by the French-language version of a software application that runs on European and Pacific clients. The script copies the file, which is stored in a folder numbered 2 in the central script repository, to the appropriate clients, then uses regsvr32.exe to register the DLL on those clients. Because only certain clients require this new DLL, the corporate update system applies this file to only the European French Clients and Pacific French Clients AD groups. Listing 6, page 14, presents 2.txt, which contains the full Lightweight Directory Access Protocol (LDAP) paths to those two AD groups.
The 2.wsf script imports the CUS_SCRIPT_REPOSITORY constant, just as 1.wsf did, from CUSLibrary.vbs. In addition, 2.wsf imports the FSO_COPY_OVERWRITE constant from GenericLibrary.vbs (I explain this constant in a minute).
The 2.wsf script starts by setting a variable equal to the source DLL, french.dll, in the folder numbered 2. The strClientTargetPath variable, which is included for visibility purposes during maintenance, stores the DLL target path. (This target path is C:\windows\system32 by default, but the environment variable is used in the pathname to ensure accuracy.) The strCommand variable stores the Regsvr32 command, which uses the /s switch to silently register the DLL after the script copies the DLL to the client.
After setting the variables, the script tries to copy french.dll by creating a Scripting::FileSystemObject object in the usual way and using three parameters to call the FileSystemObject::CopyFile method. The first two parameters are the DLL's source path and the client's destination path. The third parameter, FSO_COPY_OVERWRITE, tells the script to overwrite the existing DLL, if possible, on the client machine. Remember that the target path that the FileSystemObject::CopyFile method references should have a backslash (\) at the end. This backslash is necessary because if the destination path ends with a path separator (\), the object assumes that the destination is an existing folder in which to copy matching files. Otherwise, the object assumes that the destination is the name of the file to create.
Next, the script determines whether an error has occurred because the FileSystemObject::CopyFile method doesn't return a result. If an error has occurred, the script quits with a return code of 1 indicating an error. If an error hasn't occurred, the Shell::Run method runs Regsvr32 (in the strCommand variable) to attempt to register the newly copied DLL. The script then uses the WScript::Quit method to pass out the result (i.e., success or failure) of the execution of the strCommand variable.
Just the Beginning
Both 1.wsf and 2.wsf show you what you can do with update scripts and are exactly the sort of scripts you might already run on a regular basis. These scripts are only examples—you'll create your own. Of course, I could have expanded either script by making a copy of the original file and replacing it if there were a problem copying the new file so that the client is never in a problematic state. However, that's a project you can tackle yourself.
I should add one word of warning about the use of AD groups in a script's text file. When you add an AD group name (e.g., Europe Clients) to a text file, you link the AD group name with the text file's script. If you later delete the AD group named Europe Clients and create a new group named EMEA Clients, you must modify the text file to adequately represent the new clients so that the associated script will continue to work.
That's all I have time for this month. Next month, I'll tell you how to build the software on the clients that executes scripts in the proper order, checking for group membership in the text files as needed.