Let's suppose you develop a script that performs some action on a computer or user account. The script works great, and now you want to run the script for a different computer. The trouble is you hard-coded the target computer name in the script. As a result, you must edit the script before you run it for the new target computer. A better approach is to provide the target computer name to the script at runtime.
To provide the computer name at runtime, you need to understand the input and output facilities in Windows Scripting Host (WSH). This month, I examine the variety of mechanisms available in WSH for getting data in and out of a script.
Understanding how to work with external data in a script is important in numerous situations. Passing arguments to a script, prompting a user for data, displaying a message, reading an input file, and writing to a log file all require knowledge of WSH's input and output capabilities. First, I review the available input and output options. Then, I demonstrate how to effectively use input and output options in a script.
Table 1, page 186, lists the input and output options that WSH and the VBScript engine provide. Some input and output capabilities that Table 1 lists appear to overlap in functionality, but the overlap exists for a reason. Recall that Microsoft uses the components that make up the Windows scripting technology in a variety of products. For example, Microsoft includes VBScript with WSH, Microsoft Internet Explorer (IE), Internet Information Server (IIS), and Visual Studio (VS). Although the two mechanisms might appear to duplicate functionality, they provide distinct capabilities unique to the primary environment that each supports.
For example, consider WScript Shell's Popup method and VBScript's MsgBox function. WScript Shell's Popup method is best suited for automated scripts because the method supports a timeout parameter. If a user doesn't respond to a Popup-generated dialog box within a specified period of time, the method silently dismisses the dialog box and the script continues. Microsoft initially designed VBScript's MsgBox function to communicate with a user via IE, so MsgBox provides a mechanism to display a graphical message to a user and capture the button the user clicks to dismiss the message box. Popup requires you to create a Shell object before you use the method. MsgBox doesn't, so MsgBox is a little more efficient. If you don't need a timeout value, use MsgBox for efficiency. If you need a timeout value, use Popup.
Let's examine some input and output mechanisms in more detail. The goal of scripting is to produce scripts that are flexible, reusable, and can run unattended in an automated environment. Therefore, rather than cover the graphical forms of input and output, I'll concentrate on the features you can use in an automated environment: WScript's Arguments collection and the FileSystemObject's TextStream object.
WScript Arguments Collection
Arguments for a WSH script are accessible via the WScript object's Arguments property. Internally, the Arguments property references a collection. Understanding what a collection is and how to work with a collection are the keys to understanding and implementing WScript arguments.
A collection is a special data type that contains a set of related items. In some ways, you can think of a collection as an array with properties. The key difference is that unlike arrays, which use integer subscripts to access individual elements, collections are objects that expose several properties that you can use to access collection members. The properties are Item(n), Count, and Length. Item(n), where n is a member's location in the collection, returns an individual collection member. Count and Length perform the function of providing the total number of members in a collection. (The Length property isn't a standard collection property. According to the documentation, Microsoft includes the Length property on WSH collections for JScript compatibility.) Some collections also implement methods such as Add and Remove that let you add and remove collection members, but the Arguments collection doesn't implement these methods.
Figure 1, page 189, shows how the WScript object uses the Arguments property to expose the Arguments collection. Because every WSH script automatically creates a WScript object, the Arguments collection is immediately available and accessible using the WScript.Arguments property and the standard collection properties.
To access members in the Arguments collection, you can use dot (.) notation. (For information about using dot notation, see Scripting Solutions, "Scripting 101, Part 3," August 1999.) For example, let's say you have a script you've written in VBScript and you want to pass to the script the first two arguments that Figure 1 shows. You can access the arguments as follows:
strComputer = _ WScript.Arguments.Item(0) strUser = _ WScript.Arguments.Item(1)
When a property is absent, the object accesses the default property. Because Item is the default property method of the collection, you can omit the Item property to shorten these arguments.
strComputer = _ WScript.Arguments(0) strUser = WScript.Arguments(1)
A third way to access the Arguments collection is to set a reference to the WScript.Arguments property, then use the reference to access the collection members. Using a reference eliminates the need to type WScript.Arguments for each argument you want to access:
Set oArgs = WScript.Arguments strComputer = oArgs(0) strUser = oArgs(1)
So far, I've explained how to access arguments individually. But accessing arguments individually can lead to problems. Let's suppose you use one of the previous examples, and a user fails to provide any arguments when using your script. The script will produce a runtime error because it tries to access a collection member that doesn't exist. You can use the collection's Count property to avoid this error, as the next example shows:
If WScript.Arguments.Count _ > 1 Then strComputer = _ WScript.Arguments(0) strUser = _ WScript.Arguments(1) End If
In this example, the condition checks the Arguments collection's Count property to ensure the user running the script provides a minimum of two arguments. If Count is greater than one, the If block assigns the first two arguments to the strComputer and strUser variables. If Count isn't greater than one, the script doesn't access the Arguments collection and thereby avoids a runtime error. As you can with arrays, you can also loop through arguments using a For Each loop:
If WScript.Arguments.Count Then For Each strArg In _ WScript.Arguments WScript.Echo strArg Next End If
Alternatively, you can use a For loop in conjunction with the Count property, as the next example shows. Be sure to specify Count - 1 as the For loop's ending value because collection membership starts at zero (i.e., the first member in a collection is Item(0)).
For i = _ 0 To WScript.Arguments.Count 1 WScript.Echo _ WScript.Arguments(i) Next
You can also be creative in some ways with the Arguments collection. I often implement arguments as switch=value pairs and store arguments as key=value pairs in a Dictionary object. For information about working with Dictionary objects, see Scripting Solutions, "Leveraging Components," January 1999. Storing arguments in a Dictionary object lets you easily determine whether a specific argument is available. I use the Dictionary object's Exists method for this purpose. In addition, by incorporating the Arguments collection and Dictionary object in a For Each loop, you can provide arguments in any order you want.
FileSystemObject's TextStream Object
Providing pieces of data to your script through the Arguments collection works, but sometimes you'll want to provide more data than you care to type on a command line. For example, you might want to provide your script with a long list of computer names or usernames, or you might want to record your script's actions to a log file. The FileSystemObject's TextStream object provides an easy way to read from and write to files.
The scripting runtime's FileSystemObject provides an object-oriented interface to work with virtually all aspects of a file system. With the FileSystemObject, you can read from files, write to files, and retrieve drive, directory, and file information. Like many objects you interact with using WSH, you can reference object models that express the objects, methods, and properties that FileSystemObject exposes. An object model is a graphical representation of an object's capabilities and the object's relationship to other objects in the model. Figure 2 shows the paths through which the FileSystemObject exposes the TextStream object, including the methods and properties that the TextStream object provides.
I'll explain Figure 2 to show that using the TextStream object is much easier than it seems. The FileSystemObject provides a hierarchical view of a file system in which a Drives collection contains Drive objects, a Folders collection contains Folder objects, and a Files collection contains File objects. In the FileSystemObject model, the FileSystemObject, the Folder object, and the File object expose the TextStream object and make it accessible. (The FileSystemObject, Folder, and File objects expose the TextStream-related methods.)
As you do with all objects that the FileSystemObject exposes, you must first create (using CreateObject) a reference to a FileSystemObject, before you can create a TextStream object. After you have a reference to a FileSystemObject, you can invoke one of the two TextStream-related methods (CreateTextFile, OpenTextFile) that the FileSystemObject provides. CreateTextFile creates a new (or overwrites an existing) text file and returns a reference that you use to write to the file. OpenTextFile opens an existing (or creates a new) text file and returns a reference that you use to read from, write to, or append to the file. In both cases, you use the reference that CreateTextFile or OpenTextFile returns to access the TextStream's methods and properties.
Suppose you have a text file named "c:\temp\servers.txt" that contains a list of servers, with one server name per line. You want to perform some action on each server in the list. You can use the FileSystemObject's TextStream object to read the server list and perform the action one server at a time, as Listing 1 shows. This example first creates a reference to the FileSystemObject named oFSO. (After you have a reference, you can call the methods that the FileSystemObject exposes.) Next, the example calls the TextStream-related method OpenTextFile and passes the method the name of the file to open. OpenTextFile creates a TextStream object and returns a reference (also called a handle) to the target file. The reference that OpenTextFile returns initializes the oTextStream variable. (After you obtain a reference to a TextStream object, you can access the methods and properties that the TextStream object exposes.)
The script uses the Do While loop in conjunction with the TextStream's AtEndOfStream property to traverse the servers.txt file. During each loop iteration, the TextStream's ReadLine method reads a server name from the file and assigns it to strServer. The script then performs some action on strServer. The script continues this process until the AtEndOfStream property becomes true, which signals that the process has reached the end of the file. At that point, the script exits the Do While loop and closes the file using the TextStream's Close method.
Simplify with ReadAll
TextStream's ReadAll method provides a smarter way to perform the previous input-related task. In the following example, you use the ReadAll method to read the servers.txt file in one fell swoop. You hand the result of this continuous text stream to the Split function (using vbNewline as a delimiter), which neatly returns an array that contains one server name per array element. Rather than looping through an open file as the previous example did, you end up with an array that you can easily traverse using a For Each loop, as Listing 2 shows. In addition, you can use the array for other purposes, without incurring the overhead of reopening and rereading the file one line at a time.
Creating a Log File
You might want to create a log file to record errors that your script encounters. In Listing 3, the example shows how to use the FileSystemObject's CreateTextFile method for this purpose.
Thus far, I've explained how you can create TextStream objects using the two methods exposed via the FileSystemObject. As I mentioned earlier, the Folder and File objects also expose TextStream-related methods. As such, you can also create and open files via references to these objects. For additional information about FileSystemObject capabilities, see the VBScript 5.0 documentation at http://msdn.microsoft.com/scripting.
The WSH Arguments collection and the FileSystemObject's TextStream object are only two of many ways to get data in and out of a WSH script. Other options include leveraging data that the directory services store via Active Directory Service Interfaces (ADSI), databases store via ADO, or Microsoft Excel stores via the Microsoft Office automation interfaces. For information about ADSI, ADO, and Office object models, see Scripting Solutions, "WSH Wise," May 1999. By incorporating the Arguments collection and the TextStream object in your scripts, you can produce flexible and reusable code. In the not-too-distant future, you'll have even more flexibility because WSH 2.0 will introduce support for standard input and output (STDIO) via the console. I'll discuss STDIO and other improvements to WSH 2.0 in the coming months.