Tips for securing and maintaining the system over time
In the previous three articles in this series, I showed you how to use only scripts to architect and build a large, complex enterprise solution. This final installment in the series gives you the remaining details about the corporate update system, such as information about securing it and advice about using it to its full potential over time.
Securing the Corporate Update System
The corporate update system runs commands on almost every PC in your organization if you so choose. If you implement this solution, you've created a new way of trashing every client on your network just as if you connected to them with administrative privileges and wiped them out. Thus, only legitimate administrators should be able to write to the central script repository. Keep the people with access to a small and manageable group that works together. You should also ensure that the lead systems manager or administrator approves any new script before you install it in the repository and release it into the wild. In addition, you can give everyone read access to the central script repository, but you might prefer to restrict that access to the system account and administrators.
The CUSClientUpdater.job scheduled task on the clients must use an account to run. Figure 1 shows the Task tab of the CUSClientUpdater.job's Properties dialog box. The Run as field must point to a domain administrator—equivalent account. I suggest that you create a central account in Active Directory (AD), give it an obscure password, and set the password to not expire. Then, change the job's Run as property to use that user account when it runs. For details about how to set scheduled-task properties programmatically, see "Scripting Solutions with WSH and COM: Advanced Methods for Task Scheduling," May 2002, http://www.winscriptingsolutions.com, InstantDoc ID 24592, and "Scripting Solutions with WSH and COM: Scheduling Automated Tasks," March 2002, http://www.winscriptingsolutions.com, InstantDoc ID 23935. Or, if you have the Windows XP CD-ROM, you can install the XP support tools and use the schtasks.exe command-line tool described at http://www.microsoft.com/technet/treeview/default.asp?url=/technet/prodtechnol/winxppro/proddocs/schtasks.asp to manipulate tasks easily from the command line. You can find more information about the XP support tools at http://www.microsoft.com/windowsxp/home/using/productdoc/en/default.asp?url=/windowsxp/home/using/productdoc/en/ntcmds.asp.
To ensure that the CUSClientUpdater.job account is domain administrator—equivalent, it should be a member of Domain Admins. I'm assuming here that Domain Admins is a member of the Administrators local group on every client on your network, so adding domain users to the Domain Admins group allows control over all the clients as well. This setup is the default way of working for administrators; if your structure is different, you need to follow your local practices for creating on the domain an administrator-equivalent account that you can use.
End users shouldn't be able to modify the corporate update system's client-side components CUSClientUpdater.job, CUSClientUpdater.wsf, and CUSClientLastUpdateNumber.txt. Consequently, you must change the file access permissions for these three files to ensure that only members of the Administrators local group can modify the files. To change file access permissions, you right-click a file, select the Security tab, and make the changes you require. To automate this process, you can use another support tool: the cacls.exe command-line tool described at http://www.microsoft.com/technet/treeview/default.asp?url=/technet/prodtechnol/winxppro/proddocs/cacls.asp.
Targeting Selected Clients
In "Scripting a Corporate Update System, Part 3," December 2002, http://www.winscriptingsolutions.com, InstantDoc ID 26969, SimpleCUSClientTargetter.wsf includes the code that Listing 1 shows to set an ADO filter and search for all clients and servers that aren't members of the corporate update system management group. If you want to target only selected clients, add them to a group and modify the ADO filter, as Listing 2 shows. In Listing 2, MYGRP is a constant representing the group.
If you want to specifically target workstations and not servers, you can use an ADO filter such as the one that Listing 3 shows. This filter fetches all computers that aren't domain controllers (DCs—which have a primary group ID value of 515) and whose operatingSystem attribute doesn't end with Server. You can tweak the filter to represent other restrictions—just use ADSI Edit to determine the primary group IDs that you want to include or exclude, then add them to the filter. ADSI Edit is on the Windows 2000 CD-ROM in the \support\tools folder—just use an administrator-equivalent account and click setup.exe in the folder to install the support tools on your client. However, because OS service packs patch and update these tools, you should reinstall any service packs after installing the support tools from the CD-ROM. Or you can go to http://www.microsoft.com/windows2000/downloads/servicepacks, click the link to a service pack, scroll down to the Support Tools link, and click it to find information about which support tools the service pack updates. When Windows .NET Server (Win
.NET Server) 2003 comes out, you can use support tools from this OS version to manage Win2K as well.
Copying Updates to the Central Script Repository
PCs are fast these days. If you copy a new update named 27.wsf to the central script repository before copying the 27.txt file (if one exists) or the supporting files in the folder named 27, PCs might try to run the central script repository scripts and not find the files they need or might run the script when they shouldn't do so. Both situations are hard to clean up after because they can leave clients in a lot of different states. Even if you copy all the update files at the same time to the central script repository directory, you can cause problems if a client is looking for 27.wsf at just the wrong time. The correct way to move scripts to the central script repository is to upload the 27.txt file and the 27 folder of supporting files first—then copy 27.wsf to the repository last.
Clearing Obsolete Updates
If you create an update 27 that installs some files, then later create an update 52 that installs a more recent version of the same files, clients might no longer need to execute 27, but new clients will do so anyway. To solve this common problem, you can simply delete the main body of the script in 27.wsf so that the file contains the following
<job> <script language="VBScript"> </script> </job>
New clients will run the file, but it will do nothing because it has no code. If you need to make many updates obsolete, see the section "Manipulating the Update Numbers" later in this article.
Correcting Faulty Updates
Mistakes happen. The corporate update system can make changes on many clients quickly before you notice a mistake in an update script. If you see a mistake, your tendency will be to delete the update, but think carefully before taking this action because it can leave clients that are executing the update in a bad state. If you decide to take the update out of service, you can quickly do so by renaming the .wsf script to a name that clients aren't looking for (e.g., 27a.wsf).
Typically, the easiest solution is to let the faulty update run, then create a new update that undoes the damage of the faulty update. However, if the situation is grave and a new script can't solve the problem, you must immediately rename the faulty .wsf file and manually update the clients. You now have some clients that have executed 27.wsf (for example) and some that haven't. How can you find the faulty clients so that you can manually correct them? The clients that have executed 27.wsf are now looking for 28.wsf. You can add in an update called 28.wsf that simply emails you the names of the clients that execute it. Then you have your list of the clients you need to fix. The clients that haven't executed 27.wsf are still waiting for it to appear (you renamed it, so it isn't available). After fixing the clients that downloaded the faulty update, you can remove the code from 27.wsf and 28.wsf so that all the clients will be ready for update 29.
To prevent faulty updates, test your scripts before putting them in production, just as you would any other script. On your test PC, double-click the .wsf update script in the central script repository.
If you want to test a complex update on several clients, place the clients in an AD group and create a .txt file that restricts the update to that group (as I described in "Scripting a Corporate Update System," October 2002, http://www.winscriptingsolutions.com, InstantDoc ID 26360). For example, if the update is 27, the script is 27.wsf and the configuration file 27.txt specifies the AD group for the text PCs. All PCs not in the group will ignore update 27 and reset themselves to look for update 28; all PCs in the group will execute 27 and reset to wait for 28. If you need to refine your update script and test it multiple times, you can keep creating update numbers (28, 29, 30, and so on) until you get the result you want.
Although this approach forces all other clients to ignore an update, the overhead is minimal. If you have only a few test PCs, your other alternative is to wait for all of them to execute update 27, make the necessary changes to the scripts in 27, then change the CUSClientLastUpdateNumber.txt file on each of the test clients back to 26 so that they all download 27 again.
Determining a Client's Update Number
At some point, you might want to know each client's update number. You can either take a snapshot of the current situation when required or enhance the corporate update system to ensure that the data is always available when you need it.
Your first idea for a snapshot solution might be to script an update that has clients email you their name and most recent update number. Or you could create on a server a share with permissions that let the clients' Run as account write to the share, then create an update that writes to the directory a file containing the client name and update number. Sharp-eyed readers will realize that these solutions do nothing more than return the update number of the update you just created. The solutions don't help you find out why several clients that are supposed to be running the corporate update system never respond. One snapshot solution that works is a server-based script that connects to every client, reads its CUSClientLastUpdateNumber.txt file, and records the result.
However, I prefer a more permanent solution. Every object in AD has 15 text attributes that you can use for your own data storage. I discuss these Extension attributes in "Extending Active Directory's GUI," February 2000, http://www.winnetmag.com, InstantDoc ID 7883, and "Generating Deployment Reports," March 2000, http://www.winnetmag.com, InstantDoc ID 8054. You can use any of these attributes to store the update number. Just modify the end of the CUSClientUpdater.wsf script to use Microsoft Active Directory Service Interfaces (ADSI) to write the update number to AD as well as to the CUSClientLastUpdateNumber.txt file, as Listing 4 shows. You can then search AD at any time to populate a Microsoft Excel spreadsheet with clients' update numbers ("Generating Deployment Reports" explains the details).
If you're wondering why you should keep the number on the local machine at all if you can place it in AD, the answer is simple: You never know which DC a client will hook up with when it reads or writes data to AD, and replication takes minutes between DCs; so, a client might write an update number to one DC, then minutes later read a lower update number from another DC. For example, if a client writes a value of 77 to DC A and 5 minutes later reads the value of 72 from DC B before A has had a chance to replicate to B, then the client will try to run updates 73 through 77 all over again—and that's bad news.
Manipulating the Update Numbers
The earlier articles in this series don't discuss copying a CUSClientLastUpdateNumber.txt file to a client. If the file is absent on a client, the CUSClientUpdater.wsf script simply looks for update 1 in the central script repository. However, over the years, I've needed to change this procedure for some organizations. Let me give you a couple of real-life examples.
First, over time, the updates in the central script repository become obsolete. If you delete the body of obsolete scripts, as I suggested earlier, you can eventually end up with dozens of empty updates. To avoid all these empty updates, you might want to have new clients start at a higher update number, which is a reason to upload a different CUSClientLastUpdateNumber.txt file to new clients. Second, one organization I worked with designed its client configuration build procedure to automatically install the corporate update system client files. After installation, the clients used the corporate update system to update themselves to the current build. However, after a while, we needed to update the configuration build procedure for the new clients that we were adding, and rather than forcing new clients to go through 50 or 60 updates, we wanted to roll up the changes in these updates into the new build procedure.
The second example is simple to address. Let's say that you roll up 34 updates into your new build procedure. Then, you copy CUSClientLastUpdateNumber.txt to the new clients as part of the corporate update system's client-side files but place the number 34 inside the .txt file so that the clients begin by looking for update 35.
Addressing the first situation is a little trickier given the corporate update system's current setup. The corporate update system reads CUSClientLastUpdateNumber.txt before starting its update run, then writes a new .txt file at the end of its run. At no point during this process does the CUSClientUpdater.wsf script check for a new .txt file, nor can the updater scripts that CUSClientUpdater.wsf runs tell CUSClientUpdater.wsf to do some task. If you want to alter the processing of the CUSClientUpdater.wsf script through some communication from the updater scripts, you need to change CUSClientUpdater.wsf. Here's one easy modification that I've used. You change the CUSClientUpdater.wsf section that performs the updates (which "Scripting a Corporate Update System, Part 2," November 2002, http://www.winscriptingsolutions.com, InstantDoc ID 26632 shows) to look for a file with a .num extension. So if your clients are looking for update 34 and you want them to skip to update 67, create in the central script repository a 34.num file that contains the value 67. Then, modify the beginning of CUSClientUpdater.wsf's Do While loop to use a variable such as the following
CUS_SCRIPT_REPOSITORY & intUpdNum & ".NUM"
to check for a file with the .num extension. If CUSClientUpdater.wsf finds such a file, the script should open and read the file, just as it does the .txt file, and the value that the .num file contains will be the number of the next update to run. Listing 5 shows such code, which is just a modification of the CUSClientUpdater.wsf code that opens the .txt file. CUSClientUpdater.wsf starts its loop. When it gets to an update that has a .num file, it reads the integer inside, skips the current update, and starts again with the update that's associated with the integer it just read.
To update the CUSClientUpdater.wsf and CUSClientUpdater.job client-side files, you can just copy the files to the clients as part of an update. If you do so, you need to be sure that the immediately following updates don't rely on your new .wsf file because the previous .wsf version might execute them. For example, let's say that .wsf v1 executes update 34, which replaces the script with .wsf v2. At this point, .wsf v1 is still running, so you need to ensure that if updates 35, 36, 37, and so on exist, it doesn't matter that .wsf v1 is running them. CUSClientUpdater.job won't pick up the new .wsf v2 script and run it until .wsf v1 stops.
Here's a simple solution. Make update 34 the last update that .wsf v1 ever runs by modifying .wsf v2 to look for updates called 35a, 36a, 37a, and so on. Because .wsf v1 doesn't look for the a suffix, it never finds any updates after 34, so it stops execution and .wsf v2 picks up where it left off. Next time around, you change to b, then c, and so on. This solution works well and lets you keep your numbering scheme intact.
You don't need to install .wsf v1 on new clients from that point on as long as .wsf v2 (or later) can run earlier updates. Just install .wsf v2 onto clients, but make sure that each update in the central script repository has a corresponding a, b, c, or whatever version to ensure that .wsf v2 picks up the old updates as well as the new ones.
Checking for Group Membership
The corporate update system has one limitation that comes into play when clients are checking for group membership to determine whether to apply an update. Groups can be nested—that is, groups can be members of groups. If a client is in group AAA, the corporate update system functions IsUpdateToBeRunOnThisPC and GetPCGroupMembership will definitely pick it up. However, if the client is a member of AAA, and AAA is a member of BBB but the client isn't, and the functions are looking for members of BBB, the functions won't find the client. If you want to check for nested clients, you need to modify these functions.
Well, that's it for this corporate update system article series. If you come up with any great enhancements, let me know.