Chapter 2. VBScript Primer

Microsoft® Visual Basic® Scripting Edition (VBScript) is an easy-to-use scripting language that enables system administrators to create powerful tools for managing their Microsoft® Windows®–based computers. In the first half of this chapter, the fundamental principles of VBScript are illustrated by the creation of a simple script for determining the amount of free disk space on drive C of a computer. This script will evolve throughout the chapter into a more sophisticated tool, one that can determine the amount of free space for any drive on any computer. The second half of this chapter then explores these fundamental principles of VBScript programming in more detail and touches on other VBScript constructs of interest to system administrators.

In This Chapter

VBScript Overview

Microsoft® Visual Basic® Scripting Edition (VBScript) is often dismissed as being “just” a scripting language, the implication being that a scripting language is of little use to a system administrator faced with managing hundreds or even thousands of computers in an enterprise setting. Yet nothing could be further from the truth; when used in combination with technologies such as Windows Script Host (WSH), Windows Management Instrumentation (WMI), and Active Directory Service Interfaces (ASDI), VBScript becomes a powerful language for creating system administration tools. For example, using VBScript in combination with WMI and ADSI, you can write a script of 10,000 or so lines, complete with error handling, subroutines, and other advanced programming constructs. That single script can give you complete control over many aspects of your computing environment.

But what makes VBScript such a useful tool for system administrators is that you do not have to create such elaborate and complicated solutions. You can instead spend a few minutes typing a handful of lines of code into Notepad, and instantly create a custom solution to a particular problem.

For example, the three-line script in Listing 2.1 tells you how much free disk space is available on drive C of your computer.

Example 2.1. Retrieving Free Disk Space Using VBScript

1 Set objWMIService = GetObject("winmgmts:")
2 Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
3 Wscript.Echo objLogicalDisk.FreeSpace

If you have been having problems with users filling up drive C on their computers, you now have a custom solution for identifying the computers running low on disk space, a solution developed using nothing more sophisticated than Notepad.

Of course, it might be that this script does not fully address your needs. For example, the script tells you the free space available only on your local computer; it cannot tell you how much free space is available on a remote computer. Likewise, the script reports the free space available only on drive C; it tells you nothing about free space available on drives D or E.

But if the script does not fully meet your needs, it can easily be modified, and without starting from scratch. This is another advantage of scripting in general and VBScript in particular: You can start with a very simple script and add to it as your needs change and as you become more proficient with the language. This chapter illustrates this process. It begins with the script shown in Listing 2.1, which reports the amount of free disk space on drive C. Subsequent sections in the chapter will take this simple three-line script and gradually add functionality to make it more useful in more situations. When this series of enhancements is complete, you will have a script that can:

Retrieve free disk space information for any computer in your organization, including remote computers.

Retrieve free disk space information from multiple computers.

Retrieve free disk space information for all drives installed in a computer.

Issue a notification only if a drive is low on disk space.

Continue to function if a user types an invalid computer name, or if a computer is not available over the network.

As new features are added to the script, the VBScript constructs required to add this functionality are briefly explained. After the script has been completed, a reference section will cover these constructs (and others) in more detail.

Working with Objects

VBScript allows system administrators to create complex scripts using such advanced programming capabilities as branching, looping, error handling, and the calling of functions and subroutines. It does not, however, include intrinsic methods for performing system administration tasks. VBScript has built-in functions for determining the square root of a number or the ASCII value of a character, but no built-in methods for stopping services, retrieving events from event logs, or carrying out other tasks of interest to system administrators.

Fortunately, there are other ways to programmatically perform these tasks, primarily through the use of Automation objects. Automation objects are a subset of COM (Component Object Model), a standard way for applications (.exe files) or programming libraries (.dll files) to present their functionality as a series of objects. In turn, programmers (or script writers) can use these objects, and the functionality of the application or programming library, in their own projects. For example, a word processing application might expose its spell checker as an Automation object, thus providing a way for script writers to add spell checking to their projects.

The ability to work with Automation objects and to utilize the properties and methods of these objects makes VBScript a powerful tool for system administration. Admittedly, VBScript alone cannot read events from an event log; however, VBScript can use the functionality included within WMI to retrieve such events. VBScript has no intrinsic methods for creating user accounts; however, the language can use the functionality in ADSI to create such accounts. In fact, VBScript is often referred to as a glue language because one of its primary uses is to “glue” objects together. Rather than provide a seemingly infinite number of intrinsic functions devoted to system administration, VBScript instead provides a framework for using the methods and properties of Automation objects designed to carry out these tasks.

For example, the script in Listing 2.2 illustrates the importance of Automation objects within VBScript. This script reports the amount of free disk on drive C of the local computer. Furthermore, it does this using very little VBScript code. Instead, the script:

  1. Connects to WMI (an Automation object) by using the VBScript GetObject method.

  2. Uses the WMI Get method to retrieve information about drive C.

  3. Uses the WSH Echo method to report the amount of free disk space on drive C.

As noted, there is very little VBScript code here. Instead, the primary purpose of VBScript in this example is to glue together the functionality of WMI and WSH.

Example 2.2. Using Objects in VBScript

1 Set objWMIService = GetObject("winmgmts:")
2 Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
3 Wscript.Echo objLogicalDisk.FreeSpace

Connecting to Objects

Before you can do anything with the data in a database, you must first make a connection of some kind to that database. Likewise, before you can do anything with the methods or properties of an Automation object, you must first make a connection to that object, a process known as binding.

Binding to objects can be confusing, because VBScript and WSH both provide a GetObject and a CreateObject method for accessing objects. Furthermore, although the implementations are similar, there are some subtle differences that grow in importance as you become more proficient in scripting. These differences are discussed in more detail later in this chapter. For now, use the following rules-of-thumb without worrying about whether you are using the VBScript or the WSH method (although in most cases you will use the VBScript implementation):

Use GetObject to bind to either WMI or ADSI. Both WMI and ADSI allow you to use a moniker when binding from VBScript. A moniker (discussed later in this chapter) is an intermediary object that makes it easy to bind to objects that do not exist in the file system namespace. ADSI exists in the Active Directory namespace, while WMI exists in its own namespace.

Although it is possible (and sometimes required) to bind to WMI or ADSI using CreateObject, using GetObject and a moniker is typically faster and easier.

Use CreateObject to bind to all objects other than WMI or ADSI. In general, CreateObject will be required to create new instances of such elements as the FileSystem object, the Dictionary object, and Internet Explorer. This is not a hard-and-fast rule, however. For example, you will often need to use CreateObject to create WMI objects other than SWbemServices (such as SWbemLocator).

Connecting to WSH

In line 1 of the script in Listing 2.2, the script binds to WMI by using the following code statement:

Set objWMIService = GetObject("winmgmts:")

This connects the script to the WMI SWbemServices object.

No similar binding string is used to connect to WSH. Instead, the Echo method is called without first binding to WSH. You are already connected to WSH because WSH is required to run a script written in VBScript.

Creating an Object Reference

With Automation, you do not work directly with an object itself. Instead, you create a reference to the object by using GetObject or CreateObject and then assign this reference to a variable. After the reference has been created, you can access the methods and properties of the object by using the variable rather than the object itself.

In the script in Listing 2.2, the GetObject method is used to assign the WMI SWbemServices object to the variable objWMIService. After the assignment has been made, all the properties of the SWbemServices object can be accessed through objWMIService. For example, in line 2 of the script, the Get method is used to retrieve the properties for drive C.

Anytime you create an object reference, you must use the Set keyword when assigning the reference to a variable. For example, the following line of code will result in a run-time error:

objWMIService = GetObject("winmgmts:")

To create the object reference, you must use the Set keyword like this:

Set objWMIService = GetObject("winmgmts:")

Set is a special VBScript statement that is used only when creating an object reference. If you use Set for other purposes, such as assigning a value to a variable, a run-time error will occur. For example, this line of code will fail because no object named 5 can be found on the computer:

Set x = 5

Calling Methods

Automation objects allow you to use their capabilities within your scripts. This enables you to create more powerful and more useful scripts than you could create if you were restricted to the functionality of the scripting language. For example, it is impossible to draw a chart or graph by using VBScript alone. Through Automation, however, you can borrow the capabilities of Microsoft Excel and easily add a chart or graph to, say, a Web page.

Automation objects typically expose both methods and properties. (However, there is no requirement for them to expose either.) Methods are equivalent to the actions that the object can perform. For example, although the script in Listing 2.2 is only three lines long, it uses Automation to access the methods of two different COM objects and thus performs two different actions. These two methods are:

The Get method, available through the WMI SWbemServices object. This method retrieves information for the specified object.

The Echo method, available through the WSH WScript object. This method displays information on the screen. If a script is running in a command window and thus under CScript.exe, this information is displayed within that command window. If the script is running under Wscript.exe, the information is displayed in a message box.

After you have created a reference to an object, you can call the methods of that object using dot notation. Dot notation is so named because you call a method by typing the name of the variable that references the object, a period (or dot), and the name of the method. (Depending on the method, you might also type method parameters.) Generally, dot notation uses the following syntax:

ObjectReference.MethodName

For example, in the following line of code, the SWbemServices Get method call is written in dot notation.

Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")

The parts of the SWbemServices Get method call are shown in Table 2.1.

Table 2.1. Parts of the SWbemServices Get Method Call

Item

Description

ObjWMIService

Object reference.

.

Dot (separates the object reference and the name of the method).

Get

Method name.

(“Win32_LogicalDisk.DeviceID=’c:’”)

Method parameter. For the Get method, this can be read as, “Get the instance of the Win32_LogicalDisk class where the DeviceID is equal to C:.”

Note

Note

Instead of using Wscript.Echo to display the amount of free disk space, you can use the VBScript function Msgbox:

Msgbox objLogicalDisk.FreeSpace

In this book, however, Wscript.Echo is used instead of Msgbox. This is because the Msgbox function always displays its information in a graphical dialog box. When this dialog box appears, the OK button must be clicked before the script can proceed. For system administration scripts that display a large amount of data, this would be extremely tedious; it would also prevent the script from running automatically. By contrast, Wscript.Echo displays information as lines within a command window, provided the script is running under CScript.

Retrieving Properties

Properties are the attributes associated with an object. They are particularly important in system administration scripting because many of the objects you use are virtual representations of actual objects. For example, in line 3 of Listing 2.2, the FreeSpace property is retrieved, using the same dot notation used to call methods.

objLogicalDisk.FreeSpace

With WMI, this object reference refers not to some amorphous programming construct but to an actual hard disk within the computer. The FreeSpace property is thus not just a property of an Automation object but also of drive C. In a sense, WMI creates a virtual mirror of an actual physical object. When you retrieve the properties of that virtual mirror, you also retrieve the properties of the physical object.

Variables

The script in Listing 2.2 works exactly as expected. When run, it reports the amount of free disk space on drive C. That does not mean that the script cannot be improved, however. For example, the FreeSpace property reports the number of bytes available on a drive. Because disk drive space is typically reported in gigabytes, the FreeSpace property often returns a value that is difficult to interpret. For example, Figure 2.1 shows the value reported for a drive with approximately 10 gigabytes of free disk space.

Free Disk Space Expressed in Bytes

Figure 2.1. Free Disk Space Expressed in Bytes

Although it might be obvious from a glance that drive C has adequate disk space, it is far less obvious just how much disk space is actually available. System administrators might find it easier to interpret the data returned by this script if the data were reported as megabytes rather than bytes.

VBScript includes a full range of mathematical functions that enable you to perform such tasks as converting bytes to megabytes. In addition, VBScript also provides a construct — the variable — that can be used to store the results of those mathematical equations. In fact, variables provide a way to store any type of data while the script is running.

Variables represent portions of memory that are available to the script as it runs. You can think of computer memory, for these purposes, as being a series of cubbyholes. A variable would be one of these cubbyholes, with an identifying label attached. You can store any kind of data in this cubbyhole and VBScript will retrieve the data when it is needed. When you want to reference the data, VBScript simply looks up the memory address, and reports the information stored there.

In line 3 of Listing 2.3, a variable named FreeMegabytes is used to store the results of dividing FreeSpace by 10484576 (which converts bytes to megabytes). As soon as line 3 is run, the variable FreeMegabytes takes on the value of this equation. If you need to refer to the number of free megabytes of disk space anywhere else in the script, you do not have to repeat this equation; instead, you can simply reference the variable FreeMegabytes. This is shown in line 4, where the variable is echoed to the screen.

Example 2.3. Using Variables

1 Set objWMIService = GetObject("winmgmts:")
2 Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
3 FreeMegaBytes = objLogicalDisk.FreeSpace / 1048576
4 Wscript.Echo FreeMegaBytes

When the script in Listing 2.3 runs, a dialog box similar to that shown in Figure 2.2 is displayed.

Free Disk Space Converted to Megabytes

Figure 2.2. Free Disk Space Converted to Megabytes

Note

Note

Notice that the equation used the number 1048576 instead of 1,048,576 (with commas separating the thousands). You cannot use a comma or any other character to separate thousands in VBScript. Instead, you must run all the digits together. This is true for numbers hard-coded into the script as well as for numbers that are entered as command-line arguments or in response to a prompt.

Formatting Script Output

The value 10340.4458007813 (meaning 10,340 megabytes of free space) is probably more meaningful to the typical system administrator than the value 10842824704. However, the numbers after the decimal point are more of a distraction than they are useful information. Fortunately, VBScript provides several different ways to modify script output. For example, the Int function causes VBScript to display only the integer portion of a number, leaving off all digits following the decimal point. The Int function is shown in line 4 of Listing 2.4.

Example 2.4. Formatting Output

1 Set objWMIService = GetObject("winmgmts:")
2 Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
3 FreeMegaBytes = objLogicalDisk.FreeSpace / 1048576
4 Wscript.Echo Int(FreeMegaBytes)

When the script in Listing 2.4 runs, a dialog box similar to that shown in Figure 2.3 appears. The Int function strips away all the digits following the decimal point, leaving only the integer portion of the original value. Additional formatting commands (covered later in this chapter) can be used to add a comma to the output, resulting in the displayed value 10,340.

Using the Int Function to Format Output

Figure 2.3. Using the Int Function to Format Output

Constants

In the script in Listing 2.3, the amount of free megabytes is calculated by taking the value of the FreeSpace property and dividing it by the hard-coded value 1048576. (Hard-coded values such as this are often referred to as literals because they do not stand for something else but literally represent the value.)

In a small script such as this (particularly a small script written for your own use), hard-coding literal values usually does not pose much of a problem. However, in a larger script, particularly one used in an enterprise setting, literals can lead to at least two problems.

For one thing, in a small script it might be obvious that 1048576 is the value required to convert bytes (the value returned from the FreeSpace property) to megabytes. In a larger script, however, one that includes a number of mathematical equations, this might be less obvious. This is especially true in an enterprise setting, in which multiple administrators might use — and modify — the same script. You might know what the 1048576 represents, but another administrator charged with modifying the script might not.

The fact that scripts often need to be modified raises a second issue. Literal values not only can be confusing but can also require extra work for anyone modifying the script. Suppose this same procedure, converting kilobytes to megabytes, is used five or six times throughout a script. If you later decide to convert the value to gigabytes rather than megabytes, you will have to correctly modify each line of code where the conversion takes place or your script will no longer provide accurate results.

One way to work around the problems that can arise from the use of literals is to use constants instead. Constants are similar to variables in that they are places to store data. Unlike variables, however, after a constant has been defined (that is, after it has been assigned a value), it cannot be changed while the script is running. By assigning important items, such as the value required to convert bytes to megabytes, to a constant, you ensure that the value cannot be changed, inadvertently or otherwise.

In the script in Listing 2.5, a constant named CONVERSION_FACTOR is defined in line 1 and is assigned the value 1048576. Later in the script (line 4), the number of bytes of free disk space is converted to the number of megabytes of free disk space. Instead of the literal value 1048576, the constant CONVERSION_FACTOR is used. Both equations return the same result; however, the equation in Listing 2.5 is easier to read and understand.

Example 2.5. Using Constants

1 Const CONVERSION_FACTOR = 1048576
2 Set objWMIService = GetObject("winmgmts:")
3 Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
4 FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
5 Wscript.Echo Int(FreeMegaBytes)

Another benefit to using constants is that they can be defined once and then used multiple times throughout the same script. For example, an expanded version of the script in Listing 2.5 might require you to convert bytes to megabytes several times during the running of the script. Rather than using the literal value in each equation, use the constant instead. If you later decide to convert bytes to gigabytes, you will have to change only the value of the constant; you will not have to change the value used in each equation.

Strings

As you write more sophisticated scripts, you will begin to encounter different types of data (a topic covered in more detail later in this chapter). In line 1 of Listing 2.5, for example, numeric data was used to assign the literal value 1048576 to the constant CONVERSION_FACTOR:

Const CONVERSION_FACTOR = 1048576

This line of code works because a numeric value is being assigned to the constant. Anytime you assign a numeric value to a variable or a constant, you simply type the equals sign followed by the value.

However, unexpected results occur if you try to assign an alphanumeric value (typically referred to as a string value) using this same approach. For example, the following code sample attempts to assign the string atl-dc-01 to the variable Computer and then echo the value of that variable:

Computer = atl-dc-01
Wscript.Echo Computer

When this script runs, however, the dialog box shown in Figure 2.4 appears.

Improperly Assigning String Data to a Variable

Figure 2.4. Improperly Assigning String Data to a Variable

How did the value –1 get assigned to the variable Computer? When VBScript encounters a set of alphanumeric characters that is not surrounded by quotation marks, it assumes that these characters represent the name of a variable. If it sees a “stray” hyphen, it assumes that the hyphen represents a minus sign. As a result, VBScript interprets the line Computer = atl-dc-01 as “The variable Computer is to be assigned the value of the variable atl minus the value of the variable dc minus 01.” Because atl and dc are viewed as new variables that have not been initialized, they are assigned the value 0. VBScript thus interprets this line of code as if it were written like this:

Computer = 0 - 0 - 1

As a result, the variable Computer receives the erroneous assignment –1.

When you assign a string value to a variable or a constant, you must enclose that value within quotation marks; this is the only way to ensure that VBScript treats the string as an alphanumeric value and not as a variable. For example, the following code sample correctly assigns the string atl-dc-01 to the variable Computer and then echoes the results:

Computer = "atl-dc-01"
Wscript.Echo Computer

When this script runs, the dialog box shown in Figure 2.5 appears.

Properly Assigning String Data to a Variable

Figure 2.5. Properly Assigning String Data to a Variable

Strings as Variables

Strings are often used to assign values to variables. For example, the sample script in this chapter uses the following code to bind to WMI. (For information about binding to WMI, see “WMI Scripting Primer” in this book.)

Set objWMIService = GetObject("winmgmts:")

This code always connects you to the local computer. This is fine unless you are a system administrator responsible for managing a remote computer or two. In that case, you might want a script that can retrieve the free disk space from a remote computer. That would allow you to sit at your workstation and check the available disk space on any of the computers under your control.

When you are using WMI, it is possible to connect to a remote computer simply by including the computer name as part of the moniker passed to GetObject. For example, the following line of code binds to the WMI service on the remote computer atl-dc-01:

Set objWMIService = GetObject("winmgmts://atl-dc-01")

You can use the preceding code to write a script that binds to this one remote computer. In an enterprise setting, however, you might want a more flexible script, one that can bind to any remote computer.

