Remember when you first learned how to write scripts? I remember thinking that, all in all, scripting wasn't so hard to master. A few dozen scripts later, I began to reckon that scripting was a bit boring. After I passed the threshold of writing more than a hundred scripts, I faced just one certainty: There has to be a better way to create scripts.
Windows Script Host (WSH) 5.6 can help you find a better way through its expanded support of arguments. WSH has always supported arguments. However, WSH 5.6 has taken this support to the next level.
Let's look at how WSH 5.6 supports arguments compared with earlier versions. All the code that I use requires WSH 5.6. If you're running any other version, go to http://msdn.microsoft.com/downloads/default.asp and navigate to Web Development, Windows Script, Windows Script 5.6. WSH 5.6 is 100 percent backward-compatible, so upgrading is painless.
Arguments in Previous WSH Versions
WSH 5.5 and earlier support arguments only through the WshArguments collection. This collection object holds all the command-line arguments that a script receives from its caller (i.e., the person launching the script). When you launch a WSH script by double-clicking the filename, you're running it without arguments. When you launch a script from the Run dialog box, you can pass arguments to WshArguments. For example, the command
MyScript.vbs A B C
passes three arguments (i.e., A, B, and C) to WshArguments.
The WshArguments object isn't externally creatable. In other words, you can't use the CreateObject method to obtain a reference to it. Instead, you must use a method or property of another object. Specifically, you use the Arguments property of the WScript object (WSH's root object) in code such as
Set args = WScript.Arguments
In WSH 5.5 and earlier, WshArguments is an ordinary collection that basically lets you retrieve, count, and list the items it contains. To retrieve an argument, you use WshArguments' Item property. When you use this property, you must include a 0-based index number that specifies the argument's position on the command line. For example, if you want to access argument A in the previous command, you include the index number 0 in code such as
Item is WshArguments' default property. As such, you can omit it in VBScript code. Thus, both of the following MsgBox statements are correct and successfully display the first argument:
Although WshArguments' programming interface might appear powerful, it has some structural limitations. First and foremost, in scripts, you can identify arguments only by their position on the command line. Because of this functional limitation, managing optional arguments requires a lot of effort. Second, to launch a script, all the callers must know all the argument details, including the order of the arguments, the expected values, and the syntax to use. Requiring such close cooperation between scriptwriters and callers can lead to frustration on both sides. On one side, every time the scriptwriters want to change or add an argument to satisfy one caller's needs, they must inform all the callers about the changes. On the other side, scriptwriters' changes can break the existing code. Callers might come in one morning to find that the script they've successfully run many times before is suddenly laden with mysterious errors.
Arguments in WSH 5.6
Like its predecessors, WSH 5.6 lets you access arguments by specifying their position. Unlike its predecessors, WSH 5.6 also lets you access arguments by name, thanks to the addition of named arguments.
A named argument is a command-line argument that you've qualified with a name. You can express a named argument in three formats:
- String. The String format has the syntax /name:value, where name is the argument's name and value is the argument's string value.
- Simple. The Simple format has the syntax /name. In this case, the argument has a name and an empty value. This format is useful for arguments that specify an optional condition that's either present or not present. The presence of the argument alone provides enough information for the script, so a value isn't necessary.
- Boolean. The Boolean format uses the syntax /name\[+|-\], where + is true and is false. WSH automatically translates the + and into their respective Boolean values. This format is ideal for arguments that toggle on and off a state that's internal to the script.
A command-line argument must follow one of these formats for WSH 5.6 to handle it as a named argument. WSH 5.6 places all the command-line arguments that match one of these syntaxes into the WshNamed collection. WSH 5.6 places all the other command-line arguments into the WshUnnamed collection. Both WshNamed and WshUnnamed are subsets of WshArguments, so to access them, you use the WshArguments object's Named and Unnamed properties, respectively.
Of the three formats, you'll likely use the String format most often, so let's take a closer look at how this type of named argument works. Specifically, let's look at how you use String-formatted named arguments on the command line and in scripts.
On the Command Line
As the /name:value syntax for the String-formatted named argument shows, you place a slash (/) before and a colon (:) after the argument's name. The argument's value begins immediately after the colon and terminates with the first space. For example, the command
specifies one named argument whose name is server and whose value is expoware. If you want the argument's value to be a string that includes spaces, you must enclose the string in quotes. For example, the command
launches MyScript.vbs and passes in one named argument whose name is errormsg and whose value is error message.
Listing 1 contains Arguments.vbs, a simple script that displays information about the named and unnamed arguments WSH receives on the command line. The script itself supports one optional named argument called Verbose. When you set this argument to true or yes, the script returns details about the arguments. Otherwise, the script just outputs the number of each type of argument.
If you run Arguments.vbs with the command
/Verbose:yes Scripting Solutions
you'll receive the output that Figure 1 shows. This output reveals that WSH recognizes two named arguments—Verbose and System—and three unnamed arguments. The Verbose and System named arguments have the values of yes and Win2K, respectively. Interestingly, WSH recognizes the named arguments in the same order no matter where you place them on the command line. For example, if you use the command
you'll receive the same results as those in Figure 1.
WSH 5.6 processes command-line input a special way. First, WSH parses the command to locate individual strings. WSH assumes that spaces separate items, except when it comes across quoted strings. WSH considers a quoted string as one item no matter how many spaces the string contains. Then, WSH places all the items in the standard WshArguments collection. In this collection, an item has no name and the entire string is the item's value, even if an item follows the /name:value syntax. For example, consider the command
At this stage of processing, WSH has placed two items in WshArguments: /server:expoware and file.txt. (This behavior also occurs in versions earlier than WSH 5.6.)
Next, WSH parses each string in WshArguments to determine whether the string is a named or unnamed argument. If the parsed string matches the /name:value pattern, WSH copies and adds the string to the WshNamed collection. If the parsed string doesn't match the /name:value pattern, WSH copies and adds the string to the WshUnnamed collection.
For example, in the previous command, the string /server:expoware matches the /name:value pattern, so WSH copies and adds this argument to WshNamed. This argument has the name server and the value expoware. The file.txt string doesn't match the pattern, so WSH adds this argument to WshUnnamed.
WshNamed is a collection object, so it features the typical programming interface of collections. Like other collections, WshNamed includes the Item property (which retrieves the specified item from WshNamed), Count method (which retrieves the number of items in WshNamed—used in VBScript code), and Length property (which retrieves the number of items in WshNamed—used in JScript code). WshNamed also includes the Exists method, which determines whether WshNamed contains an item whose name matches the specified text. For example, Listing 2 contains code that uses Exists to look for an argument named server.
Before you can use Exists or any other WshNamed method or property, you need to retrieve the WshNamed collection. To do so, you use the WshArguments' Named property. If the caller didn't specify any named arguments on the command line, the Named property returns an empty collection.
To retrieve a named argument in WshNamed, you use the Item property. You use WshNamed's Item property in a slightly different way than I used WshArguments' Item property earlier. With WshNamed's Item property, you don't use a 0-based index but rather a string that evaluates to the argument's name, as the code at callout A in Listing 2 shows. This code retrieves the value of the named argument called server. Notice that the argument's name doesn't include the leading slash or the trailing colon. You don't use these characters when working with a named argument in a script; you use them only when you enter a named argument on the command line. Also notice that the code doesn't include the Item property. Like WshArguments' Item property, WshNamed's Item property is the default, so you can omit it in VBScript code.
If you don't know the names of the arguments in WshNamed, you can take advantage of the code that Listing 3 shows. This code iterates through the collection and displays the name and value of each argument.
A Quantum Leap
The introduction of named arguments in WSH 5.6 is a quantum leap from the previous versions of WSH. The ability to give arguments a unique name has practical advantages for callers and scriptwriters. If you name every argument, callers will find a script easy to launch because they won't need to know or remember all the argument details. They only need to know about the arguments that pertain to them. And if you change the script, you don't need to train all the callers again, just those callers affected by the changes. Finally, because you have the flexibility to identify parameters by name and position, you can enhance existing scripts while minimizing the risk of breaking the code.