|
Chapter 2: VBScript Primer continued
ArraysScripts are probably most useful when you need to carry out the same task multiple times. For example, if you own one computer, it is just as quick and just as easy to open the Services snap-in to see which services are installed as it would be to write a script to retrieve the same information. If you have 100 computers, however, you will likely prefer using a script to retrieve service information as opposed to manually retrieving this information.Of course, if a script is to run against 100 different computers, it needs a way to store those computer names and a way to keep track of which computers it has retrieved service information from and which computers it has not. One common way to store related bits of information (such as a list of computer names) is in an array, a special type of variable that can be used to store (and later retrieve) multiple values.
Creating Arrays The easiest way to create an array is to use the Array function. As easy as this is, however, there are two things to keep in mind when using the Array function. First, the Array function can be used only to create one-dimensional arrays. Second, this function requires you to know in advance each of the items to be placed in the array. In system administration tasks that usually is not the case: After all, one of the primary reasons you write a script is to determine these items (which services are installed on a computer, which computers are in a specified organizational unit, which printers are being managed by a particular print server. Only then could you place them in an array. Nevertheless, at times you will know the items in advance, and thus will be able to use the Array function. For example, the script used in the first half of this chapter assumed that you needed to run the script against exactly three computers. This might occur in your organization as well: If you have eight e-mail servers, you might want to hard-code all eight computer names into a script. To put items in an array, use the Array function. With this function, you simply assign a list of values to a variable. For example, the following code statement assigns four domain controllers to an array variable named Computers:
Computers = Array("atl-dc-01", "atl-dc-02", "atl-dc-03", "atl-dc-04")Declaring and Populating Arrays Of course, there is a very good chance that you will not know, in advance, the items to be stored in an array. For example, your script might read in computer names from a text file, or might populate an array using the names of files found in a folder. In either case, you will not know the actual array items, or even how many items there are, until the script is run. VBScript allows you to designate a variable as being an array without using the Array function and without immediately populating that array. To do this, you must use a Dim statement and must indicate the maximum number of items the array can hold. This can be a little confusing because the dimension of the array is based on the highest allowed index number rather than the actual number of items. Because the first item in an array is actually item 0 rather than item 1, the maximum number used in the Dim statement must always be the maximum number minus 1. If your array will contain 9 items, the Dim statement must show the maximum number as 8 (9 - 1). For example, this command creates an array variable named arrTestArray and indicates that the array can contain a maximum of 4 items (4 - 1 = 3):
Dim arrTestArray(3) After you have declared an array, you can populate it by assigning a value to the appropriate item. For example, this line of code assigns the value "A" to item 0, the first item in the array:
arrTestArray(0) = "A" This simple script creates an array named arrTestArray and assigns values to each of the four items:
Dim arrTestArray(3) So far, so good. But what if you guessed wrong when originally declaring the array? What if arrTestArray will actually contain five items rather than four? If that is the case, an error will be generated when you try to assign a fifth item to the array; an array cannot contain more items than the maximum number assigned to it. For example, this sample script creates a four-item array and then attempts to assign a value to a fifth item:
Dim arrTestArray(3) When the preceding script runs, a "Subscript out of range" error is generated. In a situation such as this, you have two options:
Creating Dynamic Arrays A dynamic array offers two advantages to script writers:
For example, you might have a script that reads computer names from a text file. Rather than guess at the number of names in the text file, you can use a dynamic array to store these names. That way, you can create an array, read in the first name, and store it as the first item in the array. If a second name happens to be in the file, you can resize the array and then read in and store the second name. You can then repeat this process until every name has been read and stored. To create a dynamic array, you must do two things. First, when you declare the array, do not specify a maximum size. Instead, simply use empty parentheses, like this:
Dim arrTestArray() Second, before you add a new item to the array, use the ReDim Preserve statement and increment the size of the array by 1. The ReDim Preserve statement performs two tasks:
For example, the following code sample creates a dynamic array named arrTestArray. The script creates a variable named intSize and assigns this the value 0. The script then uses the resultant line of code (intSize = 0) to resize arrTestArray so that it accepts one element (1 - 1 = 0). After the script has assigned a value to the first element (item 0) in the array, ReDim Preserve is used to resize the array so that it accepts two elements (intSize + 1). A value is then assigned to the second element:
Dim arrTestArray() To illustrate how you might use ReDim Preserve in an actual system administration script, the following code sample retrieves a list of all the services installed on a computer and stores those service names in a dynamic array. To perform this task, the script must:
Dim arrTestArray() Converting a Delimited String to an Array To make administrative data readily accessible to a wide variety of applications (including scripts), this data is often saved in plain-text files and in delimited format. Delimited simply means that a unique character separates the individual fields for each record in the file. This character is known as a delimiter and is typically the comma. These files are often referred to as comma-separated-values files because the individual values are separated by commas. For example, a log file that lists the name of a server, the name of an event log on that server, and the number of error events that were recorded in that event log might look like this:
atl-dc-01,DNS Server,13 One advantage of delimited files is that they can be imported into applications that can automatically parse each line into a single record with multiple fields. For example, an application such as Microsoft Excel or Microsoft Access would parse the sample file like the example shown in Table 2.18. Table 2.18 Parsing a Sample Data File into Fields
The Split function can be used to convert a delimited string to an array. This is done by passing the function two parameters:
For example, in the following script the delimited text string is the variable TestString, and the comma serves as the delimiter:
TestString = " atl-dc-01,DNS Server,13" When this script is run under CScript, the individual data fields are echoed to the screen:
atl-dc-01 Alternatives to Using Arrays Arrays provide a quick and easy way to track one-dimensional data. When you have multidimensional data, however, it can become difficult to keep track of the various subscripts; it can also become exceedingly complex to try to sort the data. Because of that, if you do have multidimensional data you might want to consider one of the these alternatives:
For i = LBound(SampleArray) to UBound(SampleArray) By contrast, this single line of code sorts a disconnected recordset by field name (in this case, FileSize):
DisconnectedRecordset.Sort = "FileSize" For more information about disconnected recordsets, see "Creating Enterprise Scripts" in this book.
Error HandlingVBScript provides a relatively simple method for handling run-time errors within a script. Run-time errors represent any errors that are not caught by the compiler and thus manifest themselves only after the script has started running. For example, the following script generates a syntax error because the command Wscript.Echo is mistyped and the compiler is unable to interpret the line. Before a script actually runs, the scripting engine reads each line to verify that the syntax is correct; no lines of code are actually run until this checking verifies that the syntax of all the lines of code is correct. Because of that, an error message will be displayed, even though the misspelled command does not occur until line 3 in the script:
Wscript.Echo "Line 1." Instead of the message boxes for lines 1 and 2, the error message shown in Figure 2.20 appears. This error is generated because line 3 appears to reference an invalid command named W. Figure 2.20 Syntax Error Message By contrast, the following script will display two message boxes before encountering an error (the misspelling of Wscript.Echo). This is because, as far as the compiler is concerned, line 3 is typed correctly. The compiler has no way of knowing that Eho is not a valid Wscript property and thus does not flag this as an error. VBScript cannot determine the properties and methods of a COM object in advance (that is, before a particular line of code runs).
Wscript.Echo "Line 1." When line 3 of the script is actually run, the error message shown in Figure 2.21 appears. You might notice that this error message lists the source as a Microsoft VBScript run-time error rather than as a compilation error. Figure 2.21 Runtime Error Message Not all run-time errors involve typographical errors within the script. For example, this line of code is syntactically and typographically correct. However, it will also generate a run-time error if the remote computer named InaccessibleComputer is not available over the network:
Set Computer = GetObject("winmgmts:\\InaccessibleComputer")This means that run-time errors are bound to occur, if only because of activities beyond your control. (The network goes down, a computer is shut down, another administrator deletes a user account.) With VBScript, you have three options for handling errors. The advantages and disadvantages of each of these options are summarized in Table 2.19. Table 2.19 Advantages and Disadvantages of Error Handling Options
Handling Run-Time Errors For the most part, to handle a run-time error simply means, "If an error occurs, do not allow the script to fail, but do something else instead." Typically the "something else" that you can do is one of the following:
Both of these methods for handling errors are implemented by including the On Error Resume Next statement within the script. When running under On Error Resume Next, a script will not fail when encountering a run-time error. Instead, the script will ignore the line of code where the error occurred and will attempt to process the next line of code. This will continue until every line of code has been either ignored (because an error occurred) or processed. Ignoring All Errors By far the simplest form of error handling is the type that instructs a script to ignore all errors and continue running until every line of code has been processed. To create a script that ignores all errors and continues to run, place the On Error Resume Next statement at the beginning of the script. For example, the following script attempts to use a nonexistent WSH method (Wscript.X); none of the lines of code can thus run successfully. However, the script will actually run without generating an error message because of the On Error Resume Next statement:
On Error Resume Next Note that error handling does not begin until VBScript processes the On Error Resume Next statement. For example, the following script will generate a run-time error because On Error Resume Next is not implemented until the error has already occurred:
Wscript.Echo "Line 1." To ensure that error handling is in place before an error occurs, put the On Error Resume Next statement near the beginning of your script:
On Error Resume Next When the preceding script is run under CScript, the following output appears in the command window. When the script host encountered the run-time error generated by Wscript.Eho, it simply skipped that line and continued with the rest of the script:
Line 1. Responding to Errors Instead of having your script ignore errors, you can create code that periodically checks the error condition and then takes some sort of action. For this purpose, you might create code that:
For example, the following script sample first attempts to connect to the WMI service. If this connection attempt fails (that is, if Err does not equal 0), a message box is displayed showing the error number and description. After displaying the error message, the script then clears the Err object. The script then attempts to return a list of all the services installed on a computer. This line of code will fail because the ExecQuery method has been misspelled as ExcQuery. After running this line of code, the script will again check the error condition. If Err does not equal 0, a message box will be displayed showing the error number and description. After displaying the error message, the script then clears the Err object.
On Error Resume Next When this script is run under WScript, the message box in Figure 2.22 is displayed: Figure 2.22 WMI Error Message Toggling Error Handling At times you might want to implement error handling in one portion of a script but not in another portion. In other words, sometimes you might want to trap errors, while other times you might prefer to simply let the script fail. You can toggle error handling on and off by:
For example, in the following script, error handling is implemented in the first line. In the second line, an error occurs because Wscript.Echo has been misspelled. However, because error handling has been implemented, the script will continue to run. Error handling is turned off in line 3, using On Error GoTo 0, and then reimplemented in line 5. Because no errors occur while error handling is off, the script will run without failing.
On Error Resume Next When the preceding script is run under Cscript, the following data is displayed in the command window. The values "A" and "B" are the only values echoed to the screen because those are the only lines of code that could be run without generating an error.
A The following sample script shows a slightly revised version of the same script. In this case, error handling is turned on and then off, but this time an error occurs while error handling is off.
On Error Resume Next When this script is run under Cscript, the following error message is displayed in the command window:
C:\Documents and Settings\gstemp\Desktop\Scripts\x.vbs(4, 1) Microsoft VBScript In this case, the script fails to run line 2 (with the misspelling of Wscript.Echo) but continues to run because error handling has been implemented. In line 3, however, error handling been turned off. As a result, the misspelling in line 4 causes the script to fail, generating a run-time error and the resultant error message. Handling Errors in COM Objects One issue that complicates error handling is the fact that system administration scripts typically make use of COM objects. If an error occurs with the COM object itself, VBScript will be aware of the error. However, VBScript might have no knowledge of what actually caused the error. For example, in the following script, WMI attempts to bind to a nonexistent printer named TestPrinter. Not surprisingly, this raises an error. You might expect, therefore, to be able to trap the error number and description and echo that to the screen:
On Error Resume Next When the script runs, however, you get neither a standard four-digit VBScript error number nor any error description. Instead, you get the cryptic message box shown in Figure 2.23. Figure 2.23 COM Object Error Number and Description The problem is that the error occurred within WMI, and the error details are not available to VBScript. VBScript receives notice that an error occurred, and in most scripts that type of notice might be sufficient. In other scripts, however, or when you are developing and debugging a new script, you might find detailed error information much more useful. Many COM objects provide their own mechanisms for trapping error information. For example, WMI allows you to create an error object, SWbemLastError, and then retrieve the following information:
The following script uses the SWbemLastError object to retrieve error information from within WMI and then display that information in a message box.
On Error Resume Next When the preceding script runs, the message box in Figure 2.24 appears. Figure 2.24 Message Box Using SWbemLastError When working with SWbemLastError, you must create the error object following the line of code where you believe an error could occur. For example, the following script will not return any error information because the SWbemLastError object was created before the error occurred:
On Error Resume Next
ProceduresThe first few system administration scripts you write are likely to be simple scripts that carry out a single task and then stop. For example, you might have a script that connects to a computer, retrieves a list of the services running on that computer, and then ends. You might have a second script that connects to a computer and starts a selected set of services, and a third that connects to a computer and stops a selected set of services.As you become more proficient with scripting, you might begin to write more complex scripts, perhaps by combining several simple scripts. Instead of having three separate scripts for managing services, you might create a single service management script that can perform three different tasks depending on the command-line parameters entered. For example:
As your scripts become more complex, you might find it beneficial to place portions of the code within a procedure. Procedures are lines of code that carry out a specific task. For example, your service management script might have three procedures: one to retrieve service information, one to start selected services, and one to stop selected services. Placing code within procedures makes it easy to identify and to isolate the tasks carried out by the script. This enhances the readability and maintainability of the script and also makes it easy to copy and paste the code into scripts that carry out similar activities. Procedures are also useful for scripts that need to carry out the same task over and over. For example, you might want to echo a message each time an error occurs within a script. To do this, you can include the message display code at every point in the script where an error might occur. Of course, this requires extra work, not only to write the original code but to maintain it as well; if you ever decide to change the displayed message, you will have to locate and change each instance within the code. A better approach is to create a procedure that displays the message and then call that procedure each time an error occurs. As a result, you have to write and maintain only a single instance of the display code. VBScript allows you to create two types of procedures:
Calling a Procedure In general, VBScript processes each line of code in succession. Procedures are an exception to this rule. Neither subroutines nor functions are run unless they have been specifically invoked somewhere in the script. If a procedure has not been invoked, the procedure is simply skipped, regardless of where it appears within the script. For example, the following script includes a subroutine embedded in the middle of the code. However, this subroutine is never actually called.
Wscript.Echo "A" When the preceding script runs under CScript, the following output appears in the command window. Because the subroutine was never called, the subroutine and all the code inside it was skipped and not run:
A The flow of the script goes like this:
To ensure that a subroutine runs, it must be called. This is done using a statement that consists solely of the subroutine name. For example, the following script echoes the message "A", calls the subroutine named EchoLineB, and then echoes the message "C".
Wscript.Echo "A" When the preceding script runs under CScript, the following output appears in the command window:
A The flow of the script goes like this:
Procedures can be placed anywhere within a script, with no degradation of performance. However, the placement of procedures can affect the ease with which a script can be read and maintained. For more information about placing procedures within a script, see "Scripting Guidelines" in this book. In the following script, an error-handling procedure is used to display any WMI errors that might occur. Throughout the script the Err object is checked. If the value is anything but 0, this means an error occurred; the ErrorHandler subroutine is called, and the appropriate error message is displayed.
On Error Resume Next Functions Like subroutines, functions provide a way for you to use one section of code multiple times within a script. Unlike subroutines, however, functions are designed to return a value of some kind. This is not necessarily a hard-and-fast rule; VBScript does nothing to ensure that a function always returns a value and that a subroutine never returns a value. However, the scripting language is designed to make it easier to return values using functions. In fact, when you create a function, VBScript automatically declares and initializes a variable that has the same name as the function. This variable is designed to hold the value derived by the function. Although there is no requirement that you use this variable, doing so makes it very clear that the value in question was derived by the function of the same name. For example, the following script includes the statement Wscript.Echo ThisDate. ThisDate also happens to be the name of a function that retrieves the current date. In this script, notice that:
Option Explicit Note that this approach works only for a function, and not for a subroutine. The following code generates a run-time error because VBScript is unable to assign the date to the name of a subroutine:
Wscript.Echo ThisDate Passing Parameters to Functions Functions are often used to carry out a mathematical equation and then return the result of this equation. For example, you might use a function to convert bytes to megabytes or convert pounds to kilograms. For a function to carry out a mathematical equation, you must supply the function with the appropriate numbers. For example, if you want a function to add the numbers 1 and 2, you must supply the functions with those two values. The numbers 1 and 2 are known as parameters (or arguments), and the process of supplying a function with parameters is typically referred to as passing those values. To pass parameters to a function, simply include those values in the function call. For example, this line of code calls the function AddTwoNumbers, passing the values 1 and 2:
AddTwoNumbers(1 , 2) In addition to including the parameters within the function call, the function itself must make allowances for those parameters. This is done by including the appropriate number of variables in the Function statement. This line of code, for example, creates a function that accepts three parameters:
Function AddThreeNumbers(x, y, z) If the number of parameters in the Function call does not match the number of parameters in the Function statement, an error will occur. For example, this script generates a "Wrong number of arguments" error. Why? Because two values are passed to the function, but the Function statement does not allow for any parameters:
x = 5 To correct this problem, include space for two parameters within the Function statement:
x = 5 You might have noticed that the parameters used in the Function call (x and y) have different names from the parameters used in the Function statement (a and b). VBScript does not require the parameter names to be identical; if it did, this would limit your ability to call the function from multiple points within a script. (Although this would be possible, you would always have to assign new values to variables x and y before calling the function. This could be a problem if you did not want to assign new values to x and y.) Instead, VBScript simply relies on the order of the parameters. Because x is the first parameter in the function call, the value of x is assigned to a, the first parameter in the Function statement. Likewise, the value of y, the second parameter in the Function call, is assigned to b, the second parameter in the Function statement. To show how a function might be used in an actual system administration script, the following sample retrieves the amount of free disk space on drive C of a computer and then calls a function named FreeMegabytes. This function converts the free space from bytes to megabytes and returns that value. This new value is then echoed to the screen:
Set objWMIService = GetObject("winmgmts:")Note when working with functions that each time VBScript sees the name of a function, it will attempt to call that function. This means that even though there is a special variable named, in this case, FreeMegabytes, you do not have access to that variable except when calling the function. For example, in the following script, the FreeMegaBytes function is called, and the free space displayed. In the next line, the script then attempts to echo the value of the FreeMegabytes variable.
Set objWMIService = GetObject("winmgmts:")When the script runs, the error message shown Figure 2.25 appears. This happens because VBScript does not echo the value of the FreeMegaBytes variable. Instead, it tries to call the function FreeMegaBytes. This call fails because the function requires you to supply the number of free bytes. Figure 2.25 Error Message for Improperly Accessing a Function Variable If you need to refer to the value derived from a function without calling that function, save the value in a separate variable. For example, in this script, the value returned from the FreeMegabytes function is saved in the variable AvailableSpace. This variable can be used at any time without calling the function.
Set objWMIService = GetObject("winmgmts:")Passing Parameters by Value or by Reference The values passed to a function are rarely hard-coded into a script; instead, values are typically passed to a function by using a variable. For example, the following two lines of code set the value of the variable x to 100 and then pass the variable to a function named ModifyValue:
x = 100 The value of x at the time the function is called is 100. The value of x at the time the function finishes running depends on two things: whether the function actually modifies the value in some way and whether the function was called by value or by reference. To explain the difference between passing variables by value or by reference, consider the following script, which sets the value of x to 100 and then, within the function itself, changes the value of x to 99:
x = 100 When the preceding script runs, the message box shown in Figure 2.26 appears. As you can see, the variable x was divided by 25 and then was reassigned the new value 99. Figure 2.26 Assigning a New Value Within a Function In many scripts, the fact that the function changed the value of x makes no difference. However, what if you need to use the original value of x later in your script? In that case, the fact that the function changed the value of x makes a very big difference. By default, VBScript passes variables by reference. This means that the function receives a reference to the variable's location in memory, and thus performs its calculations using the variable itself. Depending on the function, this can change the value of the variable. To ensure that your variables are not changed by a function, pass the variables by value. With this approach, VBScript does not pass a reference to the actual variable; instead, it merely passes the value of that variable. Because the function does not have access to the variable itself, it cannot change the value of that variable. To pass a variable by value, include the ByVal keyword within the name of the function. For example, this script passes the variable x by value to the function ModifyValue:
x = 100 When the preceding script runs, the message box shown in Figure 2.27 appears. Notice that the value of the variable x remains unchanged, even though the function appears to have set the value of x to 99. What really happened is that the function set the value of a copy of x to 99. Within the function itself, x now equals 99. As soon as the function ends, however, this temporary copy of x disappears. Meanwhile, the real x has retained its original value. Figure 2.27 Passing a Variable by Value You can pass some variables to a function by value and other variables to the same function by reference (using the ByRef keyword). For example, this line of code passes the variable x by value and the variable y by reference:
Function TestFunction(ByVal x, ByRef y) In this function, it is possible for the value of y to be changed after the function finishes. However, the value of x will remain unchanged. Recursion Recursion is a programming technique in which a subroutine or a function calls itself. You have probably seen photographs of a person staring into a mirror. In turn, his or her reflection is staring into a mirror located in the background, which reflects the person in the mirror staring into the mirror, and so on. This is the basic idea behind recursion: Subroutine A calls subroutine A, which calls subroutine A, which calls subroutine A, and so on. Although the concept of recursion might seem a bit bizarre, it actually has important uses in system administration scripting. For example, suppose you want to enumerate all the files in a shared folder. You can easily connect to the shared root folder and list all the files, but what happens if the shared root folder has subfolders? And what happens if those subfolders contain other subfolders? Recursion allows you to enumerate all items and subitems, even if you have no advance knowledge of the folder structure. For example, the folder structure shown in Figure 2.28 shows a root folder, Scripts, and main subfolders, Subfolder 1 and Subfolder 2. Each of those subfolders contains other subfolders. (Image Unavailable) Figure 2.28 Sample Folder Structure To enumerate all the folders shown in this figure, you will have to start with the Scripts folder, return a list of subfolders, bind to each subfolder, return a list of subfolders within those folders, and so on. To perform this task, you can use a script similar to the following. This script uses a function named ShowSubFolders that is called over and over until every folder and subfolder has been enumerated.
Set FSO = CreateObject("Scripting.FileSystemObject")The function ShowSubFolders does the following:
When the script runs, the following output appears:
C:\scripts\Subfolder 1 Recursion is an extremely powerful technique for exploring data stored in a tree structure, including Active Directory as well as the file system.
COM ObjectsThe Component Object Model (COM) provides a standard way for applications (.exe files) or libraries (.dll files) to make their functionality available to any COM-compliant application or script. That is the textbook definition of COM. What COM really does, however, is make it possible for nonprogrammers to write scripts for managing Windows operating systems. COM provides a mechanism for translating script code into commands that can be acted on by the operating system. Without COM, anyone hoping to automate system administration would have to master not only a high-level programming language such as C++ or Visual Basic but also all of the Windows Application Programming Interfaces (APIs). In effect, COM brings Windows programming to the masses.COM components are files (typically .exe or .dll files) that contain definitions of the objects the component has available for use. (These definitions are known as classes.) When you create a COM object in a script (a process known as instantiation), you are creating an instance, or copy, of one of the classes contained within the COM component. After the instance has been created, you can then take advantage of the properties, methods, and events exposed by the object. Objects that make their functionality available through COM are known as COM servers. Applications or scripts that make use of that functionality are referred to as COM clients. For example, when you write a script that uses WMI, WMI is the COM server and your script is the COM client. COM servers can be implemented in one of two ways:
As noted previously in this chapter, VBScript works with a subset of objects known as Automation objects. All COM objects must support one or more interfaces, which are simply the avenues by which a COM client can access the COM server. Any object that supports the IDispatch interface is known as an Automation object. Because not all COM objects support the IDispatch interface, VBScript cannot access all of the COM objects on your computer. The COM Process As a script writer, you have to know only how to create a reference to an Automation object. You do not have to worry about how to locate and load the object because the Windows operating system takes care of that for you. Nevertheless, it is still useful for you to understand what happens between the time when the script runs your CreateObject command and the time when the object itself is available for use. What is especially useful to understand is that there is no magic here; in fact, the process is relatively simple. When you install an application or a library that contains object classes, that application or library registers itself with the operating system, a procedure that enables the operating system to know:
The registration process includes adding a number of subkeys to HKEY_CLASSES_ROOT in the registry. Among the subkeys that are added is one that specifies the Programmatic Identifier (ProgID) for each new object class. The ProgID is a short text string that identifies the name given to each object class. In addition, the ProgID is the parameter used in the CreateObject and GetObject calls to specify the object you want to create. For example, in the following line of code, the ProgID is Excel.Application:
Set TestObject = CreateObject("Excel.Application")The ProgID is all the information the operating system needs to locate and instantiate the COM object. Creating a New Object When a CreateObject call runs, the script engine parses out the ProgID and passes that to a COM API. This API actually creates the object reference. For example, in this line of code, the string Scripting.FileSystemObject is passed to the COM API:
Set TestObject = CreateObject("Scripting.FileSystemObject")The COM API searches the HKEY_CLASSES_ROOT portion of the registry for a subkey with the same name as the ProgID. If such a subkey is found, the API then looks for a subkey named CLSID. The CLSID subkey maintains a globally unique identifier (GUID) for the Automation object being created. The GUID will look something like this: {172BDDF8-CEEA-11D1-8B05-00600806D9B6} The GUID is the way that the operating system tracks and uses COM objects. The ProgID is simply an alias that is easier for script writers to remember. After the GUID is discovered, the HKEY_LOCAL_MACHINE\Software\Classes\CLSID portion of the registry is searched for a subkey with the same name as the GUID. When the operating system finds this subkey, it examines the contents for additional subkeys that store the information needed to locate the executable file or library file for the object (in the case of the FileSystemObject, C:\Windows\System32\Scrrun.dll). The COM API loads the application or library, creates the object, and then returns an object reference to the calling script. Server Mode When an object is created from an executable file, the application is started in a special mode known as Server mode or Embedded mode. This means that although the application is running and fully functional, there is no graphical user interface and nothing is visible on the screen. (You can, however, use Task Manager to verify that the process is running.) Server mode allows you to carry out actions without a user seeing, and possibly interfering with, those actions. Although server mode is often useful in system administration scripting, sometimes you might want a user interface (for example, if you are displaying data in Internet Explorer). If so, you will need to use the appropriate command for that COM object to make the application appear on screen. For example, the following script creates an instance of Internet Explorer and then uses the Visible command to allow the user to see the application:
Set IE = CreateObject("InternetExplorer.Application")Binding Binding refers to the way that a script or an application accesses a COM object. When you create an object reference to an Automation object, VBScript must verify that the object exists and that any methods or properties you attempt to access are valid and are called correctly. This process of connecting to and verifying an object and its methods and properties is known as binding. COM supports two types of binding: early and late. With early binding, an object, its methods, and its properties are checked when the application is compiled. If there are any problems, compilation will fail. Early binding is faster than late binding because the object is verified before the application runs. In addition, early binding provides access to the object's type library, which contains information about the methods and properties of the object. The information in the type library can then be included within the compiled code and thus be available whenever the application needs it. Because VBScript is not a compiled language, it does not support early binding. Instead, you must use late binding, in which binding does not occur until the script actually runs. With late binding, the script must access the registry to obtain information about the object, its methods, and its properties. Because VBScript does not have access to the object's type library, it must perform a similar lookup any time it accesses the object or attempts to use one of the object's methods or properties. In addition, any incorrect calls to the object will not be found until the script actually runs. Choosing a Method for Binding to an Automation Object Binding to an Automation object is actually quite easy; the hardest part involves knowing how to bind to that object (that is, do you use the GetObject method or the CreateObject method?). For the most part, this depends on the object you are binding to; however, some general guidelines for binding to Automation objects are listed in Table 2.20. Table 2.20 Methods for Binding to Automation Objects
Verifying Object References The IsObject function allows you to verify that you were able to obtain an object reference. If the GetObject or CreateObject call succeeds, IsObject will return True (-1). If the GetObject or CreateObject call fails, IsObject will return False (0). For example, the following code uses CreateObject to try to obtain an object reference (assigned to the variable TestObject) to a nonexistent object. Because the object call fails, TestObject is not assigned an object reference, and IsObject returns 0.
On Error Resume Next Unfortunately, VBScript assumes that once an object reference has been established, that reference will remain valid for the lifetime of the script. That is generally not a problem, particularly for ADSI and WMI, which are unlikely to disappear while the script is running. The same cannot be said for other Automation objects, however. For example, consider the following script, which starts an instance of Microsoft Word, immediately stops that instance, and then uses IsObject to test whether the object reference is still valid:
Set TestObject = CreateObject("Word.Application")When the script runs, IsObject reports that TestObject is still an object because TestObject is still an object reference; it just no longer points to a running instance of Microsoft Word. There are two ways to work around this problem. One approach is to use WMI to verify that the process (in this case, Winword.exe) is still running. Although this method will work, it requires you to repeatedly query the set of running processes on the computer, something that will slow your script. In addition, matters can get complicated if multiple instances of Winword.exe are running because there is no straightforward method for identifying the instance of Winword.exe that you created and that your object reference refers to. To avoid possible problems (such as a script that inadvertently deletes text from the wrong Word document), your script should use the same instance of Winword.exe all the way through. A better approach is to add eventing to your script. With this approach, your script can receive notification when specified events occur with your Automation object. For example, should Word quit unexpectedly, notice can be sent to your script that the Word object is no longer available. Your script can then take the appropriate action. For more information about adding eventing to a script, see "Creating Enterprise Scripts" in this book. Unloading Objects from Memory In-process servers (that is, Automation objects encapsulated in .dll files) will automatically unload themselves from memory when the calling script completes. This is because these objects run in the same process as the script; when the script process ends and is thus removed from memory, any in-process servers will also be stopped and removed from memory. For example, the following script creates an instance of the FileSystemObject and then displays a message box. As soon as you dismiss the message box, both the script and the FileSystemObject are removed from memory.
Set TestObject = CreateObject("Scripting.FileSystemObject")This is not true, however, for out-of-process servers, Automation objects that run in a different process than the script itself. For example, the following script creates an instance of Microsoft Word and then displays a message box. When you dismiss the message box, the script process is unloaded from memory.
Set TestObject = CreateObject("Word.Application")However, the Microsoft Word process (Winword.exe) will continue to run and remain in memory, even though it is not visible on the screen. This is because there is no inherent tie between the script process and the Word process; anything you do to the script process does not affect the Word process and vice versa. You can verify that the process is still running and verify the amount of memory it is still allocated by using Task Manager, as shown in Figure 2.29. Figure 2.29 Automation Object Running After a Script Has Completed With out-of-process servers, you will typically have to use the method built into the object to explicitly unload it from memory. (You will need to check the documentation for the object to determine that method.) Microsoft Word, for example, is unloaded from memory by using the Quit method. The following script creates an instance of Microsoft Word and then immediately unloads that instance using the Quit method.
Set TestObject = CreateObject("Word.Application")If you run the preceding script and then check the processes running on the computer, you will not see Winword.exe (unless, of course, you had multiple copies of Winword.exe running). Nothing Keyword VBscript includes the Nothing keyword, which can be used to disassociate an object reference and an object. After an object variable is set to Nothing, the variable no longer maintains an object reference and thus cannot be used to control the object. For example, the following code creates an instance of Microsoft Word, sets the object variable to TestObject, and then tries to use TestObject to quit Word and unload the object from memory.
Set TestObject = CreateObject("Word.Application")When this script runs, the error message shown in Figure 2.30 appears. The script fails because TestObject no longer represents a valid reference. Figure 2.30 Working with an Invalid Object Reference Setting an object variable to Nothing releases a small amount of memory but does not unload the object itself from memory. Because of that, there is generally no reason to set an object variable to Nothing; in effect, object variables (and all other variables, for that matter) are set to Nothing when the script completes. For example, in the following script the last line of code is superfluous: It sets the object variable TestVariable to Nothing, but that would occur anyway as soon as the script ended.
Set TestObject = CreateObject("Scripting.FileSystemObject")
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||