One way to do this is to edit the script every time you run it, replacing one hard-coded computer name with another. A much better approach is to provide a way for the script to accept input as it runs, and thus operate against a computer whose name has been entered as a command-line argument.

User input methods will be discussed later in this chapter. Before that discussion takes place, however, it is important to understand how string values (such as computer names) can be assigned to a variable, and then used as part of the script code.

For example, in line 2 of Listing 2.6, the string value “atl-dc-01” is assigned to the variable Computer. In line 3, that variable is used to bind to the WMI service on the computer atl-dc-01. However, this is not done by hard-coding the value atl-dc-01 into the code, but instead by using the value of the variable Computer.

Example 2.6. Using Strings

1 Const CONVERSION_FACTOR = 1048576
2 Computer = "atl-dc-01"
3 Set objWMIService = GetObject("winmgmts://" & Computer)
4 Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
5 FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
6 Wscript.Echo Int(FreeMegaBytes)

In a small demonstration script such as this, assigning the string to a variable actually requires more effort than hard-coding the value. However, this script does illustrate an important concept: You can assign a value to a variable, and then use that variable in place of a hard-coded value.

Why is that important? Imagine that this script was designed to retrieve free disk space from 100 computers. Instead of hard-coding separate WMI binding strings for each computer, you could create a single binding string using the variable Computer. Your script could then run that single line of code 100 times, each time replacing the value of Computer with a different computer name.

For the moment, however, you have to focus only on line 3 of Listing 2.6:

Set objWMIService = GetObject("winmgmts://" & Computer)

