Scripting languages provide
far more than batch files do in the way of flow control and error
handling support. In addition, you can debug your scripts with greater
ease because Microsoft includes a script debugger with Windows. If you
find this script debugger a tad limited (many people do), you can also
use third-party utilities to debug your scripts. You can learn more
about the Microsoft Script Debugger at http://msdn2.microsoft.com/en-us/library/ms875975.aspx.
The following sections discuss some script techniques you can use to
create robust script applications in a command line environment.
1. Mapping a Network Drive
You can map a network
drive using a batch file, but it's more difficult and error prone than
using a script. A script can provide one thing that a batch file can't
in this case, great interactivity. Using a script lets you interact with
the user in a way that would be difficult using a batch file. In
addition, the script provides a modicum of additional error handling
support that makes error handling easier. Listing1
shows a typical example of how you can implement this functionality
using JavaScript.
Using Scripting Effectively
Sometimes you do need to
use scripting techniques to ensure you get the right results from your
command line activities. The best rule of thumb to follow is that
anything that requires direct application access through something other
than command line switches requires a script, rather than a batch file.
In addition, if you've just spent five hours trying to get a batch file
to work and feel that you still haven't made any progress, then perhaps
you're not using the right tool for the job.
It's this
second point where many people get into endless discussions about the
suitability of one technique over another. In many cases, it's part
personal preference and part skill or special need. For example, at one
time some people tried to use spreadsheets in place of word processors
(it really was common in the 1980s). However, anyone who has used both
products today knows that each tool has a particular job to perform and
it's better to use the right tool for the job. The same rule applies to
scripts versus batch files. You might be able to use batch files to meet
most of your needs, but eventually, you'll run into a complex task that
simply requires a script to perform adequately.
|
Example 1. Mapping a Network Drive with JavaScript
// Define the network object used to map the drive.
var oNetwork = new ActiveXObject("WScript.Network");
// Detect a request for command line help.
if (WScript.Arguments.length == 1)
if (WScript.Arguments(0) == "/?")
{
// Display the help information
WScript.Echo("Usage: MapNetwork <letter> <UNC target>\n");
// Exit the script and provide an error level of 1 to
// indicate a help request.
WScript.Quit(1);
}
else
{
// Display an error message.
WScript.Echo("Input argument is unknown.");
WScript.Echo("Usage: MapNetwork <letter> <UNC target>\n");
// Exit the script and provide an error level of 2 to
// indicate a data entry error.
WScript.Quit(2);
}
// Create variables to hold the drive letter and the UNC location.
var DriveLtr;
var UNCName;
// Detect the correct number of input arguments.
if ( WScript.Arguments.length < 2 )
{
// Ask whether the user wants to continue.
WScript.Echo("No input provided! Provide it interactively? [Y | N]")
; var Answer = WScript.StdIn.ReadLine();
// If the user doesn't want to continue, display help and exit.
// Use an exit code of 2 to indicate a data entry error.
if (Answer.toUpperCase() == "N")
{
WScript.Echo("Usage: MapNetwork <letter> <UNC target>\n");
WScript.Quit(2);
}
// Input the drive letter.
WScript.Echo("Type the local drive letter (X:).");
DriveLtr = WScript.StdIn.ReadLine();
// Input the UNC drive on the remote machine.
WScript.Echo("Type the UNC location (\\MyServer\MyDrive).");
UNCName = WScript.StdIn.ReadLine();
}
else
{
// Obtain the required inputs from the command line.
DriveLtr = WScript.Arguments(0);
UNCName = WScript.Arguments(1);
}
// Tell the user which drive is mapped.
WScript.Echo("Mapping drive " + DriveLtr + " to " + UNCName);
// Attempt to create the connection.
try
{
// Perform the drive mapping function.
oNetwork.MapNetworkDrive(DriveLtr, UNCName);
}
catch(e)
{
// Display an error when the task fails.
WScript.Echo("Couldn't map the drive!\n" + e.description);
WScript.Quit(3);
}
|
The example begins by creating a network object to create the connection. In this case, the code uses the new ActiveXObject() method. You can also use WScript.CreateObject() to perform the same task. The method you use depends on personal taste in most cases. This example uses the ActiveXObject() method for the sake of completeness. If you want to use the other method, you would replace this line of code with var oNetwork = WScript.CreateObject("WScript.Network");.
Of course, you also need to handle the case where someone provides a single input, but it isn't the /?
command line switch. The code displays a special error message along
with the same help that you would normally display for the /?
command line switch. Notice that in this case the script exits with an
error level of 2. Using a different error level lets you trap this
particular problem in a batch file.
At this point, the
code begins looking at the input. The input must provide two arguments
to map a network drive to a local drive letter. Consequently, when the
script detects two input arguments, it places them in the appropriate
variables and attempts to map the network drive. You might wonder why
the script doesn't perform all kinds of odd error checking on the input
arguments. The try...catch statement is the secret in this case. If the user provides incorrect input, the oNetwork.MapNetworkDrive(DriveLtr, UNCName)
call fails and the catch part of the statement traps the error. The
script displays an error message in this case and exits again. Because
this is another kind of error, the script sets the error level to 3.
Notice that the script conveniently disregards any more than two inputs.
At this point, all the
code needs to handle is the case where the user doesn't provide any
input arguments. This is where the interactive features of scripting pay
off. The script begins by asking the user whether they want to provide
the input interactively. If so, the code asks some simple questions and
tries to map the drive. If not, the code exits with a help message and
an error level of 2. The reason the script uses an error level of 2 is
that this is the same kind of error as providing a single input that
isn't the /? command line switch.
2. Creating a CSV File
Sometimes it's important
to see the same example using two different techniques. When you compare the code in Listing 2 with the code in 5.7, you'll notice that Listing 6.4 is significantly longer, even though it produces the same output. In addition, the code in Listing 2
is significantly more complex. However, if you perform just these two
comparisons, you'll miss some of the reasons to use scripts. Mostly
notably, the script version demonstrates the flexibility that this form
of coding can provide. For example, you have more control over the
files. The input files are read only, which means that the code can't
damage them, even accidentally. Consequently, the files are safer than
when you use a batch file to manipulate them. Listing 2 shows the script version of the CSV output example.
Example 2. Creating CSV Output Using a Script
// Create a File System Object to work with files.
var FSO = WScript.CreateObject("Scripting.FileSystemObject");
// Determine whether the Output2.CSV file exists and delete it.
if (FSO.FileExists("Output2.CSV"))
FSO.DeleteFile("Output2.CSV", false);
// Create a WshShell object to obtain environment variables.
var Shell = WScript.CreateObject("WScript.Shell");
// Create variables to hold the static data.
var CompName = Shell.ExpandEnvironmentStrings("%COMPUTERNAME%");
var UserName = Shell.ExpandEnvironmentStrings("%USERNAME%");
var DateTime = new Date();
// Obtain the list of file specifications.
WScript.Echo("Locating temporary files to delete.");
var DirSpec = FSO.OpenTextFile("DelFiles.TXT", 1);
// Process each entry in the file.
while (!DirSpec.AtEndOfStream)
{
// Get a single file specification.
var ThisSpec = DirSpec.ReadLine();
// Process the directory specification.
WScript.Echo("Adding database values for " + ThisSpec);
Shell.Run(
"Cmd /C Dir " + ThisSpec + " /B /S > TmpDirFiles.TXT", 0, true);
// Open the file containing the individual file entries.
var Files = FSO.OpenTextFile("TmpDirFiles.TXT", 1);
// Open the CSV file to accept the file entries.
var Output = FSO.OpenTextFile("Output2.CSV", 8, true);
// Process each of the file entries in turn.
while (!Files.AtEndOfStream)
{
// Get an individual file entry.
var File = Files.ReadLine();
// Create the CSV file entry. Begin with the computer name and
// the username.
Output.Write("\"");
Output.Write(CompName);
Output.Write("\",\"");
Output.Write(UserName);
Output.Write("\",\"");
// Processing the date requires a little additional work. You
// must extract the individual elements and put them together as
// desired. Begin by converting the day number to a day string.
var DayNum = DateTime.getDay();
switch (DayNum)
{
case 0:
Output.Write("Sun ");
break;
case 1:
Output.Write("Mon ");
break;
case 2:
Output.Write("Tue ");
break;
case 3:
Output.Write("Wed ");
break;
case 4:
Output.Write("Thu ");
break;
case 5:
Output.Write("Fri ");
break;
case 6:
Output.Write("Sat ");
break;
}
Output.Write(DateTime.getMonth() + 1);
Output.Write("/" + DateTime.getDate() +
"/" + DateTime.getFullYear());
Output.Write("\",\"");
// Extract the time from DateTime.
Output.Write(DateTime.getHours() + ":" +
DateTime.getMinutes() + ":" +
DateTime.getSeconds());
Output.Write("\",\"");
// Finally, add the filename to the output.
Output.Write(File);
Output.WriteLine("\"")
}
// Close the working files
Files.Close();
Output.Close()
}
// Close the file containing the file specifications.
DirSpec.Close();
|
The code begins by
removing any existing output file. JavaScript and VBScript lack file
support. However, you have access to the Scripting.FileSystemObject
object, which does provide full file system support. You can use this
object to perform a multitude of tasks with files, including creating,
deleting, and editing them. The FileSystemObject also includes functionality for working with folders.
The next step is to retrieve the username, computer name, date, and time. In many cases, you can simply use the ExpandEnvironmentStrings()
method to obtain the information you need from the system. Notice that
the example code uses the Date object in place of obtaining the date
from the environment variables using the Shell.ExpandEnvironmentStrings("%DATE%") method. When working with JavaScript, you can only access the environment variables that you can see with the Set
command. JavaScript doesn't support the extended functionality that's
available at the command line. In fact, you'll find that this general
rule applies to both VBScript and JavaScript; neither scripting language
supports the extensions that you can access from a batch file at the
command prompt. The Date object also provides time support, so you don't
need a separate Time variable.
At this point,
it's time to begin collecting a list of temporary files on the system.
This example, like its batch file counter, relies on an external file to
hold the file specifications. The code opens the file and begins
processing it one line at a time. The use of a constant value of 1 for
the FSO.OpenTextFile() method opens the
file in read-only mode. The code processes the file one line at a time
(one file specification at a time) using the DirSpec.ReadLine() method. You can read one character at a time using the DirSpec.Read() method instead.
This example points out a very special feature of the scripting languages. Notice the use of the Shell.Run()
method. You can use this method to run any application. To use this
feature at the command prompt, you have to begin by creating a command
processor using the CMD utility as shown. In this case, the code runs
the Dir command with the file specification obtained from DelFiles.TXT. This line of code begs the question of why the code doesn't use the FileSystemObject. In this particular case, you can perform the task faster and without any loss of functionally by using the Dir
command. The point is that you don't always have to use a scripting
object; sometimes a command line tool works just as well or even better.
The code now has two files to work with. The first is an input file, TmpDirFiles.TXT, which contains the list of temporary files. The second is an output file, Output2.CSV, which contains the database of file entries. The FSO.OpenTextFile()
constant of 8 opens the file in append (read/write) mode. If the file
doesn't exist, the code raises an error unless you also set the third
argument to True, which tells the method to create the file when it
doesn't exist.
Now all the code needs
to do is process the data and output it. The user and computer names
are straightforward. Processing the date requires the most code because
the code has to put the date string together. The downside of all this
code is that it makes the example harder to read than the batch file.
The plus side is that you can create a date string in any format
required, even nonstandard formats.
As
a final note on this example, make sure you close files when you finish
working with them. Otherwise, the script raises an error when you try
to open the file again. In some cases, the file could remain open until
you reboot the system, making it inaccessible to everyone.