How to quickly obtain data from multiple workstations

Your company is purchasing a new application, so you need to make sure that 500 workstations have a certain service pack level installed on them—a common task that script-savvy network administrators can easily perform. Typically, .bat and .cmd scripts that perform a task against a list of workstations consist of one line that performs the task and supportive code that connects to the workstation and returns error codes. Wouldn't it be nice if you didn't have to write the supportive code over and over again? A good practice is to reuse code that has already been tested and proven, but keeping track of numerous code snippets can be a challenge.

If you find yourself in this situation, try keeping just one master script that you can add operations to as needed. Letting one script grow in this fashion keeps your code centralized and within easy reach. Plus, it's truly reusable code—not copies of code in multiple scripts that can become outdated. Ideally, master scripts

  • work off a list of workstations
  • test each workstation to see whether it's a valid machine and powered on
  • connect to each workstation
  • perform whatever task you need
  • log results and error codes

In .bat or .cmd scripts, you typically want code that you can implement easily and quickly. Thus, the master script must start out and remain easily modifiable. I created an easily modifiable script called Chkws, which I've tested on Windows NT 4.0 Service Pack 6 (SP6) through Windows 2000 SP2. Let's look at how Chkws works and how to add operations to it.

How Chkws Works
As the following syntax shows, the command to launch Chkws takes at least two arguments:

                              chkws \[list.txt\] \[command\]

where list.txt is the name of the file that contains the list of workstation names (one name per line) and command is a keyword that specifies the operation to perform. The number of arguments can increase as you find new operations to perform. For example, I initially wrote Chkws to determine which version of Microsoft Internet Explorer (IE) was installed on workstations. I later expanded the script to perform other operations, such as reporting the machine's last reboot, testing whether each workstation is on the network, returning the machine's media access control (MAC) address, using Wake on LAN (WOL) to power on the workstation, checking settings for Symantec's Norton AntiVirus and LiveUpdate, installing Tivoli agents, and rebooting the workstation.

Listing 1, page 8, contains a version of Chkws that performs two operations: checking remote workstations for the time of the last reboot and the IE version installed. The code at callout A in Listing 1 checks the arguments that you used to launch the script. The first line makes sure that you included at least two arguments. The second line contains a list of valid operations. In this case, the valid operations are uptime, which is the keyword for the operation that checks the time of the last reboot, and ie_ver, which is the keyword for the operation that checks the IE version. The script uses the Find command to see whether the second argument (i.e., %2) you passed to Chkws is in a list of valid operations. The advantage of using the Find command is that you can use its /i switch to ignore the argument's case, thereby letting you enter the keyword in any case you choose.