Here is how VBScript interprets that line of code:

  1. It reads everything up to the second quotation mark. In other words:

    Set objWMIService = GetObject("winmgmts://"
    
  2. It reads the ampersand (&), which essentially means, “Append whatever comes next to the string.” What follows the ampersand is the variable Computer, which has been assigned the value atl-dc-01. VBScript will now see the line as being:

    Set objWMIService = GetObject("winmgmts://atl-dc-01"
    
  3. It reads the closing parenthesis character. VBScript requires you to have an equal number of opening and closing parentheses. If the closing parenthesis is not included, you will receive an error message. VBScript now reads the line of code as:

    Set objWMIService = GetObject("winmgmts://atl-dc-01")
    
  4. Having reached the end of the line, it runs the statement. In turn, the script will connect to the WMI service on atl-dc-01. To connect to the WMI service on a different computer, all you have to do is change the value of the variable Computer.

Concatenating Strings

Concatenation is the process of combining two or more strings into a single string. (You can also combine strings with numeric or date values.) Concatenation is often used to provide more readable or more meaningful output. For example, the script in Listing 2.4 returns the value 10340. This is very useful information, provided you know that the script is designed to return the number of megabytes of free disk space on drive C. But if you do not know what the script is designed to do, that output will be meaningless.

Among other things, concatenation helps you provide context for your script output. For example, rather than displaying the value 10340, you might want to display a message similar to “There are 10340 megabytes of free disk space.” To do this, you must combine the following three items:

“There are ” — a simple string representing the start of the message.

FreeMegabytes — the variable containing the number of free megabytes on the drive.

“ megabytes of free disk space.” — a second string representing the end of the message.

As shown in lines 6 and 7 of Listing 2.7, you concatenate items in VBScript by using the ampersand (&).

Example 2.7. Concatenating Strings

1 Const CONVERSION_FACTOR = 1048576
2 Computer = "atl-dc-01"
3 Set objWMIService = GetObject("winmgmts://" & Computer)
4 Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
5 FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
6 Wscript.Echo "There are " & Int(FreeMegaBytes) & _
7     " megabytes of free disk space."

Note

Note

The underscore (_) at the end of line 6 is known as the line continuation character and is used to indicate a statement break. This means that lines 6 and 7 should be treated as one line; the line was simply too long to fit in the allotted space. Statement breaks are covered in more detail later in this chapter.

Alternatively, you might have assigned the value “There are” to a variable named MessageStart and the value “megabytes of free disk space.” to a variable named MessageEnd. You could then have concatenated the three variables like this:

Wscript.Echo MessageStart & Int(FreeMegabytes) & MessageEnd

If you look closely at lines 6 and 7, you will notice that blank spaces were hard-coded into the string values “There are” and “megabytes of free disk space.” This is required because the ampersand does not insert any spaces between the items being concatenated. For example, suppose you leave out the blank spaces, like this:

Wscript.Echo "There are " & Int(FreeMegaBytes) & " megabytes of free disk
space."

In this case, the resulting message box will run the three values together, as shown in Figure 2.6.

Incorrectly Concatenating String Values

Figure 2.6. Incorrectly Concatenating String Values

For simple forms of concatenation, you can work around this problem by using a comma rather than an ampersand when combining the values:

Wscript.Echo "There are ", Int(FreeMegaBytes), " megabytes of free disk space."

When the items are separated by a comma, a blank space is automatically inserted between the items. As a result, the message box is properly formatted, as shown in Figure 2.7.

Correctly Concatenating String Values

Figure 2.7. Correctly Concatenating String Values

Collections

Up to this point in the chapter, the scripts have been designed to retrieve the amount of free space on drive C for a specified computer. Determining free space on a single drive is a common administrative task, particularly when you are working with user workstations that are likely to have only one hard drive. Because the intention was to retrieve free disk space for only drive C, the DeviceID (a property of the Win32_LogicalDisk class) was hard-coded into the script.

Of course, other computers, including most servers, are likely to have multiple drives. For these computers, determining the free space on drive C tells only part of the story; as a system administrator, you need to know the amount of free space on drive D, drive E, and any other drives installed on the computer.

However, this creates a problem: How do you know which drives are installed on a given computer? In theory, you could check for free space on drives C through Z. But if a computer does not have, say, a drive E, the script will fail. Although you can include code designed to handle these errors and prevent the script from failing, the resulting script will be extremely long, making it difficult to read and maintain. Such a script will also be extremely inefficient; even if a computer has only a single drive, the script will nonetheless attempt to retrieve the free space on the nonexistent drives D through Z.

Fortunately, Automation objects often return information in the form of collections. Like stamp collections or coin collections, Automation collections are simply a group of related objects. For example, the script in Listing 2.8 uses the WMI method InstancesOf (line 4) to return not just a specific drive but a collection consisting of all the logical disks installed on the computer. If the computer has four drives (C, D, E, and F), the collection will have four items, one for each drive.

Example 2.8. Using Collections

1 Const CONVERSION_FACTOR = 1048576
2 Computer = "atl-dc-01"
3 Set objWMIService = GetObject("winmgmts://" & Computer)
4 Set colLogicalDisk = objWMIService.InstancesOf("Win32_LogicalDisk")
5 For Each objLogicalDisk In colLogicalDisk
6     FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
7     Wscript.Echo objLogicalDisk.DeviceID & " " & Int(FreeMegaBytes)
8 Next

Having information returned as a collection means you do not have to guess how many drives are installed on a computer. Instead, you simply ask for the collection (all the instances of disk drives installed on the computer). After the collection has been returned, you can use a For Each loop (also known as an iteration loop) to access each individual item in the collection.

For Each

The For Each statement provides a simple way to iterate through all the items in a collection. Unlike the For Next statement (discussed later in this chapter), For Each does not require you to know how many items are in the collection. Instead, it simply begins with the first item in the collection and continues until it has iterated through every item.

A typical For Each loop looks like this:

For Each objLogicalDisk In colLogicalDisk
    Wscript.Echo objLogicalDisk.DeviceID
Next

The individual items that make up this loop are described in Table 2.2.

Table 2.2. Components of the For Each Statement

Item

Description

objLogicalDisk

Variable name representing the individual disk drive instances.

colLogicalDisk

Variable name given to the collection of disk drives retrieved using WMI.

For Each objLogicalDisk in colLogicalDisk

Starts the loop. The basic syntax can be read as “For Each instance of an object in a collection of objects” do something. In this example, this can be read as “For each individual disk drive in the collection of disk drives installed on this computer...”

Wscript.Echo objLogicalDisk.DeviceID

Commands carried out for each disk drive in the collection. (This example has only one command, but any number of lines of code are permitted between the For Each and Next statements.)

Notice that individual disk drives are referenced using the variable objLogicalDisk and the appropriate property (in this case, DeviceID). The value of this property will change each time through the loop. For example, on a computer with drives C, D, and E, objLogicalDisk.DeviceID will equal C on the first iteration because C is the DeviceID for the first drive in the collection. On subsequent passes through the loop, objLogicalDisk.DeviceID will equal D and then E.

Next

Indicates the end of the loop.

When working with collections, you typically iterate through the entire collection rather than refer to a single item within the collection. For example, suppose your collection consists of disk drives C, D, E, F, and G, and you want to echo only the available drive space on drive G. To do this, you will have to set up a For Each loop and begin iterating through the set of disk drives. For each drive in the collection, you can check the drive letter, and echo the available space only for drive G.

Tip

Tip

There is no straightforward way to avoid iterating through the entire collection. However, you can make your scripts more efficient by limiting the number of items in the collection. For example, your WMI query can specify that data should be returned only for instances of the Win32_DiskDrive class in which the drive letter is equal to G. The script still returns a collection, and you still have to iterate all the items within that collection. In this case, however, the collection contains just one item, making that iteration much faster and more efficient.

Collections with Zero Items

It is possible for a collection to contain zero items. For example, consider this script sample, which returns the set of all tape drives installed on a computer:

Set objWMIService = GetObject("winmgmts:")
Set colTapeDrives = objWMIService.InstancesOf("Win32_TapeDrive")
For Each objTapeDrive In colTapeDrives
    Wscript.Echo objTapeDrive.Name
Next

If this script is run on a computer that does not have a tape drive, it will appear that nothing happened. In truth, the script will run as expected. However, because the computer does not have a tape drive, the resulting collection of all tape drives installed on the computer will have zero items in it.

When run on a computer without a tape drive, the script will:

  1. Connect to the WMI service.

  2. Retrieve the collection of tape drives installed on the computer.

  3. Set up a For Each loop to iterate through the entire collection, echoing the name of each individual tape drive in the collection.

    However, because no items are in the collection, the For Each loop and any commands included within that loop are not actually run. Instead, the script skips the For Each loop and picks up with the first line following the Next statement. In this sample script, however, no lines of code follow the Next statement, meaning that the script simply stops.

There is no obvious way to tell whether the script actually ran. One way to improve this script is to use the Count property to determine how many items are in the collection. For example, this script sample uses the Count property to echo the number of tape drives installed on a computer:

Set objWMIService = GetObject("winmgmts:")
Set colTapeDrives = objWMIService.InstancesOf("Win32_TapeDrive")
Wscript.Echo colTapeDrives.Count

Your script can use the Count property to determine the number of items in the collection, and then do one of two things:

Echo the item properties if one or more items are in the collection.

Echo a message such as “No tape drives are installed on this computer.” if the collection contains zero items.

This script might look like the following (the use of the If-Then-Else statement is explained later in this chapter):

Set objWMIService = GetObject("winmgmts:")
Set colTapeDrives = objWMIService.InstancesOf("Win32_TapeDrive")
If colTapeDrives.Count = 0 Then
    Wscript.Echo "No tape drives are installed on this computer."
Else
    For Each objTapeDrive In colTapeDrives
        Wscript.Echo objTapeDrive.Name
    Next
End If

Looping

Scripts that monitor or measure system resources typically need to collect data at periodic intervals. For example, it is unlikely that you would measure free disk space moments after installing a new hard disk and then never check again to be sure there was still space available on the disk. Instead, you are likely to check free disk space at regular intervals, perhaps once a week, once a day, or even once an hour, depending on the computer being monitored.

If there is a relatively long period of time between data collections, you might want to run the script as a scheduled task. This way, you can schedule the script to run every morning at 2:00 A.M., and you never need to give it a second thought.

However, using scheduled tasks is not always an option. For example, suppose you want to measure processor use on a computer every 10 seconds until you have collected 500 samples. Although you can create 500 scheduled tasks, one right after another, this is far more trouble than it is worth. A better approach is to run a single script that collects all 500 samples for you.

For Next

One way to get a single script to run the same set of commands over and over is to enclose those commands within a For Next loop, which allows you to run lines of code a specified number of times.

For example, the script shown in Listing 2.9 checks free disk space on a computer every hour for 12 hours. To do this, a For statement is used on line 5 to indicate that the enclosed code block should be run 12 times. Lines 6–10 include the code required to determine the amount of free space for each disk drive on the computer, and line 11 pauses the script for one hour (using a constant that pauses the script for 3,600,000 milliseconds). Line 12 is simply the Next statement, which marks the end of the loop.

When the script runs, a connection is made to the remote computer atl-dc-01. The script retrieves the free disk space information and then pauses for one hour. After that hour, the script returns to the first statement of the For Next loop and retrieves free disk space information for a second time. This continues until the disk space information has been retrieved 12 times. After that, the script runs the line of code following the Next statement. Because no lines of code follow that statement, the script completes.

Example 2.9. Running Commands Multiple Times

 1  Const CONVERSION_FACTOR = 1048576
 2  Const ONE_HOUR = 3600000
 3  Computer = "atl-dc-01"
 4  Set objWMIService = GetObject("winmgmts://" & Computer)
 5  For i = 1 to 12
 6      Set colLogicalDisk = objWMIService.InstancesOf("Win32_LogicalDisk")
 7      For Each objLogicalDisk In colLogicalDisk
 8          FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
 9          Wscript.Echo objLogicalDisk.DeviceID & " " & Int(FreeMegaBytes)
10      Next
11      Wscript.Sleep ONE_HOUR
12  Next

The For Next statement allows you to run a block of code a specific number of times. This should not be confused with a For Each statement. For Each is used to iterate through the individual items within a collection. For Next is used to run a particular set of statements a specified number of times.

To use a For Next statement, you must determine both a starting point and an ending point. Because For Next statements are typically designed to run a set of statements X number of times, you will generally start with 1 and end with X. Therefore, to do the same thing 10 times, you start with 1 and end with 10.

Note

Note

You can pick an arbitrary starting point (for example, 314 or 6,912) and then conclude with the appropriate end point (324 or 6,922). However, your code will be easier to read and maintain if you start with 1 and end with 10.

The For Next statement requires you to use a loop variable (also known as a counter) that keeps a running tally of how many times the code has run. For example, the variable i is used as the counter in the following code sample. The counter starts at 1 and runs the lines of code contained within the For Next statement block. After all the statements have run, the counter is automatically incremented by 1, meaning i is now equal to 2. The script loops back to the beginning of the For Next statement and checks to see whether the value 2 is still within the valid execution range. Because it is, the code within the For Next statement block runs a second time.

For i = 1 to 5
    Wscript.Echo i
Next
Wscript.Echo "For Next loop complete."

What happens when i is equal to 6? The script will loop back to the beginning of the For Next statement, and check to see if 6 is part of the valid execution range. Because it is not, the For Next statement will immediately stop, and running of the script will continue with the first line following the Next statement. In this case, that is the line that echoes the message “For Next loop complete.”

The script output looks like this:

1
2
3
4
5
For Next loop complete.

Note

Note

There are times when you want to run the same set of statements over and over; however, you might have no way of determining in advance how many times you need to run that code. For example, suppose you want to check free disk space once every hour and continue to run this check until disk space drops below a specified amount. In a case such as this, you want the script to start and continue to run until disk space has dropped below the threshold, regardless of how many iterations that requires. In these situations, you should use a Do Loop, discussed later in this chapter.

Making Decisions

One of the primary reasons for using scripts as a system administration tool is that scripts reduce the need for hands-on human intervention. The scripts introduced thus far in this chapter go a long way towards this goal; the version shown in Listing 2.8, for example, can connect to any computer (even a remote one), bind to the WMI service, and determine the amount of free disk space on each of the hard drives installed on that computer. By running this script on a regular basis, administrators can receive advance notification if any drive begins to run low on disk space.

However, it is still up to a system administrator to analyze the script output and determine whether a disk is running low on disk space. The script can be improved by examining the amount of free space and issuing a notification only if the free space has dropped below a specified level. With this approach, administrators are notified only if a disk is running out of space. No notification means that all the disks are in compliance and no action is required.

VBScript provides a number of programming constructs that allow scripts to “make decisions.” This means a script can analyze a particular piece of data and then take a specified course of action based on the value of that data.

The simplest form of decision-making code is the If Then statement, which examines the value of a particular piece of data and compares it against a predetermined value (for example, if the amount of free disk space is less than 100 megabytes). If the statement is True (for example, if only 99 megabytes of disk space are available), the script carries out some action. If the statement is not true, no action is taken.

This simple type of decision-making is shown in Listing 2.10. In line 8, the script checks to see whether the amount of free space is less than 100 megabytes (by comparing the value with the constant WARNING_THRESHOLD). If this conditional statement is True (for example, if a drive has only 99 megabytes of free space), the statement immediately following the If-Then statement runs. In this script, that statement appears on line 9, which echoes the message that the drive is low on disk space.

If the conditional statement is False (for example, if the drive has 101 megabytes of free disk space), line 9 is not run. Instead, processing passes to line 10, which marks the end of the If Then code block, and the script continues with line 11.

Example 2.10. Making Decisions

 1  Const CONVERSION_FACTOR = 1048576
 2  Const WARNING_THRESHOLD = 100
 3  Computer = "atl-dc-01"
 4  Set objWMIService = GetObject("winmgmts://" & Computer)
 5  Set colLogicalDisk = objWMIService.InstancesOf("Win32_LogicalDisk")
 6  For Each objLogicalDisk In colLogicalDisk
 7      FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
 8      If FreeMegaBytes < WARNING_THRESHOLD Then
 9          Wscript.Echo objLogicalDisk.DeviceID & " is low on disk space."
10      End If
11  Next

Taking Multiple Actions by Using If Then Else

The script shown in Listing 2.10 displays a warning message if a disk drive is low on disk space. If a disk drive has adequate free space, however, no message of any kind is displayed. For a simple monitoring script this is probably acceptable. On the other hand, a user running this script would have no way of knowing whether the lack of output was because all the drives had adequate disk space, or whether the lack of output was because the script failed to run for some reason.

In other words, sometimes you want your script to evaluate a condition and then take a different course of action based on that evaluation. For example, you might want to echo a warning message if a drive is low on disk space and echo a “No problem” message if a drive has adequate disk space. This kind of approach can be implemented by using an If Then Else statement.

If-Then-Else statements work exactly as the name implies: If a condition is True (or False), Then take this course of action, Else take this course of action. If disk space is low, echo a warning message; otherwise, echo a “No problem” message.

An example of this is shown in Listing 2.11. In line 8, the amount of free disk space on a drive is compared against a warning threshold. If the conditional statement is True (that is, if the amount of free disk space is less than the warning threshold), then line 9 runs.

But what if the conditional statement is False? To handle this possibility, an Else statement is included on line 10. If the conditional statement is False, and the drive has adequate space, then line 11, the line immediately following the Else statement, runs instead.

Example 2.11. Using an If-Then-Else Statement

 1 Const CONVERSION_FACTOR = 1048576
 2 Const WARNING_THRESHOLD = 100
 3 Computer = "atl-dc-01"
 4 Set objWMIService = GetObject("winmgmts://" & Computer)
 5 Set colLogicalDisk = objWMIService.InstancesOf("Win32_LogicalDisk")
 6 For Each objLogicalDisk In colLogicalDisk
 7     FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
 8     If FreeMegaBytes < WARNING_THRESHOLD Then
 9         Wscript.Echo objLogicalDisk.DeviceID & " is low on disk space."
10     Else
11         Wscript.Echo objLogicalDisk.DeviceID & " has adequate disk space."
12     End If
13 Next

It is possible to construct more elaborate scenarios, scenarios that can take more than just two possible courses of action. Two different ways to construct these scenarios are discussed later in this chapter.

Arrays

Collections are an excellent way to package information because they allow you to work with any number of items, even if you do not know the details of any of those items. For example, the script introduced in Listing 2.8 allows you to retrieve the amount of free disk space for all the drives installed on a computer, even if you have no idea how many drives are installed on that computer. To carry out an action on each item in the collection, you simply use a For Each loop to iterate through the collection item by item.

Automation objects can create collections for you. However, you might have other information (information not returned from an Automation object) that might be easier to manipulate if you can iterate through the set of items one by one. Suppose you want to check the available disk space on three computers rather than just one. You can write the code to check the first computer, copy and paste the code, and then modify the pasted code to check free disk space on the second computer. You can repeat this process again to check for free disk space on the third computer.

Although this approach works, it can quickly become tedious, particularly if you need to check 100 computers. In addition, suppose you need to make a change in the code, perhaps to return not only the free space on the drive but also the total size of the drive. To make that change, you need to change all 100 instances of the code, a process that not only takes a long time to complete but greatly increases the likelihood that you will make an error somewhere along the way.

A better approach is to use a For Each loop to iterate through a collection of computers, checking for free disk space on each one. This can be done by placing the computer names in an array, a data structure that can be used in much the same way as a collection.

The script in Listing 2.12 places the names of three computers (atl-dc-01, atl-dc-02, and atl-dc-03) in an array and then uses a For Each loop to connect to and retrieve free disk space information from each computer. In line 3, the script creates an array named Computers by using the Array function and specifying the three computer names as the function parameters. (The names are enclosed in quotation marks because they are strings.) In line 4, a For Each loop us used to iterate through all the items in the Computers array.

Example 2.12. Using an Array

 1 Const CONVERSION_FACTOR = 1048576
 2 Const WARNING_THRESHOLD = 100
 3 Computers = Array("atl-dc-01", "atl-dc-02", "atl-dc-03")
 4 For Each Computer In Computers
 5     Set objWMIService = GetObject("winmgmts://" & Computer)
 6     Set colLogicalDisk = objWMIService.InstancesOf("Win32_LogicalDisk")
 7     For Each objLogicalDisk In colLogicalDisk
 8         FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
 9         If FreeMegaBytes < WARNING_THRESHOLD Then
10             Wscript.Echo Computer & " " & objLogicalDisk.DeviceID & _
11                 " is low on disk space."
12         End If
13     Next
14 Next

Although arrays are similar to collections, there is one primary difference. As a script writer, you have little control over collections. If you query WMI for a list of disk drives, you will get those disk drives back in whichever order WMI chooses. Furthermore, you will not be able to readily access individual drives without iterating through the entire collection.

By contrast, you can control the order of information in an array because you typically populate the array yourself. Furthermore, you have the ability to access individual items in an array without having to iterate through the entire set. This is because each item in an array is assigned an index number. In VBScript, the first item in an array is assigned index number 0, with subsequent items assigned index numbers 1, 2, 3, and so forth. The array created in Listing 2.12 therefore contains the items and index numbers shown in Table 2.3. Notice that the highest index number will always be 1 less than the number of items in the array.

Table 2.3. Index Numbers in an Array

Index Number

Item

0

atl-dc-01

1

atl-dc-02

2

atl-dc-03

You can use these index numbers to access individual items within the array. For example, this line of code will echo atl-dc-02, the value of item 1 (based on index number) in the array:

Wscript.Echo Computers(1)

To echo the value of a different item in the array, simply replace the value 1 with the appropriate index number.

Additional methods for creating arrays and for accessing the individual items within those arrays are discussed later in this chapter.

Input

The script in Listing 2.12 is designed for an organization in which the computing infrastructure is not expected to change. Needless to say, a static infrastructure such as this is the exception rather than the rule; most organizations have a more dynamic environment. Although there might be only three servers (atl-dc-01, atl-dc-02, atl-dc-03) that need monitoring today, there is no guarantee that only those three servers will need monitoring tomorrow.

Because of that, you might not want to hard-code computer names into a script. Hard-coding items such as computers names can lead to a pair of related problems:

Lack of flexibility. The script in Listing 2.12 retrieves available disk space information only for the computers atl-dc-01, atl-dc-02, and atl-dc-03. If you need to retrieve free disk space for computer atl-dc-04, you will need to modify the script.

Frequent updating. The script in Listing 2.12 was designed to retrieve free disk space information for a particular set of computers (for example, all the domain controllers in a particular location). Each time a new domain controller is added, or each time an existing domain controller is retired, the script will need to be updated. If this is the only script used in your organization, this might not pose much of a problem. If you use scores of scripts in your organization, however, you are likely to waste more time modifying scripts than you save by using those scripts in the first place.

There are a number of ways to enter information, such as server names, into a script. (For more information about these methods, see “Creating Enterprise Scripts” in this book.) Perhaps the easiest way is to have the user specify this information as command-line arguments each time the script is run.

An argument (also known as a parameter) is information supplied along with the command that actually runs the script. For example, suppose you typically start a script by typing the following at the command line:

cscript FreeDiskSpace.vbs

An argument is any information added to the end of that command. For example, this command has three arguments, one for each computer name:

cscript FreeDiskSpace.vbs atl-dc-01 atl-dc-02 atl-dc-03

In addition to supplying arguments, you must include code in the script that makes use of those arguments. This type of coding is covered in detail in the “WSH Primer” chapter of this book. A simple example is also shown in Listing 2.13. In line 9 of this script, a For Each loop is established to iterate through the set of arguments supplied when the script was started. In this script, each argument is successively assigned to the variable Computer and then used to connect to the WMI service on that computer (line 10). The first time the For Each loop runs, Computer will be assigned the value atl-dc-01. On subsequent iterations, Computer will be assigned the value atl-dc-02, and then atl-dc-03.

Example 2.13. Getting User Input

 1 Const CONVERSION_FACTOR = 1048576
 2 Const WARNING_THRESHOLD = 100
 3
 4 If WScript.Arguments.Count = 0 Then
 5     Wscript.Echo "Usage: FirstScript.vbs server1 [server2] [server3] ..."
 6     WScript.Quit
 7 End If
 8
 9 For Each Computer In WScript.Arguments
10     Set objWMIService = GetObject("winmgmts://" & Computer)
11     Set colLogicalDisk = objWMIService.InstancesOf("Win32_LogicalDisk")
12     For Each objLogicalDisk In colLogicalDisk
13         FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
14         If FreeMegaBytes < WARNING_THRESHOLD Then
15             Wscript.Echo Computer & " " & objLogicalDisk.DeviceID & _
16                 " is low on disk space."
17         End If
18     Next
19 Next

One benefit of using arguments is that they are automatically placed in a collection (Wscript.Arguments). This makes it easy to iterate through the arguments supplied to a script: You simply set up a For Each loop and iterate through each argument in the collection, exactly as you would iterate through the individual disk drives in a collection of disk drives.

Because arguments are placed in a collection, it is easy to verify how many arguments, if any, were supplied when starting a script. In line 4 of the script, Wscript.Arguments.Count is used to determine how many arguments were supplied (which will equal the number of items in the arguments collection). If Count equals 0, meaning no arguments were supplied, a set of usage instructions is displayed, and the script terminates (using the command WScript.Quit).

Error Handling

The script in Listing 2.13 has a potential flaw in it. Suppose the user enters an invalid server name as an argument. When the script attempts to connect to this nonexistent computer, it will fail with the error message, “The remote server machine does not exist or is unavailable.”

Of course, flaws like this are inherent in all the scripts used so far in this chapter, including those in which the computer names are hard-coded. After all, the script cannot distinguish between an invalid name and a valid name for a computer that, for some reason, is not available over the network. For example, suppose you run the script in Listing 2.12, and the computer named atl-dc-01 is offline. At that point, the script fails. In turn, you fail to retrieve the free disk space not only on atl-dc-01 but also on atl-dc-02 and atl-dc-03, even if those computers are connected to the network and functioning properly. This occurs because the script will stop running before it even attempts to connect to atl-dc-02 or atl-dc-03.

The inability to connect to atl-dc-01 is an example of a run-time error, an error that occurs after the script has started. (By comparison, a syntax error, such as a misspelled command, is generated and the script stops running before any lines of code are actually processed.) To help protect against run-time errors, you can include the VBScript error-handling mechanism On Error Resume Next in your scripts.

Without error handling, a script stops immediately upon encountering a run-time error. With error handling, a script does not stop but instead attempts to run the next line. The script proceeds by skipping the lines that generate errors and running lines that do not.

Using the Err Object

On Error Resume Next allows your script to continue to function should a run-time error occur. There are at least two potential problems with this, however. For one thing, no error message is generated to let you know that an error occurred, so you will have no way of knowing where the script failed.

For another, you might prefer that a script not attempt to run every single line in the event of a run-time error. For example, consider a script that does the following:

  1. Connects to a remote computer.

  2. Copies a set of files from the local computer to the remote computer.

  3. Deletes the original set of files from the local computer.

Suppose you run this script and the remote computer is not available. Here is what could happen.

  1. The script attempts to connect to the remote computer and fails. However, On Error Resume Next ensures that the script continues to run.

  2. The script attempts to copy files to the remote computer. This fails because the remote computer is not accessible.

  3. The script deletes the files from the local computer. Unfortunately, this succeeds because the local computer is available. As a result, the files are deleted from the local computer but are not copied to the remote computer.

Fortunately, you can use the intrinsic VBScript Err object to determine whether an error occurred and, if so, take the appropriate action.

The Err object is automatically created each time you run a script. (There is only one Err object per script instance.) The Err object includes several properties, including the three shown in Table 2.4. Whenever your script encounters a run-time error, these properties are automatically filled in with information that identifies the error.

Table 2.4. Err Object Properties

Property

Description

Description

Description of the error. This can be used to inform the user that an error has occurred simply by echoing the value: Wscript.Echo Err.Description

Number

Integer uniquely identifying the error that occurred. The number might represent an intrinsic VBScript error number, or it might represent an error number derived from an Automation object. To determine where the error number came from, use the Source property.

Source

Class name or programmatic identifier (ProgID) of the object that caused the error. If VBScript caused the error, you typically see “Microsoft VBScript runtime error” as the source. If an Automation object is responsible for the error, you see the ProgID (for example, “Word.Application”).

When a script is started, VBScript assigns the default value 0 (no error) to the Number property. If the script encounters an error, the value of the Number property will change accordingly. This enables you to periodically check to see whether your script has encountered any errors. For example, you might want your script to check error status after it attempts to connect to a remote computer. If Err.Number equals 0 (zero), no error occurred. If Err.Number does not equal 0, an error of some kind has occurred, and you can assume that the attempt to connect to the remote computer failed. As a result, your script can take action based on the fact that the remote computer was unavailable.

This type of error handling is implemented in the script in Listing 2.14. In line 1 of the script, On Error Resume Next enables error handling. In line 10, the script sets up a For Each loop to cycle through a list of server names. In line 11, the script attempts to connect, in turn, to each of these servers.

But what happens if one of these servers is not accessible? In line 11, the script tries to connect to one of these remote servers. If the connection is successful, no error is generated, meaning that Err.Number will remain 0. If the connection fails, however, an error will be generated and Err.Number will be changed to reflect the number corresponding to that error.

If the connection attempt in line 11 fails, On Error Resume Next ensures that the script next tries to run line 12. In line 12, the script checks the value of Err.Number. If the value is anything but 0 (meaning that an error occurred), the script echoes Err.Description and then restarts the loop with the next server name. If the value is 0, this means that the connection succeeded. The script will then retrieve the free disk space on the computer in question.

Example 2.14. Handling Errors

 1 On Error Resume Next
 2 Const CONVERSION_FACTOR = 1048576
 3 Const WARNING_THRESHOLD = 100
 4
 5 If WScript.Arguments.Count = 0 Then
 6     Wscript.Echo "Usage: FirstScript.vbs server1 [server2] [server3] ..."
 7     WScript.Quit
 8 End If
 9
10 For Each Computer In WScript.Arguments
11     Set objWMIService = GetObject("winmgmts://" & Computer)
12     If Err.Number <> 0 Then
13         Wscript.Echo Computer & " " & Err.Description
14         Err.Clear
15     Else
16         Set colLogicalDisk = _
17             objWMIService.InstancesOf("Win32_LogicalDisk")
18         For Each objLogicalDisk In colLogicalDisk
19             FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
20             If FreeMegaBytes < WARNING_THRESHOLD Then
21                 Wscript.Echo Computer & " " & objLogicalDisk.DeviceID & _
22                     " is low on disk space."
23             End If
24         Next
25     End If
26 Next

If you run the script in Listing 2.14 and one of the servers is not accessible, the properties of the Err object will be populated as shown in Table 2.5.

Table 2.5. Values Assigned to Err Object Properties

Property

Value

Err.Description

The remote server machine does not exist or is unavailable

Err.Number

462

Err.Source

Microsoft VBScript run-time error

Clearing Errors

Line 14 of the script in Listing 2.14 uses the Clear method to explicitly reset the properties of the Err object. This is important, because otherwise these properties change only when a new error occurs. When no error occurs, the property values remain the same. This might cause your script to take inappropriate action based on the mistaken notion that another error has occurred.

Here is what can happen if you do not explicitly reset the properties of the Err object. When a script begins, the Err object has the default number 0 and empty source and description properties. If the script cannot connect to the computer atl-dc-01, the Err object properties are set as shown in Table 2.5.

This works as expected. However, what happens when the computer iterates through the loop and attempts to connect to atl-dc-02? In this case, the attempt succeeds, and no error is generated. However, the Err object still contains the property values shown in Table 2.5. Why? Because the Err object is not updated unless an error occurs. Next the script checks the error number and sees 462, the value left over from the failed attempt to connect to atl-dc-01. Because 462 is not equal to 0, the script takes action based on the incorrect assumption that an error occurred and atl-dc-02 must therefore be inaccessible. This same problem occurs when the script attempts to retrieve free disk space on atl-dc-03.

The Clear method overcomes this problem by resetting the properties of the Err object back to the default values. (Number is 0; source and description are empty.) Because the values have been reset, they will correctly reflect the fact that no error occurred when the script attempts to retrieve free disk space for atl-dc-02.

VBScript Reference

The first half of this chapter introduced you to basic concepts underlying VBScript and gave you an idea of the kinds of tasks that can be accomplished using VBScript, particularly when the language is used in conjunction with ADSI or WMI. The second half of the chapter is a more standard reference work, focusing on VBScript methods and functions considered the most useful to system administrators. This is not an exhaustive list of all the things you can do with VBScript; if you need such a list, see the VBScript link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources. Instead, this section of the chapter targets a subset of VBScript functions and methods and attempts to place them in a context useful to anyone writing system administration scripts.

Statement Breaks in VBScript

Many scripting and programming languages make no attempt to match the code that is run with the actual physical lines typed into the text editor. For example, although the following Microsoft® JScript® sample covers nine physical lines of type, JScript treats it as a single code statement. This is because, for the most part, JScript does not recognize the end of a line of code until it sees the termination character (in this case, the semicolon). The actual physical lines of type taken up by the code are irrelevant.

var
objWMI
=
new
Enumerator
(GetObject("winmgmts:")
.
InstancesOf("Win32_process"))
;

By contrast, a similar code statement written in VBScript will generate a syntax error:

Set
objWMI
=
(GetObject("winmgmts:")
.
InstancesOf("Win32_process"))

This is because VBScript uses the carriage return instead of a special line termination character. To end a statement in VBScript, you do not have to type in a semicolon or other special character; you simply press ENTER.

In general, the lack of a required statement termination character simplifies script writing in VBScript. There is, however, one complication: To enhance readability, it is recommended that you limit the length of any single line of code to 80 characters. (In fact, some text editors will not allow a line to extend past 80 characters.) What happens, then, if you have a line of code that contains 100 characters?

Although it might seem like the obvious solution, you cannot split a statement into multiple lines simply by entering a carriage return. For example, the following code snippet returns a run-time error in VBScript because a statement was split by using ENTER.

strMessageToDisplay = strUserFirstName, strUserMiddleInitial, strUserLastName,
strCurrentStatus
Wscript.Echo strMessageToDisplay

You cannot split a statement into multiple lines in VBScript by pressing ENTER because VBScript sees a carriage return as marking the end of a statement. In the preceding example, VBScript interprets the first line as the first statement in the script. Next it interprets the second line as the second statement in the script, and the error occurs because strCurrentStatus is not a valid VBScript command.

Instead, use the underscore (_) to indicate that a statement is continued on the next line. In the revised version of the script, a blank space and an underscore indicate that the statement that was started on line 1 is continued on line 2. To make it more apparent that line 2 is a continuation of line 1, line 2 is also indented four spaces. (This was done for the sake of readability, but you do not have to indent continued lines.)

strMessageToDisplay = strUserFirstName, strUserMiddleInitial, strUserLastName, _
    strCurrentStatus
Wscript.Echo strMessageToDisplay

Line continuation is more complex when you try to split a statement inside a set of quotation marks. For example, suppose you split a WMI statement using a blank space and an underscore:

Set colServiceList = GetObject("winmgmts:").ExecQuery("SELECT * FROM _
    Win32_Service WHERE State = 'Stopped' AND StartMode = 'Auto' ")

If you run this script, you will encounter a run-time error because the line continuation character has been placed inside a set of quotation marks (and is therefore considered part of the string). To split this statement:

  1. Close the first line with quotation marks, and then insert the blank space and the underscore.

  2. Use an ampersand at the beginning of the second line. This indicates that line two is a continuation of the interrupted string in line 1.

  3. Add quotation marks before continuing the statement.

    These quotation marks indicate that this line should be included as part of the quoted string started on the previous line. Without the quotation marks, the script engine would interpret the continued line — Win32_Service — as a VBScript statement. Because this is not a valid VBScript statement, an error would occur.

The revised statement looks like this:

Set colServiceList = GetObject("winmgmts:").ExecQuery("SELECT * FROM " _
    & "Win32_Service WHERE State = 'Stopped' AND StartMode = 'Auto' ")

When splitting statements in this fashion, be careful to insert spaces in the proper location. In the preceding sample, a blank space was added after the word “FROM” and before the closing quotation marks. If the blank space is left out, the string is interpreted incorrectly (notice how the words “FROM” and “Win32_Service” run together), and an error results:

"SELECT * FROMWin32_Service WHERE State = 'Stopped' AND StartMode = 'Auto' "

Working with Variables

Variables are named locations within computer memory that can be used to store data. Most scripting languages allow implicit declaration of variables, enabling you to use a variable without formally declaring your intention to use that variable. For example, you can run the following script without encountering an error of any kind, even though the first line in the script assigns a value to the variable sngDegreesCelsius. Even though this variable has not been declared (meaning that VBScript has not been informed of its existence), the value 11 is assigned to the variable.

sngDegreesCelsius = 11
sngDegreesFahrenheit = ConvertToFahrenheit(sngDegreesCelsius)
Wscript.Echo sngDegreesFahrenheit

Function ConvertToFahrenheit(ByVal sngDegreesCelsius)
    ConvertToFahrenheit = (sngDegreesCelsius * (9/5)) + 32
End Function

Implicit variable declaration can make writing scripts faster and easier; at the same time, however, it can lead to subtle errors that are difficult to diagnose and fix.

To illustrate, the previous script converts 11° Celsius to Fahrenheit (51.8°). The following script should do the same thing, but it echoes the value 32 instead.

sngDegreesCelsius = 11
sngDegreesFahrenheit = ConvertToFahrenheit(sngDegreesCelsius)
Wscript.Echo sngDegreesFahrenheit

Function ConvertToFahrenheit(ByVal sngDegreesCelsius)
    ConvertToFahrenheit = (sngDegresCelsius * (9/5)) + 32
End Function

The preceding script provides an incorrect answer because of a typographical error. Instead of typing sngDegreesCelsius in line 6, the script writer typed sngDegresCelsius, leaving out one of the two e’s in Degrees. As a result, the equation uses the value of sngDegresCelsius instead of sngDegreesCelsius. Because sngDegresCelsius has never been assigned a value, it is treated as an Empty variable with the value 0. Consequently, 0 is multiplied by 9/5, resulting in 0. The script then adds 32 to that 0 and returns an incorrect answer.

Mistakes like this can be difficult to catch. The syntax is correct, so no error message will be generated. You expected to get a numeric value other than 11, and you got one. When embedded inside a much larger script, this typographical error can be very difficult to find and correct.

Declaring Variables in VBScript

To help avoid problems like this, you can explicitly declare all your variables. When explicit variable declaration is in effect, any variables that are not specifically declared in the script will result in a run-time error.

For example, in the following script the explicit declaration of variables is forced by the use of the VBScript Option Explicit statement, and each variable is then declared using a Dim statement:

Option Explicit
Dim sngDegreesCelsius
Dim sngDegreesFahrenehit

sngDegreesCelsius = 11
sngDegreesFahrenheit = ConvertToFahrenheit(sngDegreesCelsius)
Wscript.Echo sngDegreesFahrenheit

Function ConvertToFahrenheit(ByVal sngDegreesCelsius)
    ConvertToFahrenheit = (sngDegresCelsius * (9/5)) + 32
End Function

When the preceding script runs, the scripting host encounters an undeclared variable. As a result, the script halts execution and displays an error message similar to this:

C:ScriptsTempConvert.vbs(10, 5) Microsoft VBScript runtime error: Variable is
undefined:
'sngDegresCelsius'

To declare variables in VBScript:

  1. Use the Option Explicit statement to force variables to be declared. This must be the first noncommented, non-white-space line in the script.

  2. Use a separate Dim statement to declare each variable used in your script. Although you can declare multiple variables with a single Dim statement, limiting each statement to a single variable declaration allows you to add a brief inline comment that explains the purpose of the variable, as shown in the following sample:

    Option Explicit
    Dim intFirstNumber           ' First number in our simple equation
    Dim intSecondNumber          ' Second number in our simple equation
    Dim intTotal                 ' Sum of intFirstNumber and intSecondNumber
    

Initializing Variables

Initializing a variable simply means assigning a beginning (initial) value to that variable. For example, the following lines of code initialize two variables, setting the value of X to 100 and the value of Y to abcde:

X = 100
Y = "abcde"

If you create a variable but do not initialize it (that is, you do not assign it a value), the variable will take on one of two default values:

If the variable is used as a string, the value will be Empty.

If the variable is used as a number, the value will be 0.

For example, the following script creates two variables (X and Y), but does not initialize either variable:

Dim X
Dim Y
Wscript.Echo X & Y
Wscript.Echo X + Y

In line 3 of the script, the two variables are used as strings. (The & operator is used to combine two strings.) When this line runs, the message box shown in Figure 2.8 appears. Because the two variables are Empty, combining the two strings means combining nothing with nothing. As a result, the message box displays an empty string.

Combining Two Uninitialized Variables

Figure 2.8. Combining Two Uninitialized Variables

In line 4 of the script, the two variables are used as numbers. Numeric variables that have not been initialized are automatically assigned the value 0. Thus, this line of the script echoes the sum of 0 + 0, as shown in Figure 2.9.

Adding Two Uninitialized Variables

Figure 2.9. Adding Two Uninitialized Variables

Using the Equals Sign in VBScript

In VBScript, the equals sign (=) has a subtly different meaning than it does in arithmetic. In arithmetic, the equation X = 2 + 2 is read:

“X equals 2 plus 2.”

In VBScript, however, this same statement is read:

“X is assigned the value of 2 plus 2.”

In the preceding example, there is not much difference; either way, X gets assigned the value 4. But consider the following script, which uses a loop to count from 1 to 10:

For i = 1 to 10
    X = X + 1
Next

The second line in this script would appear to be a mathematical impossibility: How can X be equal to X plus 1? The reason why this is a perfectly valid VBScript statement is that it is not an example of a mathematical equation but is instead an example of a variable (X) being assigned a new value. The statement is actually read:

“X is assigned the current value of X plus 1.”

In other words, if X is currently 3, when this statement runs X will be assigned the value 4, that is, 3 (the current value) plus 1.

The fact that the equals sign is actually an assignment sign is also useful in constructing strings. For example, the following script constructs a message from multiple string values:

Message = "This "
Message = Message & "is a "
Message = Message & "test message."
Wscript.Echo Message

When the script runs, the message box shown in Figure 2.10 appears.

Concatenated Message

Figure 2.10. Concatenated Message

Using Constants

Constants represent values that cannot change while a script is running. For example, suppose you have a script that converts Japanese yen to U.S. dollars. Assuming the current exchange rate is 1 yen for every 0.00888759 dollars, you can hard-code the literal value into the script, like the following:

curConvertedPrice = curPriceInYen * 0.00888759

Although this approach works, it does pose some potential problems:

Another administrator editing your script might not understand what 0.00888759 represents.

Exchange rates change frequently. By using the literal value, you must search the script for each occurrence of the literal value and then change this value every time the exchange rate changes. If you overlook one such occurrence, your script will no longer provide accurate information.

Every time you type a literal value, you risk making a mistake that can affect your calculations. For example, inadvertently typing 0.0888759 will drastically affect the equation. One thousand yen converted using the correct rate of 0.00888759 is a little less than $9. The same thousand yen converted using the incorrect rate of 0.0888759 is approximately $89.

To help overcome these problems, use constants rather than literal values. Constants have a number of advantages over literal values. Constants:

Can be given meaningful names. Rather than use a cryptic value such as 0.00888759, you can use a meaningful name such as YEN_TO_DOLLARS_EXCHANGE_RATE.

Are easy to modify. If the exchange rate changes, you have to modify only the single statement where the value of the constant is defined.

Are less prone to typing mistakes because the value (which might be something along the lines of 0.00888759) has to be entered only once. In addition, constants must be predefined the same way variables are predeclared. If you use the Option Explicit statement in VBScript, any mistyped constant name will generate an error.

Cannot be changed, inadvertently or otherwise. After a constant has been defined, any attempt by the script to change its value will generate an error.

Are useful for string values as well as for numeric values. For example, suppose you have a standard message that is repeatedly echoed to users when a script runs. Rather than type the message multiple times, define it once as a constant. If you need to change the message, you need to change it only in one location.

Defining Constants

Constants are defined in VBScript by using the Const statement followed by the name and value of the constant. When you define a constant, you must assign it a literal value; you cannot assign a value to a constant by using a variable, another constant, or a function. For example, the following code sample, which attempts to define a constant using the variable NumberOfDepartments, will generate an “Expected literal constant” error:

NumberOfDepartments = 20
Const NUMBER_OF_DEPARTMENTS = NumberOfDepartments

Instead, assign the constant the literal value 20:

Const NUMBER_OF_DEPARTMENTS = 20

Using Intrinsic Constants

VBScript includes a number of intrinsic constants that can be used to construct message boxes, format output, or carry out other activities. To improve the readability of your scripts, you might want to use these intrinsic constants rather than their numeric equivalents.

For example, the following script sample uses a pair of numeric values to display a message box and then determine which button in that message box was clicked. Although the script works, anyone not familiar with VBScript values would have difficulty reading and editing the script. They must know that 260 means, “Create a message box with a Yes button and a No button, with the second button being the default button,” and that the number 7 means, “The user clicked the No button.”

ConfirmDelete = MsgBox ("Are you sure you want to delete these files?",  _
    260, "Delete all files")
If ConfirmDelete = 7 then
    Wscript.Quit
End If

The following revised version of this script uses VBScript intrinsic constants (VbYesNo, VBDefaultButton2, and VbNo) instead of literal values. This makes the script easier to read and understand.

ConfirmDelete = MsgBox ("Are you sure you want to delete these files?",  _
    VbYesNo OR VBDefaultButton2, "Delete all files")
If ConfirmDelete = VbNo then
    Wscript.Quit
End If

Using intrinsic constants also helps prevent your scripts from breaking each time the scripting language is updated. The VBScript constants are unlikely to change; there is little chance that the constant VbYesNo will be changed to something like VbNoYes. However, the value of those constants can conceivably change the next time VBScript is updated. Using constants can also make it easier to migrate scripts to another language should the need arise. For example, VBScript uses the value –1 to represent True. In Visual Basic .NET, however, True equals 1. It will be much easier to convert a script that uses the constant True than a script that uses the hard-coded value –1.

Most likely you will repeatedly find yourself using two intrinsic constants:

VbCrLf. This is equivalent to pressing the ENTER key and is typically used to format output for display. For example, this statement creates a message that displays a line of text, a blank line, and then a second line of text:

Wscript.Echo "This is the first line of text." & VbCrLF & VbCrLF & _
    "This is the second line of text."

When the preceding script is run using Wscript, a message box similar to the one shown in Figure 2.11 is displayed.

Messages Separated by Using VbCrLf

Figure 2.11. Messages Separated by Using VbCrLf

VbTab. This is equivalent to pressing the TAB key. For example, this statement creates three tab-separated columns:

Wscript.Echo " 1" & VbTab & " 2" & VbTab & " 3"
Wscript.Echo "A" & VbTab & "B" & VbTab & "C"
Wscript.Echo "D" & VbTab & "E" & VbTab & "F"

When the preceding script runs under Cscript, the following output appears in the command window:

1      2      3
A      B      C
D      E      F

Scripts written in VBScript have access only to the intrinsic constants defined in VBScript, and do not have native access to the intrinsic constants found in WMI, ADSI, the Script Runtime library, or other external Automation objects. When you write scripts using VBScript, you can use intrinsic VBScript constants such as VbCrLf or VbYesNo without defining those constants. However, to use intrinsic WMI or ADSI constants, you must explicitly define the value of those constants.

For example, the Script Runtime Drive object includes the intrinsic constant Fixed to indicate a fixed disk drive. Automation languages that have access to the Script Runtime constants can use Fixed without explicitly defining a value for it.

Because VBScript does not have access to this constant, any attempt to use this constant without first defining it will cause the script either to fail or to provide inaccurate information. For example, the following script will run, but it will not identify any fixed disks on your computer:

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set colDiskDrives = objFSO.Drives
For Each objDiskDrive in colDiskDrives
    If objDiskDrive.DriveType = Fixed then
        Wscript.Echo objDiskDrive.DriveLetter
    End if
Next

The script fails because VBScript does not know that Fixed is a constant that has the value 2. Instead of treating Fixed as a constant, VBScript treats Fixed as a variable. Until you assign a value to a variable, that variable is Empty. In this example, then, VBScript looks for disk drives where the DriveType property is equal to 0 rather than equal to 2. Because VBScript cannot find any drives with this property, the script does not return any data.

To make this script work, you must create your own constant named Fixed and explicitly assign it the value 2. This is shown in the following example:

Const Fixed = 2
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set colDiskDrives = objFSO.Drives
For Each objDiskDrive in colDiskDrives
    If objDiskDrive.DriveType = Fixed then
        Wscript.Echo objDiskDrive.DriveLetter
    End if
Next

VBScript Data Types

VBScript is a typeless language. This means that variables cannot be restricted to a single data type. VBScript does not allow you to specify in advance that a particular variable can hold only a particular kind of data. Instead, VBScript uses a single kind of variable, known as a variant, which can store any kind of data.

By contrast, a programming language such as C++ is considered strongly typed because you must specify in advance the type of data that can be stored in a variable. Your code will generate an error if you attempt to store any other kind of data in that variable. If you specify that a variable can only contain numeric data, the program will crash if you try to store alphabetic data in that variable.

Variants can make writing scripts easier; you can declare and use variables without considering the type of data being stored in them. However, variants can introduce a new set of problems into a script if you do not understand the process of type coercion.

Working with Type Coercion

Scripting languages appear typeless only to the script writer. Internally, scripting languages must still work with data types. For example, when presented with a simple statement such as c = a + b, the scripting language must derive typed values for both a and b. In other words, it must take those two variants and create typed values such as integer or string. After it has derived the typed values, the scripting language can then perform the operation.

The process of deriving typed values is known as type coercion because the variant value is “coerced” into temporarily storing a new, typed, value. Type coercion is based on specific rules about how and when to coerce variants into different data types, and, for the most part, VBScript handles type conversions without any problem.

However, type coercion can lead to trouble, as shown in the following:

intFirstNumber = InputBox("Please enter the first number:")
intSecondNumber = InputBox("Please enter the second number:")
intTotal = intFirstNumber + intSecondNumber
Wscript.Echo intTotal

If you run the script and enter 4 as the first number and 2 as the second number, the computer echoes back “42” as the answer to 4 + 2, rather than the expected answer of 6.

This is because the addition operator is valid both for numbers and for strings. VBScript is given two values (4 and 2), with no way to know which data type these values represent. With no other information available, VBScript uses type coercion to convert the two variables to string data.

By contrast, the following script snippet returns the correct value (2) if you enter the numbers 4 and 2. This is because the division operator works only with numbers, so VBScript uses type coercion to correctly convert the two values to numeric data.

intFirstNumber = InputBox("Please enter the first number:")
intSecondNumber = InputBox("Please enter the second number:")
intTotal = intFirstNumber / intSecondNumber
Wscript.Echo intTotal

To avoid the problems that can occur with type coercion, explicitly declare data types when performing operations, a process referred to as casting type values. For example, the following script uses the VBScript function CInt (convert to integer) to convert the input variables to integers before adding them:

intFirstNumber = CInt(InputBox("Please enter the first number:"))
intSecondNumber = CInt(InputBox("Please enter the second number:"))
intTotal = intFirstNumber + intSecondNumber
Wscript.Echo intTotal

Table 2.6 lists the type conversion functions used in VBScript.

Table 2.6. VBScript Type Conversion Functions

Function

Description

CBool

Converts any nonzero value to True and 0 (zero) to False.

CByte

Converts an expression to a Byte value.

CCur

Converts an expression to a Currency value.

CDate

Converts an expression to a Date value.

CDbl

Converts an expression to a Double value.

CInt

Converts an expression to an Integer value. If the fractional part of the expression is .5, CInt will round the value to the nearest even number. For example, 3.5 will be rounded to 4, and 6.5 will be rounded to 6.

CLng

Converts an expression to a Long value.

CSng

Converts an expression to a Single value.

CStr

Converts an expression to a String value.

Working with Empty Variables and Null Variables

Understanding the difference between an Empty variable and a Null variable can make an equally big difference in the success or failure of your scripts. An Empty variable is a variable that has not been initialized. For example, in a statement such as Dim curBonus, the variable is considered “empty” until you set curBonus equal to a value. An Empty variable is coerced into having the value 0 when used as a number and coerced into having the value “” (zero-length text string) when used as a string.

By contrast, a Null variable is a variable that has not had a valid value assigned to it. Typically, Null variables are derived from database operations. Suppose you query a database, retrieve the current bonus field for a particular employee, and assign that value to the variable curBonus. If no bonus has been assigned, the value of curBonus will be Null. Note that while curBonus could be 0, you do not know for sure if it is 0. You cannot assume that the value is 0; in this case, the user might actually have a $5,000 bonus, but this value has not been entered into the database yet. This is why VBScript draws a distinction between Empty variables and Null variables.

The difference between an Empty variable and a Null variable is apparent when doing mathematical operations. For example, in the following script snippet, the value of curBonus is set to Empty, and then curBonus is added to curBaseSalary (50,000). The net result: 50,000 + 0 = 50,000.

curBonus = Empty
curBaseSalary = 50000
curTotalCompensation = curBaseSalary + curBonus
Wscript.Echo TotalCompensation

In the revised version of this script, the same operation is performed, this time setting curBonus to Null. When you run the calculation, however, you do not get 50,000 (that is, 50,000 + 0). Instead, the answer is null. Anytime a Null variable is used in a mathematical equation, the end result is always null. This is because you do not know the actual value of a Null variable. You cannot substitute 0 because you do not know whether the value of that variable really is 0. Because you do not know the value, you do not know what the result would be if you used that value in a calculation. Thus, the answer has to be Null. (Think of Null as meaning, “I do not know.”)

curBonus = Null
curBaseSalary = 50000
curTotalCompensation = curBaseSalary + curBonus
Wscript.Echo TotalCompensation

Null values can be a problem when working with databases and when using ADSI to retrieve data from Active Directory. Fortunately, you can use the IsNull method to ensure that a variable is not Null. For example, the following script checks the value of the variable curBonus. If curBonus is Null, the value 0 is explicitly assigned to the variable. This allows the variable to be used in the calculation. Alternatively, you can choose not to perform the calculation but instead echo a message like, “No bonus information available for this employee.”

curBonus = Null
curBaseSalary = 50000
If IsNull(curBonus) Then
    CurBonus = 0
End If
curTotalCompensation = curBaseSalary + curBonus
Wscript.Echo curTotalCompensation

Working with Dates and Times

Dates and times play important roles in system administration. For example, when working with event logs you will often want to extract a set of records based on a specific time period (all the events that occurred yesterday, all the events that occurred last week, all the events that occurred last month). To determine such things as service reliability, you need to take the date and time a service started and the date and time the service stopped, and then use the difference to calculate the service uptime. To ensure that your scripts are running as scheduled, you need to log the date and time that a script ran and the date and time that it finished. It is difficult to underestimate the importance of dates and times in managing your computing infrastructure.

VBScript provides several different ways for you to retrieve date and time values. It also provides several methods for performing date arithmetic — that is, calculating such things as the amount of time that elapsed between two events or determining whether the date 180 days from today falls on a weekend.

Note

Note

The date and time formats used in VBScript are very different from the date and time formats used in WMI. For information about the WMI date and time formats and how they can be converted to the VBScript format, see “WMI Scripting Primer” in this book.

Retrieving Current Date and Time Values

The ability to determine the current date or the current time is a useful task in system administration scripting. Many scripts, including those that write to log files or those that write to databases, need to include the current date or time as part of the data written. Scripts that need to take action on specific days or at specific times need to be able to determine the current date and time. Scripts designed to work with a range of dates (for example, retrieve all the error events written to the event log in the past two weeks) need to be able to identify the current date or time to use as a starting point.

VBScript includes three functions that can be used to identify the current date, the current time or both:

Now — retrieves both the date and the time.

Date — retrieves the current date.

Time — returns the current time.

For example, the following script retrieves date and time information by using Now, Date, and Time and then displays the results of all the functions in a single message box:

DateInfo = DateInfo & Now & VbCrLf
DateInfo = DateInfo & Date & VbCrLf
DateInfo = DateInfo & Time & VbCrLf
Wscript.Echo DateInfo

When the preceding script runs, a message box similar to the one shown in Figure 2.12 appears.

Date and Time Information by Using Now, Date, and Time Functions

Figure 2.12. Date and Time Information by Using Now, Date, and Time Functions

Verifying That a Value Is a Date

In working with dates, it is important to know whether a particular value is actually a date or not. This is especially true when making WMI queries or when working with databases; your script will fail if it attempts to use an invalid date in these situations.

The IsDate function can tell you whether a supplied value is a date. IsDate returns False (0) if the value is not a date and True (–1) if the value is a date. Date values can be passed using either of the following:

Date literals. These are date values enclosed within pound signs (#). This is the recommended way of using dates in scripts because it eliminates the possibility that VBScript will misinterpret the value as being something other than a date. A date literal might look like this:

#9/3/2002#

Date and time formats recognized by your system settings. For example, if your system is set for English (United States), these values are recognized as valid dates:

6/6/2002

6,1,2002

6-1-2002

However, this value is not recognized as a valid date:

6.1.2002

If you change your settings to German (Austria), all four values are recognized as dates.

Note

Note

To check valid date formats for your computer, open the Regional and Language Options control panel, click Customize, and then click Date.

The following script creates an array of values and then enumerates each item in the array. The script then uses IsDate to determine whether the item represents a valid date and echoes the value and a message indicating that this is actually a date.

DateArray = Array("6/1/2002", "June 1, 2002", "6", "6/1")
For Each dtmDate in DateArray
    If IsDate(dtmDate) = 0 Then
        Wscript.Echo dtmDate & " is not a valid date."
    Else
        Wscript.Echo dtmDate & " is a valid date."
    End If
Next

When the preceding script runs under CScript, the following information appears in the command window:

6/1/2002 is a valid date.
June 1, 2002 is a valid date.
6 is not a valid date.
6/1 is a valid date.

Note

Note

Why is 6/1 a valid date? When using the IsDate function, VBScript tries to construct a plausible date from the value given to it. When it sees a statement that can be interpreted as Month/Day, it automatically appends the current year, resulting in Month/Day/Year. In the preceding script, which was run in the year 2002, that means a value of 6/1/2002, which is a valid date.

Retrieving Specific Portions of a Date and Time Value

Often times you are interested in only a portion of a date or time. For example, you might have a backup script that performs a full backup on Sundays and a partial backup every other day of the week. Likewise, you might have a script that retrieves event log events every day, but on the 15th and 30th of each month also clears the event log.

VBScript provides two ways to retrieve specific portions of a date or time. The DatePart function is a generic function that can retrieve any portion of a date or time value. In addition, VBScript also includes several functions, such as Day, Month, and Year, that allow you to retrieve a specific part of a date or time.

The DatePart function can be used to return a specific part of a date or time value. This function requires two items: the date to be parsed and one of the parameters shown in Table 2.7.

Table 2.7. DatePart Parameters

Parameter

Description

yyyy

Year. Returns the year from the date-time value.

q

Quarter. Returns the quarter — 1, 2, 3, or 4 — from the date-time value.

m

Month. Returns the month of the year using the following values:

  • 1 – January

  • 2 – February

  • 3 – March

  • 4 – April

  • 5 – May

  • 6 – June

  • 7 – July

  • 8 – August

  • 9 – September

  • 10 – October

  • 11 – November

  • 12 – December

y

Day of Year. Returns the day number, with January 1 being 1 and December 31 being 365 (366 during leap years). For example, February 1 returns 32 because it is the 32nd day of the year.

d

Day. Returns the day of the month. For example, both April 17 and August 17 return 17.

w

Weekday. Returns the day of the week using the following values:

  • 1 – Sunday

  • 2 – Monday

  • 3 – Tuesday

  • 4 – Wednesday

  • 5 – Thursday

  • 6 – Friday

  • 7 – Saturday

You can specify that the day of the week start on a day other than Sunday. For more information, see “Retrieving Specific Portions of a Date and Time Value” later in this chapter.

ww

Week of Year. Returns the week number, with the week of January 1 typically being week 1 and the week of December 31 being week 52. However, you have several options for specifying which is the first week in the year. These options, in turn, affect the other week numbers. For details, see “Retrieving Specific Portions of a Date and Time Value” later in this chapter.

h

Hour. Returns the hour from the date-time value using 24-hour format. For example, 2:00 P.M. is returned as 14, and 6:00 P.M. is returned as 18. Times between midnight and 1:00 A.M. are returned as 0. Midnight (12:00 A.M.) is also returned as 0.

n

Minute. Returns the minutes from the date-time value.

s

Second. Returns the seconds from the date-time value.

To use the DatePart function, you can create a variable and assign it the DatePart value. For example, the following line of code extracts the year from the current date and assigns it to the variable CurrentYear:

CurrentYear = DatePart("yyyy", Date)

In the preceding example, the two parameters are:

“yyyy” —Indicates that the year should be returned from the specified date. This parameter must be enclosed in quotation marks.

Date —Indicates that the date to be parsed should be the current date. You can also enclose a valid date within quotation marks (for example, “6/1/2002”) or use a variable that has been assigned a date. For example, these two lines of code return the value 1977:

DateToCheck = #8/15/1977#
CurrentYear = DatePart("yyyy" , DateToCheck)

Note

Note

When assigning a date to a variable, enclose the date using date literals (#). This ensures that VBScript views the value as a date, and not as a number or string. Alternatively, you can use the CDate function.

The script shown in Listing 2.15 parses the current date and time, and then displays each date time component.

Example 2.15. Using the DatePart Function

 1 Wscript.Echo Now
 2 Wscript.Echo "Year: " & DatePart("yyyy" , Now)
 3 Wscript.Echo "Quarter: " & DatePart("q", Now)
 4 Wscript.Echo "Month: " & DatePart("m" , Now)
 5 Wscript.Echo "Day of Year: " & DatePart("y" , Now)
 6 Wscript.Echo "Day: " & DatePart("d" , Now)
 7 Wscript.Echo "Weekday: " & DatePart("w" , Now)
 8 Wscript.Echo "Week of Year: " & DatePart("ww" , Now)
 9 Wscript.Echo "Hour: " & DatePart("h", Now)
10 Wscript.Echo "Minute: " & DatePart("n" , Now)
11 Wscript.Echo "Seconds: " & DatePart("s" , Now)

When the preceding script was run on January 17, 2002, at 11:40:27 A.M., the following output was returned:

1/17/2002 11:40:27 AM
Year: 2002
Quarter: 1
Month: 1
Day of Year: 17
Day: 17
Weekday: 5
Week of Year: 3
Hour: 11
Minute: 40
Seconds: 27

No error will be generated if you pass incomplete data to DatePart, but you might not get the expected results. For example, this line of code returns the value 1899:

Wscript.Echo DatePart("yyyy", "8:00 AM")

This line of code returns 0:

Wscript.Echo DatePart("h", "12/1/2002")

Configuring DatePart Options

By default, VBScript considers the first week of the year to be the week in which January 1 occurs. However, the DatePart function includes an optional parameter that can be used to set the first week of the year to one of the values shown in Table 2.8.

Table 2.8. Parameters for Setting the First Week of the Year

Constant

Value

Description

vbUseSystem

0

Uses the National Language Support API to determine the first full week based on the regional and language settings.

vbFirstJan1

1

Sets the first week as the week in which January 1 occurs.

vbFirstFourDays

2

Sets the first week as the first week to have at least four days in it.

VbFirstFullWeek

3

Sets the first week as the first week that begins on a Sunday.

To use this parameter, call the DatePart function followed by the “ww” parameter, the date for which the week number is being determined, and the appropriate constant. The following script shows how the different constants can affect the week number assigned to a particular date.

TestDate = "1/6/2003"
Wscript.Echo ProjectedDate
Wscript.Echo "Week of Year: " & DatePart("ww" , TestDate)
Wscript.Echo "Week of Year: " & DatePart("ww" , TestDate, vbFirstJan1)
Wscript.Echo "Week of Year: " & DatePart("ww" , TestDate, vbFirstFourDays)
Wscript.Echo "Week of Year: " & DatePart("ww" , TestDate, vbFirstFullWeek)

When the preceding script runs under CScript, the following output appears in the command window:

1/6/2003
Week of Year: 2
Week of Year: 2
Week of Year: 2
Week of Year: 1

As shown in Figure 2.13, January 6, 2003, does not fall in the same week as January 1, nor does it fall in the first week to have four days. However, it does fall during the first week to start with a Sunday.

January 6, 2003

Figure 2.13. January 6, 2003

Other Functions for Retrieving Portions of a Date

In addition to the DatePart function, the functions shown in Table 2.9 can also retrieve portions of a date-time value. DatePart can retrieve values such as day of the year and week of the year that cannot be retrieved any other way. However, DatePart does have parameters, such as “n” for minute, that are difficult to remember and can make your code harder to read and maintain than the stand-alone equivalent. For example, although these two lines of code return the same value (the minute), the second line leaves little doubt as to its purpose:

Wscript.Echo DatePart("n", Now)
Wscript.Echo Minute(Now)

Table 2.9. Functions for Retrieving Portions of a Date

Function

Description

Day

Returns the day for the specified date-time value.

Hour

Returns the hour for the specified date-time value.

Minute

Returns the minute for the specified date-time value.

Month

Returns the month for the specified date-time value.

Second

Returns the second for the specified date-time value.

Weekday

Returns the day of the week for the specified date-time value. Return values are:

  • 1 – Sunday

  • 2 – Monday

  • 3 – Tuesday

  • 4 – Wednesday

  • 5 – Thursday

  • 6 – Friday

  • 7 – Saturday

Year

Returns the year for the specified date-time value.

To use one of the stand-alone functions, simply call the function, passing the appropriate date-time value as the sole parameter. For example, the following code passes the current date and time to each stand-alone date function:

CurrentDate = Now
Wscript.Echo "Year: " & VbTab & VbTab & Year(CurrentDate)
Wscript.Echo "Month: " & VbTab & VbTab & Month(CurrentDate)
Wscript.Echo "Day: " & VbTab & VbTab & Day(CurrentDate)
Wscript.Echo "Weekday: " & VbTab & Weekday(CurrentDate)
Wscript.Echo "Hour: " & VbTab & VbTab & Hour(CurrentDate)
Wscript.Echo "Minute: " & VbTab & Minute(CurrentDate)
Wscript.Echo "Second: " & VbTab & Second(CurrentDate)

When the preceding script is run using CScript, the following output appears in the command window:

Year:            2002
Month:           1
Day:             31
Weekday:         5
Hour:            14
Minute:          20
Second:          41

Date Arithmetic

Individual dates are an important consideration is system administration. Equally important, however, are ranges of dates and intervals between dates. In system administration, you will often need to make projections based on a specified date and a specified interval. For example, you might need to know which date is 180 days from today. Alternatively, you might have two dates or times and need to calculate the difference between them. If a service started on February 1, 2002, at 6:00 A.M. and then shut down on July 17, 2002, at 3:30 P.M., you might want to calculate how long the service ran before stopping.

VBScript provides two functions, DateDiff and DateAdd, which enable you to perform date arithmetic.

Determining the Interval Between Two Dates or Times

Calculating the amount of time that elapsed between two events is a common system administration task. For example, using WMI, you can determine the date and time that a computer started. By subtracting that value from the current date and time, you can calculate the system uptime, the amount of time the computer has been running since its last reboot.

The DateDiff function is used to calculate time intervals such as this. DateDiff requires three parameters:

The date or time interval (for example, the number of days between two events or the number of hours between two events). DateDiff accepts the same date parameters as DatePart. These parameters are shown in Table 2.9.

The date of the first event.

The date of the second event.

For example, the script shown in Listing 2.16 calculates the number of days between the current date and July 1, 2002. To calculate a different interval, simply substitute the appropriate parameter. For example, to calculate the number of weeks between the two dates, replace the “d” parameter with the “w” parameter.

Example 2.16. Determining Time Intervals

1 Wscript.Echo "Date: " & Date
2 Wscript.Echo "Days Until July 1: " & DateDiff("d", Date, "7/1/2002")

When the script is run using Cscript, the following output is returned:

Date: 1/8/2002
Days Until July 1: 174

Note

Note

Depending on the dates you use, you might get a negative number. For example, the difference between January 18, 2002, and July 1, 2002, is 164 days. If you reverse the order of the two dates, however, DateDiff will return the value -164. If you do not want negative values, you can use Abs, the VBScript absolute value function: Abs(DateDiff(“d”, “7/1/2002”, “1/18/2002”). The absolute value function always returns a positive (unsigned) value.

With DateDiff, you might get different results depending on whether you use the “w” or the “ww” parameter. The “w” parameter calculates the number of weeks between the two dates, based on the day of the week when the first date occurs. For example, January 18, 2002, occurred on a Friday. With the “w” parameter, DateDiff calculates the number of Fridays between the two dates, not counting the initial Friday. For the interval January 18, 2002, to July 1, 2002, the “w” parameter counts 23 weeks.

The “ww” parameter, however, counts the number of Sundays between the two dates, again not counting the initial date. In this case, there are 24 Sundays between the two dates, meaning that the “w” and “ww” parameters return different values.

Note

Note

You might also get unexpected results when calculating the number of years between events. For example, the time between December 31, 2001, and January 1, 2002, is 1 day. However, because it covers two different years, DateDiff will tell you that there is 1 year between the two days. Because of that, you might want to return the number of days between two events and then divide by 365 to determine the number of years between the two.

DateDiff is also a useful tool for monitoring how long it takes for your scripts to run. To time the running of a script, set a variable to Now before the script runs any code. This variable will thus hold the time that the script started. At the end of the script, subtract this value from Now, and echo the results.

For example, the following script tracks how long it takes to retrieve and display the free disk space for all the disk drives on the local computer.

Start = Now
Set objWMIService = GetObject("winmgmts://")
Set colLogicalDisk = objWMIService.InstancesOf("Win32_LogicalDisk")
For Each objLogicalDisk In colLogicalDisk
    Wscript.Echo objLogicalDisk.DeviceID & " " & objLogicalDisk.FreeSpace
Next
Wscript.Echo DateDiff("s", Start, Now)

Projecting Dates Based on a Specified Time Interval

DateDiff can tell you the interval between two dates; however, there will be times when you know the interval but need to calculate either the starting date or the end date. For example, suppose you want to retrieve all the event log events that were recorded in the past 45 days. You know the interval: 45 days. You also know the end date: today. What you do not know is the beginning date.

The DateAdd function projects a date forward or backward from a specified starting point; in a sense, it is almost the reverse of the DateDiff function. With DateAdd, you specify the following three parameters:

The time interval (again using the values shown in Table 2.9). For example, to calculate 180 days from today, use the “d” parameter.

The time value (for example, 180 to indicate 180 days). To work backward from the starting date (for example, to determine the date 45 days ago), use a negative number. To project forward in time, use a positive number.

The starting date.

For example, the script shown in Listing 2.17 calculates the date that falls 180 days from the current date.

Example 2.17. Projecting Dates

1 Wscript.Echo "Date: " & Date
2 Wscript.Echo "180 Days From Today: " & DateAdd("d", 180, Date)

When the script shown in Listing 2.17 was run on January 8, 2002, the following output was returned:

Date: 1/8/2002
180 Days From Today: 7/7/2002

Formatting Date and Time Values

By default, VBScript displays the output of the Now, Date, and Time functions using the same date-time settings used by the operating system. For computers running the American English version of Windows, output will look similar to that shown in Table 2.10.

Table 2.10. Default Date-Time Output

Function

Sample Output

Now

6/30/02 9:01:47 PM

Date

6/30/02

Time

9:01:47 PM

However, you are not limited to these default formats. Instead, VBScript includes several constants (shown in Table 2.11) that can be used along with the FormatDateTime function to display date-time values in alternate ways.

Table 2.11. FormatDateTime Settings

Constant

Value

vbGeneralDate

0

vbLongDate

1

vbShortDate

2

vbLongTime

3

vbShortTime

4

The following script displays the current date and time using each of the date-formatting constants:

CurrentDate = Now
Wscript.Echo "General date: " & FormatDateTime(CurrentDate, vbGeneralDate)
Wscript.Echo "Long date: " & FormatDateTime(CurrentDate, vbLongDate)
Wscript.Echo "Short date: " & FormatDateTime(CurrentDate, vbShortDate)
Wscript.Echo "Long time: " & FormatDateTime(CurrentDate, vbLongTime)
Wscript.Echo "Short time: " & FormatDateTime(CurrentDate, vbShortTime)

When the preceding script runs under CScript, the following output appears in the command window:

General date: 1/31/2002 3:29:37 PM
Long date: Thursday, January 31, 2002
Short date: 1/31/2002
Long time: 3:29:37 PM
Short time: 15:29

Although these are the only date-time formats predefined in VBScript, you can use the various date and time functions to create your own formats. For example, the following lines of code display the date using just the month and year (8/2002):

Wscript.Echo Month(Now) & "/" & Year(Now)

Formatting Days of the Week and Months of the Year

The WeekdayDay and Month functions allow you to retrieve just the day of the week or just the month from a specified date. When you use these functions, the data is returned as an integer. These values are summarized in Table 2.12.

Table 2.12. Weekday and Month Return Values

Value

Day of the Week

Month

1

Sunday

January

2

Monday

February

3

Tuesday

March

4

Wednesday

April

5

Thursday

May

6

Friday

June

7

Saturday

July

8

August

9

September

10

October

11

November

12

December

Both WeekdayName and MonthName require you to first use the Day or Month function to retrieve the integer value for the day or the month. You cannot use either WeekdayName or MonthName directly with a date. For example, this code will fail because the function cannot determine the integer value to use for the month:

Wscript.Echo MonthName("9/19/2002")

To determine MonthName, you must first use the Month function:

MonthValue = Month("9/19/2002")
Wscript.Echo MonthName(MonthValue)

Alternatively, you can nest the two functions in a single line of code:

Wscript.Echo MonthName(Month("9/19/2002"))

The WeekdayName and MonthName functions are described in more detail in Table 2.13.

Table 2.13. Date Formatting Functions

Function

Description

WeekdayName

Returns the actual name of the day rather than the number of the day. (For example, WeekdayName returns the value Tuesday rather than the value 3.) WeekdayName requires only one parameter, the day of the date being evaluated. This means you must nest the WeekdayName and Day functions like this:

Wscript.Echo WeekDayName(Day("9/1/2002"))

The preceding code returns the string “Sunday” because September 1, 2002, occurred on a Sunday. WeekdayName also accepts an optional parameter to abbreviate the name of the day. By setting this parameter to True, WeekdayName will return a three-letter abbreviation for the day. For example, this command returns the value “Sun”:

Wscript.Echo WeekDayName(Day("9/1/2002") True)

MonthName

Returns the actual name of the month rather than the number of the month. (For example, MonthName returns the value September rather than the value 9.) MonthName requires only one parameter, the month of the date being evaluated. This means you must nest the MonthName and Month functions like this:

Wscript.Echo MonthName(Month("9/1/2002"))

The preceding code returns the string “September”. MonthName also accepts an optional parameter to abbreviate the name of the month. When this parameter is set to True, MonthName will return a three-letter abbreviation for the month. For example, this command returns the value “Sep”:

Wscript.Echo MonthName(Month("9/1/2002") , True)

Working with Strings

Many system administration scripts are designed to return information about managed items, including such things as the services running on a remote server, the hardware installed on a client workstation, or the events recorded in the event logs on a domain controller. Much of the information returned from these managed objects comes back in the form of string data, data composed of any combination of alphabetic and numeric values. For example, if you query an event log for information about an event description, you might get back a string similar to this:

Security policy in the Group policy objects has been applied successfully.

In other words, there is nothing particularly special about strings; strings are just bits of text, the same kind of text you create when typing in a text editor.

Although strings are a simple enough concept, working with string data can be tricky, especially when 1) that data is returned from a COM object and 2) you need to store that data in a database or display it on the screen. This is because you have no control over how the returned data is packaged; it might be oddly formatted (for example, all uppercase letters), or it might be too long or too short to fit the allocated space. (For example, your database field might be sized to accept no more than 20 characters, but the returned string might contain 30 characters.)

Because of this, it is important for you to be able to manipulate string data; you might want to shorten (or, in some cases, lengthen) the size of a string, you might want to reformat a string, and sometimes you might even want to search within a string and extract only a specified portion of the text. To help you carry out these tasks, VBScript provides a number of functions for manipulating strings.

Manipulating Strings and String Lengths

Many times you want to work with only a portion of a string value. For example, Windows Script Host (WSH) has a property, FullName, which tells you whether a script is running under CScript or WScript. This can be useful information because you are likely to have certain scripts that should be, or in some cases can only be, run under CScript. However, the FullName property returns not just the name of the script host but the full path to the script host executable file. Typically these paths are:

C:WindowsCscript.exe
C:WindowsWscript.exe.

Of course, most of the time you are not interested in the full path but only the name of the script host; after all, the full path will vary depending on the drive and the directory where Windows is installed. Because of this, you need a method for retrieving only the last eight characters (cscript.exe or wscript.exe) of the path string.

At other times, the length of a string is very important. This is particularly true when it comes to formatting output either to display in a command window or to save to a text file. For example, the following script retrieves a collection of files stored in C:Windows and displays the file name, file size, and last access time for each one:

Set FSO = CreateObject("Scripting.FileSystemObject")
Set Folder = FSO.GetFolder("C: Windows")
Set FileList = Folder.Files
For Each File in FileList
    Wscript.Echo File.Name & VbTab & File.Size & File.DateLastModified
Next

When the preceding script runs, output similar to the following appears. As you can see, it is very difficult to make immediate sense of this output.

PCL5EMS.X12   46618/3/00 9:07:50 AM
SchedLog.Txt   1778412/17/00 7:51:40 PM
Straw Mat.bmp   5905/11/98 8:01:00 PM
Bubbles.bmp   21185/11/98 8:01:00 PM
Carved Stone.bmp   5825/11/98 8:01:00 PM
HKLM   3711/12/99 8:27:20 AM
LT Win Modem.log   207512/3/01 9:30:16 PM

When placed in tabular format, with well-defined columns, the data is much easier to read and to analyze:

PCL5EMS.X12                                  4661    8/3/00 9:07:50 AM
SchedLog.Txt                                 17784 12/17/00 7:51:40 PM
Straw Mat.bmp                                590    5/11/98 8:01:00 PM
Bubbles.bmp                                  2118   5/11/98 8:01:00 PM
Carved Stone.bmp                             582    5/11/98 8:01:00 PM
HKLM                                         371    1/12/99 8:27:20 AM
LT Win Modem.log                             2075   12/3/01 9:30:16 PM

To create tabular output such as this, you need to be able to take string values and manipulate them in various ways. For example, in the preceding output, the file name begins in the first character position, and the file size begins in the 51st character position (digits have been added to make it easier to identify character positions):

123456789012345678901234567890123456789012345678901
PCL5EMS.X12                                       4661   8/3/00 9:07:50 AM

To create this output, you need to:

  1. Calculate the length of the file name. (For example, PCL5EMS.X12 is 11 characters long.)

  2. Subtract the length of the file name (11 characters) from the total space allocated for the file name (50 characters).

  3. Append enough blank spaces (50 minus 11, or 39 blank spaces) to ensure that the length of the file name equals the total space allocated.

This works very well for short file names. But what if you had a file name of 75 characters? In that case, you would need to truncate the string, taking only the first 50 characters and leaving off the rest.

Fortunately, VBScript includes a number of functions that allow you to manipulate strings. For example, you can calculate the length of a string and add blank spaces to pad the length, or use functions that return only a specified number of characters, beginning either from the start of the string and working forward or from the end of the string and working back (or even from some point in the middle and working in either direction).

Some of the more commonly used functions for manipulating strings are shown in Table 2.14.

Table 2.14. String Manipulation Functions

Function

Description

Len

Returns the number of characters in a string. This is particularly useful when formatting data for on-screen display. Suppose you allow 20 characters for a particular column. To ensure that columns line up correctly, you need to determine the number of characters in the string and then add or subtract characters as needed until the string is 20 characters long.

The following two lines of code returns the value 22 because 22 characters are in the string This is a test string.

TestString = "This is a test string."
Wscript.Echo Len(TestString)

Left

Returns the specified number of characters from the string, starting with the first character and working forward toward the end of the string (from left to right). You must supply two parameters: the string and the number of characters to return.

The following two lines of code return the string Thi because these are the first three characters in the string This is a test string.

TestString = "This is a test string."
Wscript.Echo Left(TestString, 3)

Right

Returns the specified number of characters from the string, starting with the last character and working backward toward the beginning of the string (from right to left). You must supply two parameters: the string and the number of characters to return.

The following two lines of code return the string ng. because these are the last three characters in the string This is a test string.

TestString = "This is a test string."
Wscript.Echo Right(TestString, 3)

Mid

Returns the specified number of characters from the string, starting with a designated character position and working toward the end of the string (from left to right). You must supply three parameters: the string, the starting character position (for example, 5 to start counting from the fifth character in the string), and the number of characters to return.

The following two lines of code return the string is_ (with the underscore representing a blank space) because these are the characters that hold positions 6, 7, and 8 in the string This is a test string.

TestString = "This is a test string."
Wscript.Echo Mid(TestString, 6, 3)

Space

Inserts the specified number of blank spaces into a string. For example, the following line of code inserts 10 blank spaces between “This is a” and “test string.” The end result is the string “This is a test string.”

Wscript.Echo "This is a" & Space(10) _
    & "test string."

LTrim

Removes any blank spaces that appear at the beginning of a string. (These are typically referred to as leading spaces.) For example, the following code transforms the string “ This is a test string. ” into “This is a test string. ”

TestString = "         This is a test string.       "
Wscript.Echo LTrim(TestString)

RTrim

Removes any blank spaces that appear at the end of a string. (These are typically referred to as trailing spaces.) For example, the following code transforms the string “ This is a test string. ” into “This is a test string.”

TestString = "         This is a test string.       "
Wscript.Echo RTrim(TestString)

Trim

Removes both the leading and trailing spaces from a string. For example, the following code transforms the string “ This is a test string. ” into “This is a test string.”

TestString = "        This is a test string.       "
Wscript.Echo Trim(TestString)

Creating Tabular Output

The string manipulation functions are particularly useful in creating tabular output, output in which items are displayed in aligned columns. An example of this is shown in the following script, which uses the Len and Space functions to create a table showing two services and their current state. The script in Listing 2.18 does the following:

  1. Sets the value of four variables, two representing the names of services and two representing service states.

  2. For the first service variable, uses the Len function to determine the number of characters in the string and saves this value to the variable NameLength. For example, Alerter has seven characters in its name, so the value 7 is saved to NameLength.

  3. Subtracts the value of NameLength from 20, the number of spaces allocated for displaying the service name, and stores the difference in the variable SpacesToAdd. For the Alerter service, this value would be 13: 20 – 7.

  4. Sets the value of the variable DisplayName to the name of the service plus as many blank spaces as needed to make the name 20 characters long. For example, the Alerter service requires 13 blank spaces (the value stored in SpacesToAdd) to make the name 20 characters long. These blank spaces are added using the Space function.

    The DisplayName for the Alerter service looks like this (the hyphens indicate blank spaces):

    Alerter-------------
    
  5. Echoes the DisplayName and the service state.

  6. Repeats the process for the next two variables.

Example 2.18. Creating Tabular Output

 1 Service1 = "Alerter"
 2 State1 = "Running"
 3 Service2 = "DHCP Client"
 4 State2 = "Stopped"
 5
 6 NameLength = Len(Service1)
 7 SpacesToAdd = 20 - NameLength
 8 DisplayName = Service1 & Space(SpacesToAdd)
 9 Wscript.Echo DisplayName & State1
10
11 Display = " "
12
13 NameLength = Len(Service2)
14 SpacesToAdd = 20 - NameLength
15 DisplayName = Service2 & Space(SpacesToAdd)
16 Wscript.Echo DisplayName & State2

When this script runs under CScript, the following output appears in the command window:

Alerter          Running
DHCP Client      Stopped

Formatting Text for Message Boxes

If you prefer using message boxes to display data, you will discover that it is much harder, if not impossible, to display data in aligned columns. This is because of a difference in the fonts used in a command window and the fonts used in a message box. The command window uses a nonproportional font, which means that all characters (including spaces) are the same width. As shown in Figure 2.14, the letters i, j, and l take up the same amount of space as the letters m, v, and w in a nonproportional font.

Letters Displayed in Nonproportional Font

Figure 2.14. Letters Displayed in Nonproportional Font

By contrast, message boxes typically use a proportional font, which means that characters vary in width. As shown in Figure 2.15, the letters i, j, and l take up far less space than the letters m, v, and w in a proportional font.

Letters Displayed in Proportional Font

Figure 2.15. Letters Displayed in Proportional Font

Because of the differences in character widths, you cannot specify that a column should begin at, say, the 20th character space, and assume that all the column items will line up.

Searching for Text in a String

As noted previously, you do not always have control over the text returned to you when running a script. For example, suppose your script opens log files on a computer and checks to see whether a particular action succeeded or failed. In most cases, the log file will contain far more text than simply the word Succeeded or Failed. Instead, the log might look like this:

MSI: False - MSP: True - CMW: False
MSISW: REINSTALL=all REINSTALLMODE=vomus /QB /L*V+ C:WINNTDEBUG
   officexp_sp1.log
MSPSW: REBOOT=ReallySuppress /QB- /L*V+ C:WINDOWSDEBUGofficexp_sp1.log
InstallFile Result: [0] and Success: True
Client Maint Wiz: PROPLUS detected

To determine the success or failure of the activity, your script will need to read in all the text and programmatically search for the relevant piece of information (in this example, the phrase “Success: True”).

The InStr function provides a way to search for the presence of a string within another string. In practical terms, this allows you to create scripts that perform tasks such as reading through a log file and reporting back only if a line contains a specific error code. For example, imagine a plaintext log file that contains hundreds of lines like this:

6/1/2002   File a.doc successfully copied
6/1/2002   File b.doc successfully copied
6/1/2002   Error: File c.doc could not be copied
6/1/2002   File d.doc successfully copied

Rather than manually read through this log looking for errors, you can create a script that reads each line in the log file, echoing back any lines that contain the word Error. This provides a quick and easy way for you to extract only the operations that failed and thus need your attention.

Note

Note

The InStr function is designed to carry out simple string manipulation. If you need to carry out more complex types of operation, you need to use the VBScript Regular Expressions object. This use of this object is beyond the scope of this book; for more information, see the VBScript link on the Web Resources page at http://www.microsoft.com/windows/reskits/webresources.

InStr works by reporting back the character position where the substring is first found. For example, suppose you are looking for the letters ment in the word developmental. InStr returns the value 8 because the letters ment are found beginning in the eighth character position, as shown in Figure 2.16.

Character Positions in the Word Developmental

Figure 2.16. Character Positions in the Word Developmental

By contrast, if you searched for the letters mant, InStr would return 0, indicating that the substring could not be found.

InStr requires two parameters: the string being searched and the string being searched for (the substring). For example, the following line of code searches for the letters ment in the word developmental:

Wscript.Echo InStr("developmental", "ment")

For longer strings, you can set variables equal to the string values and then use those variables as InStr parameters. For example, the following script searches for the words test and strong in the sentence, “This is a test string being searched for two different words.”

TestString = "This is a test string being searched for two different words."
PresentString = "test"
AbsentString = "strong"
Wscript.Echo InStr(TestString, PresentString)
Wscript.Echo InStr(TestString, AbsentString)

When the preceding script runs, the values 11 and 0 will be returned. The value 11 indicates that the word test can be found beginning with the eleventh character. The value 0 indicates that the word strong could not be found anywhere within the string being searched.

Modifying String Case

Font control is not a strong point of VBScript. Regardless of whether you are displaying output in the command window or in a message box, you have no ability to control such font characteristics as style, size, and color. In fact, about the only thing you can do with fonts is modify the case; that is, you can configure a character (or set of characters) to be either uppercase or lowercase.

Modifying string case can be useful in displaying information. By changing case as needed, you can either ensure a consistent look to a data display (for example, all items shown in lowercase letters) or, alternatively, draw attention to specific items. For example, in this hypothetical output it is easy to see which server requires immediate attention:

Server         Status
atl-dc-01      Healthy
atl-dc-02      Healthy
ATL-DC-03      ERROR
atl-dc-04      Healthy

Modifying the case is also useful for data entry. For example, to ensure that users enter states using an all-uppercase format (WA, OR, CA), you can simply take the value they enter, regardless of case, and convert it to all uppercase letters.

The two functions that allow you to modify string case are described in Table 2.15.

Table 2.15. Functions for Modifying String Case

Function

Description

LCase

Converts all the alphabetic characters in a string to their lowercase equivalents.

The following lines of code convert the string Arlene Huff to the all-lowercase string arlene huff.

UserName = "Arlene Huff"
Wscript.Echo LCase(UserName)

UCase

Converts all the alphabetic characters in a string to their uppercase equivalents.

The following lines of code convert the string Arlene Huff to the all-uppercase string ARLENE HUFF.

UserName = "Arlene Huff"
Wscript.Echo UCase(UserName)

Working with Numbers

Much of system administration revolves around numbers: How much memory is available on this computer? How many services are paused on that computer? How many failed logons have been recorded in the event log? How much processor time is being used on our domain controllers?

This type of numeric information can easily be returned using scripts; for example, you can use WMI to answer all of the preceding questions. As explained in the first half of this chapter, however, this information does not always come back in a format that is useful for system administrators. Free disk space is reported in bytes; a disk drive with approximately 10 gigabytes of free disk space might show 10,842,824,704 bytes of free disk space. Processor use is reported in 100-nanosecond increments; the maximum length and width of the paper that can be used on a printer is reported in tenths of a millimeter.

Fortunately, VBScript provides a number of functions that allow you to manipulate numbers, including functions for performing mathematical calculations and functions for formatting numeric output.

Arithmetic Precedence

Most of the calculations you carry out as a system administrator are likely to be simple ones, such as dividing bytes by 1,024 to convert the value to kilobytes. On occasion, however, you might need to carry out a calculation that involves more than one operator (for example, a calculation that adds two numbers and then divides that value). Because of that, it is important to understand arithmetic precedence.

VBScript does not treat the arithmetic operators (addition, subtraction, multiplication, and division) equally. Instead, when given an equation, VBScript always performs the math operations in this order:

  1. Division

  2. Multiplication

  3. Addition

  4. Subtraction

Why does that matter to you? Consider the following script, which adds two numbers, multiplies them by a third, subtracts a fourth, and then divides by a fifth:

Wscript.Echo 1 + 2 * 3 - 4 / 5

When this script runs, the value 6.2 is echoed to the screen. Here is how that value is derived.

  1. Because division takes precedence over all the other operations, the first thing VBScript does is divide 4 by 5, yielding .8. To VBScript, the equation now looks like this:

    1 + 2 * 3 - .8
    
  2. Next it multiplies 2 and 3, yielding 6. Now the equation looks like this:

    1 + 6 - .8
    
  3. Because addition is next in the order of precedence, it adds 1 and 6, yielding 7, and resulting in this equation:

    7 - .8
    
  4. Finally it subtracts .8 from 7, giving the final answer 6.2.

Of course, this might not be the answer, or the equation, you expected. Instead, you might have preferred that VBScript:

  1. Add 1 + 2.

  2. Multiply that value by 3.

  3. Subtract 4.

  4. Divide the total by 5.

For this to happen, you need to use parentheses to indicate the preferred order of precedence. Parentheses always take precedence, regardless of the arithmetic operator being used. Because of this, your equation should be rewritten to ensure that the steps are carried out in the proper order. (When multiple parentheses are used, the equation within the innermost parentheses is evaluated first and the equation in the outermost parentheses evaluated last.)

Wscript.Echo (((1 + 2) * 3) - 4) / 5

Formatting Numbers

On its own, VBScript does not always format numbers in easy-to-read fashion. For example, one of the first scripts used in this chapter returned the free disk space on a hard drive. The value returned was 10842824704. This value is accurate but difficult to read; it would be much better to have the free space reported like this: 10,842,824,704.

Likewise, a calculation performed earlier in this chapter returned the value 10340.4458007813. Rarely, if ever, will you require that type of precision when performing system administration tasks. Instead, it would be better to have the value returned as 10,340 or 10,340.45. These numbers not only are easier to read but also require less room to display. This is an important consideration when creating tabular output to be displayed on the screen.

The FormatNumber function allows you to control how numbers are displayed in script output. FormatNumber requires you to specify the number to be formatted, followed by any or all of the parameters described in Table 2.16. For example, this line of code displays the number 37.874 with no decimal places (that is, the value 37 will be displayed):

Wscript.Echo FormatNumber(37.874, 0)

Table 2.16. FormatNumber Parameters

Parameter

Description

Number of digits after the decimal point.

FormatNumber does not simply delete digits. Instead, it rounds the number to the closest value. For example, if you decide to display 11.6 with no decimal places, the value 12 will be displayed. By contrast, 19.2 is displayed as 19.

If you use the value –1, the number of digits after the decimal point will be based on the regional and language options for the computer.

Use leading zero.

Values are:

  • -1 – True. Use the leading 0.

  • 0 – False.

  • -2 – Use the setting configured in the regional and language options for the computer.

Place negative numbers in parentheses.

Values are:

  • -1 – True. Put negative numbers in parentheses.

  • 0 – False. Do not put negative numbers in parentheses.

  • -2 – Use the setting configured in the regional and language options for the computer.

Group digits.

Values are:

  • -1 – True. Group digits using the group separator.

  • 0 – False. Do not group digits.

  • -2 – Use the setting configured in the regional and language options for the computer.

To help make your scripts easier to read and maintain, you might want to use constants in place of the literal values. For example, in this script it is not readily apparent what the 0 means:

Wscript.Echo FormatNumber(37.874, 0)

Using a constant makes the script easier to understand:

Const NoDecimalPlaces = 0
Wscript.Echo FormatNumber(37.874, NoDecimalPlaces)

Because each of the FormatNumber parameters is optional you, can perform such tasks as grouping digits without specifying values for the other parameters. However, the parameter for grouping digits must still be the fifth item in the parameter list (after the number to be formatted and the other three optional parameters). To ensure that the parameters maintain the correct order, you can do one of the following:

Use a “blank” parameter (that is, simply insert a comma without including a value).

Use the default value by setting each unused parameter to False.

Use the default value by setting each unused parameter to a constant that has been set to False.

For example, in this script constants are assigned the appropriate values, and then used to format the result of 1 / –7:

Const Decimals = 3
Const LeadingZero = True
Const UseParentheses = True
Const Default = False
NumberToFormat = 1 / -7
Wscript.Echo NumberToFormat
Wscript.Echo FormatNumber(NumberToFormat, Decimals)
Wscript.Echo FormatNumber(NumberToFormat, Default, LeadingZero)
Wscript.Echo FormatNumber(NumberToFormat, Default, Default, UseParentheses)

When the preceding script runs under CScript, the following output appears in the command window:

-0.142857142857143
-0.143
-0.14
(0.14)

The following script snippet shows different ways to format the value returned when multiplying 33 by 454:

Const GroupDigits = True
Const Default = False
Const NoDecimals = 0
NumberToFormat = 33 * 454
Wscript.Echo NumberToFormat
Wscript.Echo FormatNumber(NumberToFormat, Default, Default, Default,
GroupDigits)
Wscript.Echo FormatNumber _
    (NumberToFormat, NoDecimals , Default, Default, GroupDigits)

When the preceding script runs under CScript, the following output appears in the command window:

14982
14,982.00
14,982

Formatting Percentages

Sometimes raw numbers are not as meaningful as percentages. For example, event logs have a maximum size that can vary from computer to computer. Because of that, simply knowing the current size of an event log (a property that can be retrieved by using WMI) will not tell you whether the log is nearly full and should be backed up and cleared. In this case, it might be more useful to know the maximum allowed size (another property available through WMI) as well as the current size. This lets you determine what percentage of the event log is “full” and what percentage is still available.

If you are going to retrieve numbers as percentages, it is a good idea to also report those numbers as percentages. For example, suppose you have an event log with a maximum size of 5,000,000 bytes and a current size of 3,127,354 bytes. To determine how full the event log is, you can divide 3,127,354 by 5,000,000. When you do the calculation, the value .6254708 is returned. To lessen the possibility of confusion, it is better to have this value returned as a percentage (63%).

The FormatPercent function converts a number to a percentage; in effect, this means multiplying the number by 100 and then appending a percent symbol. However, FormatPercent also accepts the same optional parameters as FormatNumber. These parameters allow you to:

Specify the number of digits to be displayed after the decimal point.

Place a leading 0 in front of fractional values (numbers less than 1 but greater than –1).

Display negative values in parentheses.

Group digits using a group separator (allowing you to display a value as 6,500,000% rather than 6500000%).

The syntax for the FormatPercent function is identical to that for FormatNumber: You supply the value to be formatted, followed by zero or more parameters. As with FormatNumber, the order in which the parameters are presented is crucial. For example, the leading 0 must always be the second parameter following the value being formatted. If you do not want to specify a value for the first parameter (number of digits following the decimal point), use False as a placeholder for that parameter.

The following script divides 1 by 7 and echoes the result. The script then uses FormatPercent to display the result as a percentage, with no decimal places.

Const NoDecimals = 0
NumberToFormat = 1 / 7
Wscript.Echo NumberToFormat
Wscript.Echo FormatPercent(NumberToFormat, NoDecimals)

When the preceding script is run under CScript, the following output appears in the command window:

0.142857142857143
14%

Running Statements Multiple Times

One reason system administration is so difficult and so time-consuming is that computing environments are dynamic. This means that system administrators find themselves doing the same tasks over and over. For example, the fact that a computer has adequate hard disk space today is no guarantee that it will have adequate hard disk space a month from now, a week from now, or even an hour from now. Because there is no guarantee, you must check hard disk space repeatedly. Likewise, when monitoring processor use, you might want to check this value every 10 seconds for the next 5 minutes. Trying to perform this task manually would be very difficult, extremely tedious, and prone to error.

Of course, scripts get neither tired nor bored, nor are scripts apt to start making errors the more often they have to repeat a task. This makes a script the perfect vehicle for carrying out the same activity over and over. With VBScript, repetitive tasks can be performed by means of a Do loop within your code.

Do Loop

The Do loop allows you to repeatedly run a set of statements until a specified condition has been met. For example, you might want to periodically monitor free disk space until the amount of free disk space falls below a specified value. Likewise, you might want to run some sort of disk cleanup process as long as available memory remains above a certain level; if memory drops below that level, you might end the cleanup process as a way to regain memory. The Do loop allows you to run loops for an indefinite number of times. (The For Next loop, by contrast, requires that the loop be run a specific number of times.)

VBScript supports two types of Do loops: Do Until and Do While. The Do Until loop continues running a loop until a specific condition is met. This generally means that the loop runs until the loop condition becomes True. For example, the following script uses the FileSystemObject to open a text file and then read in the text file line by line. To do this, the code for reading the text file is placed in a loop that will continue to run until the end of the file (the text stream) has been reached. In the following script, the loop condition has the following syntax:

Do Until objTextFile.AtEndOfStream

This is read as, “Continue looping until the end of the file is reached.”

Const ForReading = 1
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile _
    ("c:	est.txt", ForReading)
Do Until objTextFile.AtEndOfStream
    strNextLine = objTextFile.Readline
    Wscript.Echo strNextLine
Loop

When the preceding script runs, the loop condition is checked each time to see whether the end of the file has been reached. If it has, the loop will not run. If it has not, the loop will run, and the next line of the file will be read.

By contrast, the Do While loop runs as long as the condition has not been met (that is, as long as it is False). The following script uses the syntax:

Do While Not objTextFile.AtEndOfStream

This is read as, “Continue looping as long as the end of the text file has not been reached.” If the text file still has unread lines, this loop will continue to run.

Const ForReading = 1
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objTextFile = objFSO.OpenTextFile _
    ("c:	est.txt", ForReading)
Do While Not objTextFile.AtEndOfStream
    strNextLine = objTextFile.Readline
    Wscript.Echo strNextLine
Loop

Creating Endless Loops

As you might expect, it is possible to create an endless loop, one that never stops running. (If the condition is never met, the loop will continue to run forever.) This is often a mistake on the part of the script writer. However, suppose you have a script that alerts you each time an error event is recorded in the event log. You do not want that loop to stop after the first alert has been sent; instead, you would want the loop to continue and allow you to continue to receive these notifications.

To create a loop that runs forever, you simply specify a loop condition that will never be met. For example, in the following script fragment, the loop is designed to run until Loop Variable is equal to 0. Because the script does nothing to change the value of Loop Variable, this condition will never be met, and the loop will continue to run indefinitely.

LoopVariable = 1
Do Until LoopVariable = 0
    FreeMegaBytes = objLogicalDisk.FreeSpace / CONVERSION_FACTOR
    If FreeMegaBytes < WARNING_THRESHOLD Then
        Wscript.Echo Computer & " " & objLogicalDisk.DeviceID & _
            " is low on disk space."
    End If
Loop

The only way stop this loop is to stop the script.

Checking the Loop Condition

VBScript allows you to check the loop condition in one of two places: either before the loop begins or after the loop has concluded. This can make a difference in whether the loop is run. If you want to run the loop only if the condition has been met, check the condition before the loop is run. If you want ensure that the loop always runs at least one time, regardless of whether the condition has been met, check the condition after the loop has been run.

The following script sample illustrates the difference between the two methods for checking loop conditions. After setting a variable named LoopVariable to 1, the script sets up two loops. In the first loop, the condition is checked before the loop runs; this is done by using the following syntax:

Do Until LoopVariable = 1

The second loop uses this syntax, which does not check the condition until after the loop has been run:

Loop Until LoopVariable = 1

The script itself looks like this:

LoopVariable = 1
Do Until LoopVariable = 1
    Wscript.Echo "Script entered loop 1."
Loop
Do
    Wscript.Echo "Script entered loop 2."
Loop Until LoopVariable = 1

When the preceding script runs, the following output appears on the screen:

Script entered loop 2.

As you can see, loop 2 ran a single time, while loop 1 did not run at all.

Why the difference? In the first loop, the condition (is LoopVariable equal to 1?) is tested before the loop begins. Because LoopVariable equals 1, the Do loop is skipped altogether.

In the second loop, however, the condition is not tested until after the loop has begun. Because scripts are processed sequentially, line by line, VBScript does the following:

  1. Sets up the Do loop.

  2. Echoes the message “Script entered loop 2.”

  3. Checks to see whether the condition (is LoopVariable equal to 1?) is True. Because it is, the loop stops, but only after it has run one time.

With the Loop Until construct, the loop will always run at least one time. If that is not your intention, use the Do Until construct.

Exiting a Loop

After you have entered a Do loop, VBScript will, by default, process each line of code within that loop. In most cases, that is exactly what you want it to do. However, there might be times when you want to exit a loop immediately, without running every line of code. In that event, you can use the Exit Do statement to immediately stop processing of the loop.

For example, the following script has been designed to cause an error (dividing 5 by 0). The loop condition states, “Run this loop until an Err occurs” (Err <> 0). That might lead you to believe that the loop will stop as soon as the script attempts to divide 5 by 0.

On Error Resume Next
Do Until Err <> 0
    X = 5/0
    Wscript.Echo "The result of the equation is " & X & "."
Loop

When you run the script, however, you see the dialog box shown in Figure 2.17.

Erroneous Message Box

Figure 2.17. Erroneous Message Box

Why? When the script encountered the divide by 0 error, it did not exit the loop, even though the loop condition specified, “Run this loop until an error occurs.” Instead, it continued processing the remaining statements in the loop. This is because the loop condition, in this example, is tested only before the loop is running. As a result, the dialog box echoes the message, “The result of the equation is ” and the result of the equation, which happens to be Null. After processing this statement, the script loops around, checks the error condition, and then exits the loop.

If you need to stop a loop before the loop actually completes, use the Exit Do statement. In the following script the error condition is checked within the loop itself. If an error has occurred, an error message is displayed, and the Exit Do command is used to stop the loop. If no error has occurred, the result of the equation is displayed.

On Error Resume Next
Do Until Err <> 0
    X = 5/0
    If Err <> 0 Then
        Wscript.Echo "An error has occurred."
        Exit Do
    End If
        Wscript.Echo "The result of the equation is " & X & "."
Loop

When the preceding script runs, the message box shown in Figure 2.18 appears. As you can see, the script stopped the loop before processing the statement that would have displayed the equation result.

Message Box Indicating That an Error Has Occurred

Figure 2.18. Message Box Indicating That an Error Has Occurred

Making Decisions

Typically, VBScript runs a script in sequential order, running line 1, then line 2, and then line 3, continuing until it runs the last line and then automatically completes.

In general, this makes script writing easier; after a script has started, you can rely on VBScript to run each line in the proper order and then stop the script and release any memory that was allocated to run the script. At the same time, however, the fact that VBScript runs every line in a script introduces a complication: What happens if there are lines in the script that you do not want to run?

For example, suppose you have a script that backs up a set of files to a remote computer and then deletes the original files from the local computer. Provided there are no problems, you want the script to run each line. But what if the remote computer is unavailable? In that case, if the script runs each line, it will try to back up the files. This will fail because the remote computer is not available. It then deletes the original files, even though the backup was not successful. Unfortunately, the delete operation succeeds, and you lose the files.

In a case such as this, you want the script to check the condition — is the remote computer available? — and proceed only if the condition is True. This type of decision-making can be implemented by using If Then or Select case statements.

Testing Multiple Conditions

The scripts used in the first half of this chapter made a decision based on a single condition: If the amount of free disk space was less than a specified amount, an alert was triggered:

If FreeMegaBytes < WARNING_THRESHOLD Then
    Wscript.Echo objLogicalDisk.DeviceID & " is low on disk space."
End If

There might be times, however, when you want to take action only if two or more conditions are met. For example, you might want immediate notification if your e-mail servers start running out of disk space. For other servers, you might not need immediate notification. Instead, you can simply check the log at some point to verify that these servers have adequate disk space.

In a situation such as this, you want an alert to be triggered only if two conditions are true: the amount of free disk space has dropped below the specified level and the server in question is an e-mail server. You can test for multiple conditions by using the logical operator And.

In the following script fragment, an alert is triggered only if free disk space has fallen below the threshold and the computer in question is an e-mail server. If either of these two conditions is not true (that is, either there is adequate disk space or the computer is not an e-mail server), no alert will be triggered.

If FreeMegaBytes < WARNING_THRESHOLD And ServerType = "Email" Then
    Wscript.Echo objLogicalDisk.DeviceID & " is low on disk space."
End If

On other occasions, you might want to take action if any one of a specified set of conditions is true. For example, you might want an alert if free disk space has fallen below a specified threshold or if available memory has fallen below a specified threshold. The decision matrix for this type of script is shown in Table 2.17.

Table 2.17. Decision Matrix

Adequate Disk Space?

Adequate Memory?

Trigger Alert?

Yes

Yes

No

Yes

No

Yes

No

Yes

Yes

No

No

Yes

To make this type of decision, use the logical Or operator, as shown in this code snippet:

If FreeMegaBytes < WARNING_THRESHOLD Or AvailableMemory < MEMORY_THRESHOLD Then
    Wscript.Echo "Computer is low on disk space or memory."
End If

The logical Or operator returns True if either condition is true (or if both conditions are true). In the preceding example, if FreeMegabytes is less than the warning threshold or if Available Memory is less than the memory threshold, the condition is true, and the warning message is echoed. Thus, the warning is displayed if the computer is running low on memory, even if free disk space is adequate.

If Then ElseIf

System administrators often work in an either-or world: Either a computer has enough disk space or it does not; either a computer has enough available memory or it does not. In situations in which there are only two possible conditions — either a computer has enough free disk space or it does not — the If Then Else construction allows you to test the condition and choose a course of action, with a minimal amount of coding.

At other times, however, there can be more than two possible conditions. For example, you might assess maintenance requirements for disk drives by using the following criteria:

A drive with less than 100 megabytes of free space requires immediate attention.

A drive with less than 250 megabytes of free space but more than 100 megabytes of free space should be looked at as time allows.

A drive with more than 250 megabytes of free space requires no attention at this time.

To evaluate multiple possibilities, you can use an If Then ElseIf statement. With an If Then ElseIf statement in your script, the script checks for the first condition (for example, x = 1). If true, the script takes the specified action and then exits the If Then construct.

But what if the condition is false and x does not equal 1? In that event, the script can check for a second condition by using a syntax similar to this:

ElseIf x = 2 Then

You can continue inserting ElseIf statements until you have exhausted all the possible values for x. (Actually, to ensure that you have all the possible values covered, your last statement should be simply an Else statement. The Else statement can be translated as, “If none of the preceding conditions are true, then take this action.”)

For example, the following lines of code echo a message depending on whether:

FreeMegabytes is less than or equal to 100.

FreeMegabytes is greater than 100 but less than 250.

FreeMegabytes is equal to or greater than 250.

If FreeMegabytes <= 100 Then
    Wscript.Echo Computer.Name & " requires immediate attention."
ElseIf FreeMegabytes < 250 Then
    Wscript.Echo Computer.Name & " should be looked at as soon as possible."
Else
    Wscript.Echo Computer.Name & " requires no attention at this time."
End If

When evaluating multiple possibilities using either an If Then ElseIf or a Select Case statement (discussed in the next section of this chapter), VBScript checks each condition until it finds a True statement. At that point it takes action without evaluating any of the other conditions. That means it is very important to properly order your conditional statements.

For example, consider the following script sample:

x = 5
If x < 20 Then
    Wscript.Echo "X is between 11 and 20."
ElseIf x < 11 Then
    Wscript.Echo "X is between 0 and 10."
Else
    Wscript.Echo "X equals 0."
End If

Figure 2.19 shows the message box that is displayed when you run this script.

Incorrectly Ordering Conditional Statements

Figure 2.19. Incorrectly Ordering Conditional Statements

Why is the result incorrect? The first condition checked in the If Then ElseIf statement is this: Is X less than 20? Because that happens to be true, VBScript echoes the message, “X is between 11 and 20” and then immediately exits the statement block. The script never checks to see whether X is less than 11 because processing ends as soon as a True statement is found.

To get the script to work properly, the conditions must be reordered like this:

x = 5
If x = 0 Then
    Wscript.Echo "X equals 0."
ElseIf x < 11 Then
    Wscript.Echo "X is between 0 and 10."
Else
    Wscript.Echo "X is between 11 and 20."
End If

It is generally a good idea to always include an Else statement as the last statement within the If Then block; this enables your script to account for all conditions, not just the ones specified in the ElseIf statement. For example, consider the following script snippet, which matches a job ID number with a job title:

If JobID = 1 Then
    Wscript.Echo "This user is a manager."
ElseIf JobID = 2 Then
    Wscript.Echo "This user is not a manager."
End If

The preceding script works fine as long as each user has been assigned a job ID of 1 or 2. But what if the user was assigned a job ID of 3, or not assigned a job ID? By adding an Else statement, you can account for any job ID that is not 1 or 2:

If JobID = 1 Then
    Wscript.Echo "This user is a manager."
ElseIf JobID = 2 Then
    Wscript.Echo "This user is not a manager."
Else
    Wscript.Echo "This user has not been assigned a valid job ID."
End If

Select Case

The Select Case statement provides a more readable alternative to the If Then ElseIf statement. If you have a script that evaluates more than three conditions, you will typically find it faster and easier to use Select Case rather than If Then ElseIf. For example, the following code sample uses the Select Case statement to evaluate the printer status codes that can be returned by using WMI:

Select Case PrinterStatus
    Case 1 strCurrentState = "Other"
    Case 2 strCurrentState = "Unknown"
    Case 3 strCurrentState = "Idle"
    Case 4 strCurrentState = "Printing"
    Case 5 strCurrentState = "Warming Up"
End Select

By comparison, the task written using an If Then ElseIf statement is almost twice as long, and more difficult to read:

If PrinterStatus = 1 Then
    strCurrentState = "Other"
ElseIf PrinterStatus = 2 Then
    strCurrentState = "Unknown"
ElseIf PrinterStatus = 3 Then
    strCurrentState = "Idle"
ElseIf PrinterStatus = 4 Then
    strCurrentState = "Printing"
ElseIf PrinterStatus = 5 Then
    strCurrentState = "Warming Up"
End Select

To use a Select Case statement, do the following:

  1. Start the statement block with Select Case, followed by the name of the variable being evaluated.

  2. For each potential value, use a Case statement followed by the potential value, and then by any code that should be run if the statement is true. For example, if you want to echo an error message if the value is equal to 99, use a code statement similar to the following:

    Case 99 Wscript.Echo "An error has occurred."
    
  3. Close the statement block with End Select.

You can also use a Case Else statement to account for all other possibilities:

Select Case PrinterStatus
    Case 1 strCurrentState = "Other"
    Case 2 strCurrentState = "Unknown"
    Case 3 strCurrentState = "Idle"
    Case 4 strCurrentState = "Printing"
    Case 5 strCurrentState = "Warming Up"
    Case Else strCurrentState = "Status cannot be determined."
End Select

In addition, you can place the Case statement on one line, followed by the lines of code that should be run if the statement is True. For example:

Case 1
    strCurrentState = "Other"
    Wscript.Echo strCurrentState
Case 2

Arrays

Scripts 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.

Note

Note

A full discussion of arrays, including the use of multidimensional arrays, is beyond the scope of this chapter. This chapter focuses on those aspects of arrays most commonly used in system administration scripts.

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)
arrTestArray(0) = "A"
arrTestArray(1) = "B"
arrTestArray(2) = "C"
arrTestArray(3) = "D"

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)
arrTestArray(0) = "A"
arrTestArray(1) = "B"
arrTestArray(2) = "C"
arrTestArray(3) = "D"
arrTestArray(4) = "E"

When the preceding script runs, a “Subscript out of range” error is generated.

In a situation such as this, you have two options:

You can allocate more space to the array than you will need. For example, you might declare, in advance, that arrTestArray will contain a maximum of 1,000 items. That provides adequate storage space for the array. However, it also wastes memory, and hundreds of items in the array might have no value at all.

You can create a dynamic array.

Creating Dynamic Arrays

A dynamic array offers two advantages to script writers:

You do not have to specify the maximum size of the array in advance.

The size of the array can change during the course of the script.

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:

The ReDim portion resizes the array.

The Preserve portion ensures that no data is lost when the array is resized. This is important. If you simply use the ReDim statement by itself, all the data in your array will be lost when the array is resized.

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()
intSize = 0
ReDim Preserve arrTestArray(intSize)
arrTestArray(intSize) = "A"
ReDim Preserve arrTestArray(intSize + 1)
arrTestArray(intSize + 1) = "B"

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:

  1. Create a dynamic array named arrTestArray.

  2. Create a variable named intSize and assign this variable the value 0.

  3. Connect to the WMI service and retrieve a list of the installed services.

  4. For each service in the returned collection:

    1. Use the ReDim Preserve statement to set the size of arrTestArray to the value stored in intSize. For the first service in the collection, intSize is equal to 0. The array arrTestArray will thus have a size of 0, meaning it can contain one element.

    2. Assign the name of the service (objService.DisplayName) to the array element just created.

    3. Increment the size of intSize. For example, after the first element has been assigned a value, intSize will be changed to 1 (the original value, 0, plus 1).

    4. Repeat the step for the remaining services in the collection. For the second service, ReDim Preserve sets the size of the array to 1, allowing two elements to be contained within the array. For the third service, ReDim Preserve sets the size to 2, allowing three elements to be stored in the array.

Dim arrTestArray()
intSize = 0

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\" & strComputer & "
ootcimv2")
Set colRunningServices = objWMIService.ExecQuery _
    ("SELECT * FROM Win32_Service")

For Each objService in colRunningServices
    ReDim Preserve arrTestArray(intSize)
    arrTestArray(intSize) = objService.DisplayName
    intSize = intSize + 1
Next

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
atl-dc-01,System,14
atl-dc-02,Security,3
alt-dc-02,Application,22

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

Field 1

Field 2

Field 3

atl-dc-01

DNS Server

13

atl-dc-01

System

4

atl-dc-02

Security

3

atl-dc-02

Application

22

The Split function can be used to convert a delimited string to an array. This is done by passing the function two parameters:

The delimited text string.

The delimiter.

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"
TestArray = Split(TestString , ",")
For i = LBound(TestArray) to UBound(TestArray)
    Wscript.Echo TestArray(i)
Next

When this script is run under CScript, the individual data fields are echoed to the screen:

atl-dc-01
DNS Server
13

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:

Dictionary object. The Dictionary object is part of the Script Runtime library and provides a way for you to easily store, retrieve, and modify array-like data. For more information about the Dictionary object, see “Script Runtime Primer” in this book.

Disconnected recordset. A disconnected recordset is a temporary database that is not tied to an actual physical database. Instead, the recordset is constructed in memory and is deleted when the script finishes. Like the Dictionary object, the disconnected recordset allows you to refer to items by name rather than by an index number. Equally important, a disconnected recordset has access to the same methods and properties as a standard recordset, including the ability to sort records. For example, the following code sample sorts a one-dimensional array:

For i = LBound(SampleArray) to UBound(SampleArray)
    For j = LBound(SampleArray) to UBound(SampleArray)
        If j = UBound(SampleArray) Then
        Else
            If SampleArray(j) > SampleArray(j + 1) Then
               TempValue = SampleArray(j + 1)
               SampleArray(j + 1) = SampleArray(j)
               SampleArray(j) = TempValue
            End If
        End If
    Next
Next

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 Handling

VBScript provides a relatively simple method for handling run-time errors within a script. Runtime 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."
Wscript.Echo "Line 2."
W script.Echo "Line 3."

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.

Syntax Error Message

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."
Wscript.Echo "Line 2."
Wscript.Eho "Line 3."

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.

Runtime Error Message

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

Option

Advantages

Disadvantages

Allow the script to fail.

  • Requires no work of any kind on the part of the script writer. By default, all scripts will fail when they encounter a run-time error.

  • In many cases, running part of a script might be worse than not running a script at all. For example, if you have a script that backs up files to a remote computer and then deletes the original files, you would probably prefer that the script fail if the backup cannot take place. You would not want the backup portion to fail but then have the delete portion succeed.

  • Allowing the script to fail at some arbitrary point could leave your computer in an unstable state.

  • Some failures need not be fatal. For example, suppose a monitoring script is designed to retrieve information from 100 computers. If computer 1 is inaccessible, the script will fail and no information will be returned, even though the other 99 computers are up and running.

Have the script ignore all errors.

  • Scripts will continue to run without interruption.

  • Scripts can complete a major share of their tasks, even if a problem arises. For example, a script might be able to successfully copy 99 of 100 files, even if one file could not be copied.

  • End users will not be presented with error messages that they must respond to.

  • Difficult to debug problems because no message is displayed indicating that an error occurred, nor is any clue given as to where the error might have taken place.

  • Can leave a computer in an uncertain state, wherein some actions have been carried out while others have not.

Write code to respond to errors as they occur.

  • Allows you to create meaningful error messages.

  • Allows you to create code that attempts to address the error that occurred. For example, if a script is unable to connect to a remote computer, it might try to make that connection again before terminating.

  • Requires extra work on the part of the script writer. The script writer must anticipate where errors are likely to occur and then include the code to respond appropriately.

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:

You can ignore the error and proceed with the next line of the script.

You can trap the error and include code that responds to the error in some way (for example, by displaying an error message to the user).

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
Wscript.X "Testing 1-2-3."
Wscript.X "Testing 4-5-6"
Wscript.X "Testing 7-8-9"

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."
Wscript.Echo "Line 2."
Wscript.Eho "Line 3."
On Error Resume Next
Wscript.Echo "Line 4."

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
Wscript.Echo "Line 1."
Wscript.Echo "Line 2."
Wscript.Eho "Line 3."
Wscript.Echo "Line 4."

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.
Line 2.
Line 4.

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:

  1. Checks to see whether the value of the Err object is not equal to 0. (If Err is equal to 0, that means that no run-time error has occurred.)

  2. Takes some sort of action (for example, displaying the value of Err.Number and Err.Description).

  3. Resets the value of the Err object to 0. This is very important because, as noted previously in this chapter, the Err object does not automatically reset itself each time the error condition is checked.

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
Set Computer = GetObject("winmgmts:")
If Err <> 0 Then
    Wscript.Echo Err.Number & " -- " & Err.Description
    Err.Clear
End If
Set ServiceList = Computer.ExcQuery("SELECT * FROM Win32_Service")
If Err <> 0 Then
    Wscript.Echo Err.Number & " -- " & Err.Description
    Err.Clear
End If

When this script is run under WScript, the message box in Figure 2.22 is displayed:

WMI Error Message

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:

Using the On Error Resume Next statement to turn error handling on.

Using the On Error GoTo 0 statement to turn error handling off. After this statement has been run, error handling will not take place until another On Error Resume Next statement has been encountered.

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
Wscript.Eco "A"
On Error GoTo 0
Wscript.Echo "B"
On Error Resume Next
Wscript.Eco "C"
Wscript.Echo "D"

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
B

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
Wscript.Eco "A"
On Error GoTo 0
Wscript.Eco "B"
On Error Resume Next
Wscript.Eco "C"
Wscript.Echo "D"

When this script is run under Cscript, the following error message is displayed in the command window:

C:Documents and SettingsgstempDesktopScriptsx.vbs(4, 1) Microsoft VBScript
runtime error: Object doesn't support this property or method: 'Wscript.Eco'

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
Set Test = GetObject _
    ("Winmgmts:rootcimv2:Win32_Printer.Name='TestPrinter'")
Wscript.Echo Err.Number & VbTab & Err.Description

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.

COM Object Error Number and Description

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:

Method being called when the error occurred.

Parameter responsible for generating the error.

Name of the WMI provider where the error occurred.

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
Set Test = GetObject _
    ("Winmgmts:rootcimv2:Win32_Printer.Name='TestPrinter'")
Set WMI_Error = CreateObject("WbemScripting.SwbemLastError")
Wscript.Echo WMI_Error.Operation & VbTab & _
    WMI_Error.ParameterInfo & VbTab & WMI_Error.ProviderName

When the preceding script runs, the message box in Figure 2.24 appears.

Message Box Using SWbemLastError

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
Set WMI_Error = CreateObject("WbemScripting.SwbemLastError")
Set Test = GetObject _
    ("Winmgmts:rootcimv2:Win32_Printer.Name='TestPrinter'")
Wscript.Echo WMI_Error.Operation & VbTab & _
    WMI_Error.ParameterInfo & VbTab & WMI_Error.ProviderName

Procedures

The 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:

If you include /info on the command line, the script retrieves service information.

If you include /start on the command line, the script starts a selected set of services.

If you include /stop on the command line, the script stops a selected set of services.

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:

Subroutines. Code that is run but typically does not return a value. For example, you might create a subroutine to display error messages.

Functions. Code that is run and returns a value. Functions are often used to carry out mathematical equations. For example, you might pass free disk space to a function, and the function will, in turn, convert the free space from bytes to gigabytes and then return that value.

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"
Sub EchoLine2
   Wscript.Echo "B"
End Sub
Wscript.Echo "C"

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
C

The flow of the script goes like this:

  1. The script runs line 1 and echoes the message “A”.

  2. The script parses line 2 and sees that it marks the beginning of a subroutine. Because the subroutine has not been called, the script skips lines 2, 3, and 4.

  3. The script runs line 5, the first line following the end of the subroutine, and echoes the message “C”. The script then automatically stops without ever running the subroutine.

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"
EchoLine2
Wscript.Echo "C"
Wscript.Quit
Sub EchoLineB
   Wscript.Echo "B"
End Sub

When the preceding script runs under CScript, the following output appears in the command window:

A
B
C

The flow of the script goes like this:

  1. The script runs line 1, and echoes the message “A”.

  2. The script runs line 2, which is a call to the subroutine named EchoLineB.

  3. The script skips to line 5, where the subroutine begins. It skips all the intervening lines, including line 4, which would have caused the script to stop.

  4. The script runs line 6, the only line within the subroutine, which echoes the message “B”.

  5. The script runs line 7, which marks the end of the subroutine. Because the subroutine has ended, control of the script returns to the line immediately following the line (line 2) where the subroutine was called.

  6. The script runs line 3 and echoes the message “C”.

  7. The script runs line 4 and stops.

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
Set objWMIService = GetObject("winmgmts:rootcimv2")
If Err <> 0 Then
    ErrorHandler
End If
Set colPrinters = objWMIService.ExecQuery _
    ("SELECT * FROM Win32_Printer WHERE Name='TestPrinter'")
If Err <> 0 Then
    ErrorHandler
End If
For Each objPrinter in colPrinters
    Wscript.Echo objPrinter.Name
Next
Sub ErrorHandler
    Select Case Hex(Err.Number)
        Case "80041001"
            Wscript.Echo "The call failed."
        Case "80041002"
            Wscript.Echo "The object could not be found."
        Case "80041010"
            Wscript.Echo "The specified class is not valid."
        Case "8004103A"
            Wscript.Echo "The specified object path was invalid."
        Case "80041048"
            Wscript.Echo "The specified class is not supported."
        Case Else
            Wscript.Echo "An unknown error occurred."
    End Select
    Err.Clear
End Sub

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:

The Wscript.Echo statement actually performs two tasks.

First it calls the function ThisDate. The function, in turn, sets the value of the special variable ThisDate to the current date.

After the function has completed, Wscript.Echo then echoes the value of this special variable.

Option Explicit is used, and the variable ThisDate is never declared. However, no error occurs because VBScript internally declares and initializes this function variable for you.

Option Explicit
Wscript.Echo ThisDate
Function ThisDate
    ThisDate = Date
End Function

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
Sub ThisDate
    ThisDate = Date
End Sub

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
y = 10
Wscript.Echo AddTwoNumbers(x, y)
Function AddTwoNumbers
    AddTwoNumbers = a + b
End Function

To correct this problem, include space for two parameters within the Function statement:

x = 5
y = 10
Wscript.Echo AddTwoNumbers(x, y)
Function AddTwoNumbers(a, b)
    AddTwoNumbers = a + b
End Function

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:")
Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
Wscript.Echo FreeMegaBytes(objLogicalDisk.FreeSpace)
Function FreeMegabytes(FreeBytes)
    FreeMegabytes =  FreeBytes / 1048576
    FreeMegabytes = Int(FreeMegabytes)
End Function

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:")
Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
Wscript.Echo FreeMegaBytes(objLogicalDisk.FreeSpace)
Wscript.Echo "Free space: " & FreeMegaBytes
Function FreeMegabytes(FreeBytes)
    FreeMegabytes =  FreeBytes / 1048576
    FreeMegabytes = Int(FreeMegabytes)
End Function

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.

Error Message for Improperly Accessing a Function Variable

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:")
Set objLogicalDisk = objWMIService.Get("Win32_LogicalDisk.DeviceID='c:'")
AvailableSpace = FreeMegaBytes(objLogicalDisk.FreeSpace)
Wscript.Echo "Free space: " & AvailableSpace
Function FreeMegabytes(FreeBytes)
    FreeMegabytes =  FreeBytes / 1048576
    FreeMegabytes = Int(FreeMegabytes)
End Function

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
Wscript.Echo ModifyValue(x)

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
Wscript.Echo ModifyValue(x) & VbTab & x
Function ModifyValue(x)
    ModifyValue = x / 25
    x = 99
End Function

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.

Assigning a New Value Within a Function

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
Wscript.Echo ModifyValue(x) & VbTab & x
Function ModifyValue(ByVal x)
    ModifyValue = x / 25
    x = 99
End Function

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.

Passing a Variable by 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.

Sample Folder Structure

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")
ShowSubfolders FSO.GetFolder("C:Scripts")
Sub ShowSubFolders(Folder)
    For Each Subfolder in Folder.SubFolders
        Wscript.Echo Subfolder.Path
        ShowSubFolders Subfolder
    Next
End Sub

The function ShowSubFolders does the following:

  1. Retrieves a collection consisting of all the subfolders of the root folder, C:Scripts. This collection has two items: Subfolder1 and Subfolder 2.

  2. Takes the first item in the collection, Subfolder 1, and echoes the folder path. It then uses the name of that folder as a parameter passed to itself. In other words, it now runs the function ShowSubFolders using Subfolder 1 as the parameter.

  3. Retrieves a collection consisting of all the subfolders of Subfolder 1. This collection has two items: Subfolder1A and Subfolder 1B.

  4. Takes the first item in the collection, Subfolder 1A, and echoes the folder path. It then uses the name of that folder as a parameter passed to itself. In other words, it now runs the function ShowSubFolders using Subfolder 1A as the parameter.

  5. Because Subfolder 1A has no subfolders, control passes to the next item in the collection, Subfolder 1B. The function calls itself using Subfolder 1B as the parameter.

  6. Because Subfolder 1B has no subfolders, the function has finishes recursing through Subfolder 1. It thus returns to the second item in the original collection (subfolders of C:Scripts), and repeats the entire process.

When the script runs, the following output appears:

C:scriptsSubfolder 1
C:scriptsSubfolder 1Subfolder 1A
C:scriptsSubfolder 1Subfolder 1B
C:scriptsSubfolder 2
C:scriptsSubfolder 2Subfolder 2A
C:scriptsSubfolder 2Subfolder 2B
C:scriptsSubfolder 2Subfolder 2C

Recursion is an extremely powerful technique for exploring data stored in a tree structure, including Active Directory as well as the file system.

COM Objects

The 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:

Out-of-process servers. Out-of-process servers are typically implemented in executable files and run in a different process than the script. For example, when you start a script, an instance of Wscript.exe begins to run. If you next instantiate a Microsoft Word object, you are then working with two processes: Wscript.exe and the Winword.exe process in which the Microsoft Word object runs.

In-process servers. Libraries (.dll files) are known as in-process servers because they run in the same process as the application or script that called them. For example, when you call the FileSystemObject from within a script, no new process is created. This is because the FileSystemObject (which is found in the Scrrun.dll library) is an in-process server and thus runs in the same process as the script. In-process servers typically run faster than out-of-process servers because the operating system does not have to cross process boundaries (from the script process to the object process and then back) to access the object’s methods and properties.

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:

That a new COM server is available for use.

The object classes that the new COM server makes available.

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_MACHINESoftwareClassesCLSID 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:WindowsSystem32Scrrun.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")
IE.Visible = True

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

To Perform This Task ...

Use This Method ...

Bind to WMI or ADSI.

VBScript GetObject and the appropriate moniker.

A moniker is an intermediate object that makes it possible to locate, activate, and create a reference to Automation objects. Both WMI and ADSI are accessed using monikers; this allows your script to locate WMI and ADSI objects without having to know the physical location of these objects. Monikers are typically used to bind to COM objects that reside outside the file system.

Set TestObject = GetObject("winmgmts:")

Bind to a new instance of an Automation object.

VBScript CreateObject and the appropriate ProgID.

Set TestObject = CreateObject("Word.Application")

Bind to an existing instance of an Automation object.

VBscript GetObject and the appropriate ProgID.

Set TestObject = GetObject("Word.Application")

Bind to an Automation object by using an existing file.

VBScript GetObject and the appropriate file path.

Set TestObject = GetObject("c:scripts	est.xls")

Bind to a new instance of an Automation object, with the ability to receive event notifications from that object.

Wscript CreateObject, the appropriate ProgID, and an event mapping variable.

Set TestObject = Wscript.CreateObject _
    ("Word.Application", "Word")

Bind to an existing instance of an Automation object, with the ability to receive event notifications from that object.

Wscript GetObject, the appropriate ProgID, and an event mapping variable.

Set TestObject = Wscript.GetObject _
    ("Word.Application", "Word")

Bind to a new instance of an Automation object on a remote computer.

VBScript CreateObject, the appropriate ProgID, and the name of the remote computer.

Set TestObject = CreateObject _
    ("Word.Application", "atl-dc-01")

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
Set TestObject = CreateObject("Fake.Object")
Wscript.Echo IsObject(TestObject)

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")
TestObject.Quit
Wscript.Echo IsObject(TestObject)

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")
Wscript.Echo "Click here to end the script."

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")
Wscript.Echo "Click here to end the script."

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.

Automation Object Running After a Script Has Completed

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")
TestObject.Quit

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")
Set TestObject = Nothing
TestObject.Quit

When this script runs, the error message shown in Figure 2.30 appears. The script fails because TestObject no longer represents a valid reference.

Working with an Invalid Object 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")
Set TestObject = Nothing
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset