Disk defragmentation is an important aspect of routine system maintenance. Windows 2000 provides a built-in defrag utility that can be started only by an administrator using Microsoft Management Console (MMC). To automate this utility, you must send keystrokes to the GUI—a process that can be awkward and error-prone. Windows XP and later includes a command-line defrag utility called Disk Defragmenter (defrag.exe) that's much easier to automate. However, Disk Defragmenter isn't without limitations. For example, you can execute it on only one volume at a time—it won't run on multiple volumes simultaneously. In addition, you can specify only one volume name at a time and the utility sends output to the command window only.
I wrote a VBScript script called XDefrag that works around some of these problems and extends the usefulness of Disk Defragmenter. XDefrag provides the following value-added features:
- It lets Disk Defragmenter sequentially defrag multiple volumes.
- It lets Disk Defragmenter sequentially defrag all local fixed drives, without having to create different batch files for different computers if there are different local fixed drive letters.
- It can record all status information in the Application event log. Each report contains a start time, end time, elapsed time, and exit code.
You can find XDefrag on the Windows Scripting Solutions Web site. Go to http://www.windowsitpro.com/windowsscripting, enter InstantDoc ID 43924 in the InstantDoc ID text box, then click the 43924.zip hotlink. Before you download this script, though, you should know how to run it and how it works. Specifically, it helps to understand how the Main subroutine, DefragVolume function, and Elapsed function work.
There are only two requirements to run XDefrag. First, your machines must be running XP or later. Second, you must use Windows Script Host's (WSH's) CScript to run XDefrag because the script sends its output to the command window. From that point, you can redirect the script's output to a file if desired.
To set CScript as the default host, enter the following command on the command line:
cscript //h:cscript //nologo //s
XDefrag and all your other scripts will then automatically run under CScript. Alternatively, if you want to use CScript for just this script, you can preface the script's launch command with cscript.exe.
The command to launch the script uses the following syntax:
\[/E\] \[/A\] \[/F\] \[/V\]
(Although this command appears on several lines here, you would enter it on one line in the command-shell window. The same holds true for the other multiline commands in this article.) If you specify the /L switch, the script will sequentially process all local fixed drives. Or, you can specify one or more volumes, where volume can be in the format d: or d:\volume\mountpoint. If you specify two or more volumes, the script will process them sequentially.
The /E switch tells the script to record its activities in the Application event log. If defrag returns a nonzero exit code, the event log will show an error. Figure 1 shows a sample event-log entry. The script's /A, /F, and /V switches are the same as the Disk Defragmenter's options of -a (analyze only), -f (force defragmentation, even if free space is low), and -v (verbose output), respectively.
To schedule the script to run, you can use the Scheduled Tasks window or use the Schtasks command. In either case, because the script requires CScript, I recommend specifying cscript.exe as the executable when creating the scheduled task because CScript isn't the default scripting host on a fresh OS install. If you specify cscript.exe as part of the command, you'll avoid the potential problem of the command not doing what's expected because WScript is executing it. If you were to run the Schtasks command
/rp password /sc DAILY
/tn Defrag_Local_Drives /tr
/E /L /F /V" /st 02:00:00
the current computer will be configured to run a task called Defrag_Local_Drives daily at 2 a.m. The /ru and /rp options specify the credentials under which the task should run, so you need to customize administrator and password to use this command. The string after the /tr switch specifies the command to execute. Note that because the command contains spaces, you must enclose it in quotes.
One advantage to using the Schtasks command is that it can create scheduled tasks on remote computers, so you can construct a batch file to automate scheduled deployment to multiple systems. For more information about how to use the Schtasks command, type
Inside the Main Subroutine
XDefrag starts by declaring a set of global variables, then calls the Main subroutine. The script's main body exists in the Main subroutine, which has a local scope, rather than in code that has a global scope. All global variables use the g_ prefix to distinguish them from local variables.
As Listing 1 shows, the Main subroutine first calls the ScriptHost function to determine the name of the executable that started the current script. If it isn't CScript, the script exits with an error message.
To parse the command-line arguments, the Main subroutine takes advantage of the WScript.Arguments.Named and WScript.Arguments.Unnamed collections. The Named collection contains named command-line arguments (i.e., arguments that start with a forward slash) and WScript.Arguments.Unnamed contains unnamed arguments (all other arguments). When a named argument exists, the Exists method returns a value of True. In keeping with standard conventions, the script exits with a short usage message when the /? option is present on the command line.
As callout A shows, the Main subroutine checks to see whether the /L switch exists on the command line. If so, the subroutine calls the LocalFixedDrives function. This function returns a collection of local fixed drives by iterating the FileSystemObject object's Drives collection, checking the DriveType property for each drive in the collection, and building a semicolon-delimited list of fixed drives. When exiting, the LocalFixedDrives function calls VBScript's Split function to return an array, which can be iterated the same way as a collection.
If the command line doesn't contain /L, the Main subroutine assigns the Unnamed collection to the colVolumes variable. If this collection's Count method returns a value of 0, the command line contains insufficient information to continue and the script exits with an informational error message.
Next, the Main subroutine populates a Scripting.Dictionary object, as callout B in Listing 1 shows. The DefragVolume function uses the Dictionary object in its report output. This object contains Disk Defragmenter exit codes and a textual description of each one. For more information about the exit codes, see the Microsoft article "How to Provide Event Logging for the Disk Defragmenter Utility with Windows Script Host" (http://support.microsoft.com/?kbid=294743).
Finally, the Main subroutine processes the volumes sequentially by iterating through the colVolumes collection and passing each member of the collection to the DefragVolume function. The script's exit code will be the highest exit code returned by all calls to DefragVolume.
Inside the DefragVolume Function
The DefragVolume function calls the Disk Defragmenter to defrag a specific volume, then the function produces an activity report. The function's return value is a number corresponding to the Disk Defragmenter's exit code. Besides returning the exit code, DefragVolume plays two other important roles. First, it logs the event to the event log if /E was specified on the command line. Second, it displays the report output in the command window; you can redirect the output to a file if desired.
Listing 2 shows the DefragVolume function. As callout A in Listing 2 shows, DefragVolume calls the GetTempFileName function, which uses the FileSystemObject object's BuildPath, GetSpecialFolder, and GetTempName methods to create a temporary file. The script uses the temporary file to capture Disk Defragmenter's output for reporting purposes. To make sure that a file by that name doesn't already exist, GetTempFileName checks existing filenames by using the FileSystemObject object's FileExists method within a Do...Loop statement.
Next, DefragVolume builds the command that the script will use to launch Disk Defragmenter. This command follows the syntax
volume \[options\] > tempfile
where volume is the volume to be defragged, options are the command-line arguments you want to use with Disk Defragmenter, and tempfile is the name of the temporary file just created. Note that this command uses the > redirection symbol to save a copy of Disk Defragmenter's output to the temporary file.
After building the launch command, DefragVolume uses VBScript's Now function to record Disk Defragmenter's start time, then uses the WshShell object's Run method to execute the command, as callout B in Listing 2 shows. After Disk Defragmenter finishes, DefragVolume records the end time.
To retrieve Disk Defragmenter's output, DefragVolume creates a TextStream object by using the FileSystemObject object's OpenTextFile method to open the temporary file. DefragVolume uses the ReadAll method to read the contents of the temporary file into a string. Next, DefragVolume closes the file by using the TextStream object's Close method, then uses the FileSystemObject object's DeleteFile method to delete the file from disk.
The DefragVolume function then builds a report following the format that Figure 2 shows. The code at callout C in Listing 2 builds the report. Note that the function determines the description for the exit code by looking up the error's description in the g_objErrors Dictionary object.
If event logging was requested on the command line (i.e., the /E switch was included), DefragVolume uses the WshShell object's LogEvent method to write the report to the event log. When Disk Defragmenter returns an exit code of 0, the event type is 0 (success); otherwise, the event type is 1 (error).
Finally, DefragVolume uses the WScript object's Echo method to display the report in the command window. Having the report in the command window lets you redirect the output to a file if desired. You can use the > or >> redirection symbols after the launch command to save the report to a text file. For example, the command
/V > C:\defragreport.txt
defrags all local fixed drives and redirects the output to the C:\defragreport.txt file.
Inside the Elapsed Function
As I mentioned previously, DefragVolume uses the Now function to retrieve the current date and time both before and after it calls Disk Defragmenter (i.e., retrieve the start and end times). To determine the elapsed time, DefragVolume calls the Elapsed function, which uses these start and end times to return a formatted string representing the elapsed time.
As Listing 3 shows, the Elapsed function works by using VBScript's DateDiff function to determine the number of seconds difference between the start and end times. The Elapsed function uses integer division (\) to divide the resulting number by 3600 (the number of seconds in an hour) and determine the number of hours. If the elapsed time is 1 hour or more, the function subtracts the hours from the number of seconds, leaving the number of seconds remaining after the hours have been subtracted. The function then performs similar operations for the number of minutes. Finally, the Elapsed function uses the LeadingZero function for each portion of the elapsed time to return a formatted result.
A Useful Wrapper
XDefrag is simply a wrapper for XP's built-in Disk Defragmenter. As such, XDefrag can't overcome all of Disk Defragmenter's limitations. Third-party software can provide more advanced features than XDefrag, such as detecting fragmentation levels and running only when necessary. However, XDefrag is a no-cost solution for environments in which administrators would like a bit more flexibility and the ability to record defrag activity to the event log.