To determine when the last reboot occurred, Chkws uses uptime.exe, a utility that queries a machine to determine how long it has been powered on. You can download uptime.exe from the Microsoft Web site ( To determine the IE version, Chkws uses reg.exe, a utility that remotely queries registry information. Reg.exe is available from several sources, including the Win2K Support Tools and Microsoft Windows NT Server 4.0 Resource Kit Supplement 4.

The code at callout B verifies that uptime.exe and reg.exe are available for use. Hard-coding the utilities' paths can be cumbersome and confusing, so the first line at callout B sets the UTILS variable to the path of the utilities folder (%~dps0UTILS). At runtime, the code %~dp0 evaluates to the full path of the running file, Chkws. Appending UTILS to that path means that you need to place any utilities in a UTILS subdirectory under the directory that contains Chkws. If you want to store the utilities in a different location, you can change this variable to your preferred directory's path.

The For command in the code at callout B transforms the command arguments' values (i.e., the keywords) into environment variables, which provides for easy reference and use later on. Note that the script checks for as many as four command arguments (%2, %3, %4, and %5), which lets Chkws perform as many as four different operations during one run (providing that you add more operations to the script). The next few lines determine that the UTILS directory exists and that it contains the required files. Chkws uses the Find command to accomplish these tasks, but this time the script checks for the environment variables set earlier. If the %IE_VER% variable is set to the letter Y, the script looks for reg.exe. If the %UPTIME% variable is set to the letter Y, the script looks for uptime.exe. Because the script uses the Find command, if additional commands require the same utility, you can simply add the new command's argument after the Echo command to prompt the script to perform the same check.

The code at callout C kicks into action the script's main subroutine, called Work. This code passes each workstation name in list.txt to the Work subroutine.

The Work subroutine, which callout D shows, begins by displaying the workstation's name so that you can see the script's progress. The next line uses the Ping command to verify the connection to the workstation. This Ping command has two switches. The —l 16 switch shortens the ping packet from the default 32 bytes to 16 bytes. The —n 2 switch tells the subroutine to make two ping attempts. Using the default of four ping attempts can significantly slow the script's execution. Although having just one ping attempt would provide the quickest execution time, you run a slight risk of the ping not returning from a powered-on workstation.

The subroutine pipes the Ping command's results to the Find command, which looks for the string bytes=16. This string is present only when a ping is successful. Because you don't need to use any of the Find command's output (i.e., filtered text), the subroutine pipes the Find command's output to NUL. Of interest is the Find command's exit code, or error level. To check the error level, you can use the If command with the Errorlevel clause. The error level is based on whether the preceding command executed successfully (typically denoted by an error-level value of 0) or failed (typically denoted by an error-level value of 1 or higher). The Find command returns an error-level value of 1 if it doesn't find the specified search string. Thus, in this case, a value of 1 means that the Ping command failed because the workstation didn't respond. If the Ping command fails, the subroutine reports this finding in the log file report.txt, then uses the Goto command to send the script's flow to the end of the file (EOF) to stop any further processing of that workstation.

This code might appear complicated, but it's really only three commands: the Ping command, the If command with the Errorlevel clause, and the Goto command. I put these commands on one line (it appears on two lines in Listing 1 because of column-length restrictions) because I wanted to group together the commands that perform one task.

Assuming that the Ping command succeeds, the Work subroutine attempts to connect to the workstation by checking that a built-in network share exists. If this test fails, the subroutine records the failed connection in the log and goes to EOF. (If your user account doesn't have administrator access to the workstations, you would need to use the Net Use command to connect to them.)

Assuming that the connection was successful, the Work subroutine performs the specified operations. The subroutine determines whether the For command in callout B sets the %UPTIME% and %IE_VER% variables to the letter Y. If the %UPTIME% variable is set to the letter Y, the subroutine runs uptime.exe. Uptime.exe needs only one argument—the name of the workstation—which the subroutine provides by passing in %1. After uptime.exe executes, the subroutine pipes the results to the log.

If the %IE_VER% variable is set to the letter Y, the subroutine runs reg.exe. To obtain the IE version, the subroutine uses the Reg Query command followed by the IE registry subkey. The /v Version switch tells the utility to specifically retrieve the Version subkey. Figure 1 shows sample output that reg.exe returns. Because only the version number is relevant, the subroutine uses the Find command to filter reg.exe's output for the line that contains the string Version. To isolate the number, the Reg Query and Find commands are embedded in a For command. The For command's /f "tokens=2 delims=Z" options tell the subroutine to parse (/f) the version line, using the letter Z as the delimiter (delims=Z), and return the second token (tokens=2).

If you're familiar with reg.exe, you might know that this utility returns information that's delimited by tabs. Although you can use a tab character as a delimiter in the For command, getting that character to appear in sample code is difficult. In this case, using the letter Z as the delimiter achieves the same result as using a tab. Documentation about using the delims= and other options is available by typing

                              For /?

at a command prompt.

In the For command, note the single quotes that enclose the Reg Query and Find commands. Using single quotes inside a For loop is a handy way to have the For command process another command's output. Any commands that appear inside the single quotes will run before any other commands in that line of code. The output of the command sequence inside the single quotes is actually an argument to the For command.

When you use single quotes for this purpose, you need to watch for reserved shell characters, such as the pipe (|) and ampersand (&). When a .bat or .cmd file executes, the command processor processes any reserved shell characters before executing the command in which they appear. Hence, the reserved shell characters aren't passed as arguments to the command. Typically, this processing strategy doesn't present a problem. However, it does when the reserved shell character appears in single quotes inside a For loop. Fortunately, you can easily avoid any problems by placing a caret (^) in front of that reserved shell character. The caret tells the command processor to interpret the symbol as the literal character (e.g., |, &) and pass it along as an argument rather than interpret and process the symbol as a reserved shell character.

A similar problem occurs if you need to display reserved shell characters on screen or send them to another file. Once again, if you want the command processor to interpret the symbol as the literal character and not a reserved shell character, simply place a caret in front of that character.

The Work subroutine ends by going to EOF, thereby terminating the For loop. The script's flow then proceeds to the line that appears under the code at callout C. This line releases the environment variables set earlier. After displaying the message Finished, the script opens Notepad and displays the log's contents.

How to Add Operations
Adding operations to Chkws is simple. You just need to modify four areas:

  • In the code at callout A, add the operation's keyword.
  • In the code at callout B, add code that checks for any required utilities. (If no utilities are required, this modification isn't necessary.)
  • In the Work subroutine at callout D, add an If command that performs the operation.
  • In the Syntax subroutine, add the keyword and description of the operation.

For example, suppose you want to add an operation that determines the service pack level installed on each workstation. First, you add a keyword such as spver to the list of valid keywords contained in the second line of callout A. The code would then read

                              echo uptime ie_ver spver |                               find /i "%2" >nul & if                               errorlevel 1 goto SYNTAX

Checking the service pack level requires the use of reg.exe. Because the script already determines that reg.exe exists, you can simply insert the %SPVER% environment variable in the reg.exe code at callout B. The resulting code looks like

                              echo. %IE_VER% %SPVER% |                                find "Y" >nul & if not                                errorlevel 1                                if not exist %UTILS%\reg.exe                                goto NOREG

The line that actually does the work will be inserted at callout D and look like

                              if .%SPVER%.==.Y. for /f                                "skip=4 delims=Z tokens=2*"                                %%v in ('%UTILS%\reg query                                "\\%1\HKLM\Software\Microsoft  Windows NT\CurrentVersion"                                /v CSDVersion') do echo                                %1,%%v                                >>"%TEMP%\report.txt"

Finally, in the Syntax subroutine, you add the operation's keyword and description. The code to add is

                              echo          SPVER   (checks                                service pack level)

Make the Investment
Nearly any type of workstation query can be incorporated into Chkws, even queries that use VBScript, Windows Script Host (WSH), or Perl files. The investment in time to get Chkws working in your network environment will pay off down the line.