Files that contain a lot of output from many data-capture points—performance-metrics log files, for example—can get large rather quickly. Anyone who has used Notepad to open a large text file that contains several weeks' or months' worth of data knows that getting the file to open, let alone finding an individual piece of information within the file, can be prohibitively time-consuming. And sorting through several hundred log files to locate the one you need can be frustrating. Yes, you can use the last-modified date to locate files, but if someone has opened and edited a file, the last-modified date might not truly reflect the date of the information in the file.
If these scenarios sound familiar, consider timestamping. (The term timestamping can describe several techniques of associating files, or the data in files, with specific capture dates or times. For the purposes of this article, I use the term to refer to associating a file or data with both the date and time of capture.) You can close out the log weekly, use timestamping to rename the file with a date-and-time—related filename, then place the file in a date-named folder. This process gives you smaller, well-organized files that are much easier to locate, open, and scan for specific data. If a file contains data from multiple data-capture points, you can Echo the date/timestamps for each collection point to the output log file, then search for a specific date and jump directly to the associated log entry. Such centralized reports can also help with script debugging and scheduling. The information in this article applies primarily to Windows 2000 and later systems.
Benefits of Timestamping
Timestamping can simplify the process of diagnosing script coding or run problems. Capturing script start and finish times can also help you plan and verify your script-scheduling strategy in several ways.
First, you can use this information to plan scheduled tasks so that they execute in a logical fashion and don't overload your scheduling server. In my environment, for example, we use an administrative server as the central scheduling location for most of our scripts. At any given time, almost 70 scripts are running repeatedly as often as every 5 minutes. During any 24-hour period, as many as 1000 script runs occur. Therefore, we need to have a handle on how long each script takes to finish running. We can use Task Scheduler to view each script's start time (in Settings) and finish time (the Task Scheduler logs retain start and finish times for a short period), but capturing both times to the data file that the script produces is more straightforward.
Second, you can add the code that Listing 1 shows to your scripts to Echo the start- and finish-time data to a centrally located report. You can compare the data in this report with your Performance Monitor logs to evaluate the server-resource requirements for your scripts. (CPU and memory usage are the most common resource bottlenecks, but network throughput can be a problem as well.) Note that the code's If Exist statement will let the Echo code run only when the specified log file exists. To turn on central logging, create the file \\servername\centrallogs\log.txt. During the next script run, the script will detect that file's presence and begin central logging. When you finish collecting information, simply rename the log.txt file (make sure that no jobs are in progress when you do so). The Echo code will remain dormant until you create a new log.txt file.
Another benefit of collecting timestamps into a central report is to confirm that all your scripts are running to completion. You might find that a script you thought was collecting data only seemed to be working properly. Many things (e.g., permissions problems, network outages, incorrect input information) can cause a script to terminate prematurely, and a script that runs to completion today might not do so 6 months from now, even if the code never changes. Depending on the script's code, it might handle errors gracefully and continue to run to completion, or it might exit prematurely or hang until Task Scheduler kills the job. By reviewing the logs and confirming that all jobs have completion timestamps, you can verify that your scripts are running to completion. If you find scripts that start but never seem to finish, you need to check those scripts to be sure they're working properly and have adequate time to finish.
Timestamps can also help reveal problems in your scheduling configuration. Task Scheduler, by default, lets scripts run for as long as 72 hours before terminating the job. If you have a script with an extremely long (i.e., more than 72-hour) runtime, Task Scheduler might cut the job short. Imagine the boss calling you in and asking you to produce security-related log files for the last server in an alphabetically ordered input file, only to discover that the files exist for every server except that one (which was near the end of the list of servers and which Task Scheduler cut short). Talk about an embarrassing moment. And if you've scheduled servers to automatically shut down and restart during nonwork hours, you can use the report to confirm that all your data-gathering scripts are finishing before the shutdown occurs.
As you can see, collecting timestamp information in a centralized report can be a valuable tool. The first step, however, is to begin naming the applicable log files with date-and-time—based filenames.
Creating a Date-Based Filename
As an example, let's create a script that uses Win2K's DATE variable to create a log file that records, on a weekly basis, the local Administrator account membership on an Engineering department's computers. Win2K and later support the Date and Time variables, which eliminate the need to use Windows NT's Date /t and Time /t commands to capture the system date and time. Although these new built-in variables simplify the process of retrieving date and time information and eliminate the need for NT's Now utility, the variables still produce output that includes forward slash (/) and colon (:) characters. This fact is a problem if you try to create a filename by using unfiltered variable values. See the sidebar "The NT 4.0 Approach to Timestamping" for more information about these commands and how to filter values.
Local Administrator group membership is retained on the local machine rather than in Active Directory (AD), so for this example, let's assume that the PCs will be on when the script runs. Listing 2 shows a simple script that captures the membership information to a log file with a date-oriented filename. Note that the script uses the For command to filter out the day-of-the-week abbreviation and also uses Dsquery, a tool that comes with Windows Server 2003. If you want to perform queries against AD objects in command-shell scripts, you'll want to become familiar with this tool. The sample code uses Dsquery to capture all the computers, within the specified organizational unit (OU), with names that start with "eng" at the start of their name in a specified OU.
After you get the hang of working with date and time information, you can think about some advanced scenarios, such as how to name a file in a way that identifies the day of the week (e.g., Monday, Tuesday), the month of the year (e.g., 1, 6, 12), the quarter of the year (e.g., 1, 4), the week of the year (e.g., 1, 52) or the day of the year (e.g., 1, 365). These more advanced requirements require, at a minimum, some For commands to reorder or filter the tokens. Some of these options also need some fairly sophisticated If statements and math calculations to arrive at the appropriate date. But dedicating more than half of your script to filtering the retrieved information into the desired format can be frustrating.
Dealing with dates reveals limitations in shell scripting's built-in variables and native commands. Sometimes you can overcome these limitations by using resource kit utilities, Support Tools, or third-party utilities. But after you exhaust all the native commands and available utilities, you might find that you still aren't getting exactly what you need. Recently, as I was reading through the Microsoft Windows 2000 Scripting Guide, I discovered some great information about VBScript date functions. I immediately realized that with a little bit of coding, I could use these functions to retrieve some of the date information that had been lacking from my shell scripts' timestamping code. (And if you're new to VBScript, this exercise is a great way to begin learning VBScript.)
I took on the challenge of using VBScript to write my own version of the Now utility. By calling the resulting script, which I call ItsAboutTime.vbs (and Listing 3 shows), from inside a regular script, you can access 24 common date and time format options. You can specify one or more command-line arguments to get the custom date output you need for file and folder names or to Echo to a log file. The script uses If and Case statements, Procedures, the Exec method, and command-line arguments.
My former Pascal instructor said he could write code in any language because of his knowledge of FORTRAN; likewise, you can code in VBScript as long as you know command shell scripting. I briefly step through the code in ItsAboutTime.vbs and point out the similarities to command-shell code.
VBScript If statements, such as the one at callout A in Listing 3, are simple but require an accompanying Then statement, which isn't necessary in command-shell scripts. WScript.Arguments is a collection of all the arguments that the script operator specifies at runtime. If the script runs with no arguments, as the code "Count=0" indicates, the script shifts flow to the Help subroutine to give the user a list of possible command-line arguments. The Help menu option moves script flow to a subroutine called Help, similar to the way a Call or Goto command would do in a shell script. Wscript.Quit is the equivalent of shell scripting's Exit, and the End If statement completes the If statement.
The For Each command at callout B in Listing 3 is similar to shell scripting's For command. In ItsAboutTime.vbs, For Each performs a repetitive operation on each command-line argument. If the user wants to retrieve the time in hours-minutes-seconds-hundreths of seconds, the user would specify four switches. The script loops through the arguments and returns the specified arguments; cntr=1 is the first argument, cntr=cntr+1 is the first two arguments, and so on. If the code specifies more than one argument, the script will use a dash (-) to separate the results. The Next command is similar to the Goto :EOF command that returns script flow back to the top, similar to shell scripting's For command.
The Case statement is really the equivalent of a multiple If statement in which you test for a matching entry. The downside of multiple If statements in shell scripting is that they take a lot of space and you need to make script flow jump over the other options when a condition is True. VBScript accomplishes the same task in only one line of code per option. If the command-line argument is "ya" (which indicates year abbreviated), the script will perform the operation that follows that argument. In the code at callout C in Listing 3, if the clArg is ya, the script will write out the last two digits of the year. Notice that a Case Else statement appears at the end of the Case statement. The Case Else statement, which specifies the default that the script will use if none of the previous Case statements are true, is similar to a shell script's use of a final default statement after a series of If statements.
The Function statement which the code at callout D in Listing 3 shows, is similar to a labeled block of code in shell scripting and lets you pass in a variable or variables to retrieve results that the script can then use. The HSEC(t) function (in which you replace the "t" with a passed-in value) retrieves the hundredths of a second figure that is available through the TIME environment variable that the Exec method accesses. You can use the Exec method to run and retrieve results from other commands such as Dir or Ping. The HSEC(2) function will retrieve hundredths of a second; the HSEC(11) function will return a detailed time that includes the hundredths of a second.
A subroutine, which the code at callout E in Listing 3 shows, is another type of procedure that's similar to a Function and generally refers script flow to another location but doesn't return a value. (For more details about either of these procedures, see the Microsoft Windows 2000 Scripting Guide.) ItsAboutTime.vbs uses another subroutine to provide Help screen information.
ItsAboutTime.vbs shouldn't require any modifications; simply reference it in a shell script. For example, the script that Listing 4 shows modifies the Dsquery script that Listing 2 shows to use ItsAboutTime.vbs to create a timestamped filename. The code that also kicks things up a notch and creates a quarter-named folder, then places timestamped log file in that folder.
Take the Time
I've tested ItsAboutTime.vbs on Windows 2003, Windows XP Professional Edition, and Win2K Server and Win2K Professional with Windows Script Host (WSH) 5.6 installed (to support the Exec method). You can use the script to provide timestamps for your files and folders, increase your organization, and decrease the time you spend searching through log files.