Chapter 18. Scripting Guidelines

Many system administrators who write scripts work as part of a team. This means it is important that the code they write follow accepted standards and conventions, making it easy to read and understand. By following a defined set of scripting guidelines, you can write clean, consistent code that your team can easily read, understand, and maintain. The standards presented in this chapter help you create high-quality scripts to use on the Microsoft® Windows® 2000 family of operating systems.

In This Chapter

Overview of Scripting Guidelines

When you write a script, you should always consider how the script might be used in the future. Some scripts are disposable by design: they are written once, they are run once, and then they are never used again. These scripts might resolve unique situations, or be so short and easy to write that there is no need to maintain them. Should the need arise again, you can easily re-create the script. This is one of the benefits of scripting: you can make the process as formal or as informal as needed.

Other scripts might be run repeatedly or be copied and used by other administrators. These administrators might need to modify the scripts to fit their own needs. At the very least, they will probably want to view the script code so they can better understand what the script actually does, and how. In these situations, it is a good idea if your scripts follow accepted organizational standards for such things as commenting, formatting, and naming; following these standards makes it easier for others to read, modify, and maintain the scripts.

As you read this chapter, keep in mind that everything suggested here is just that: a suggestion. You need not implement each guideline in every script; after all, a two-line script that maps a network drive probably does not need a 12-line script header and multiline comments. The first rule of script writing is: Never make things more complicated than they need to be.

Preparing for Visual Basic .NET

The scripts used in this chapter are written in Microsoft® Visual Basic® Scripting Edition (VBScript). Throughout this chapter, you will find notes that compare VBScript with the .NET programming languages (in particular, Microsoft® Visual Basic® .NET). If you plan to move to Visual Basic .NET, keep these notes in mind when you choose scripting guidelines for your organization, Although the scripts you write now cannot be copied as is into Visual Basic .NET, it is easier to manually convert scripts if you adopt the .NET standards now.

Using Naming Conventions

In many organizations, names of assets — whether they are buildings, computers, printers, or some other asset — follow a standardized convention. For example, a computer name might consist of the name of the primary user followed by the user department. These conventions provide at least two advantages. First, the name alone gives you important information about the item; by itself, the computer name kenmeyer-finance tells you that this computer belongs to Ken Meyer in the Finance department. Second, this convention takes the guesswork out of naming new assets; when Pilar Ackerman in Accounting gets a new computer, coming up with a name for this computer requires very little effort.

Scripts are no different than these other assets; it can be advantageous to use standard, consistent, and meaningful names for scripts and for the variables, constants, functions, and procedures used in those scripts. These naming conventions help make your scripts easier to read, understand, and maintain.

Table 18.1 contains a brief overview of naming conventions in scripting. These conventions are discussed in more detail later in this chapter.

Table 18.1. Naming Conventions in Scripting

Item

Naming Convention

Example

Script file name

As short as practicable, with no spaces or special characters in the name. Capitalize the first letter of each word in the name.

CreateOU.vbs

Variable

Meaningful name that reflects the purpose of the variable and the data it stores. The first word in the name should be a prefix indicating the type of data stored in the variable. The prefix should begin with a lowercase letter, and all subsequent words should begin with an uppercase letter.

intNumberOfFileServers

Constant

All uppercase letters. Separate individual words with underscores, except for constants that are intrinsic to a language or to an automation object. In those cases, follow the naming convention used by the language or the automation object. For example, use VbTab rather than VB_TAB.

DOMAIN_NAME

Function or Procedure

Verb followed by the noun that the verb acts upon. Capitalize all words in the name.

DeleteBackupFiles

Naming Scripts

If you are like most people, you consider the 8.3 naming convention (file names consisting of no more than 8 letters, followed by a three-letter file extension) to be a bygone relic of the distant past. Admittedly, there is probably no need to limit file names to the 8.3 naming convention; however, there are good reasons for keeping file names as short as possible and for not including spaces or special characters in those names. Although the name “Create an Exchange mailbox for a new user.vbs” clearly indicates the purpose of the script, long file names can create problems for system administrators who actually use the script. For example, to run a script named “NewMailbox.vbs” from the command line, you need only type the following:

newmailbox.vbs

By contrast, it takes more time to type a long file name such as the following:

"create an exchange mailbox for a new user.vbs"

Not only does this require additional typing, but using spaces in the name requires you to enclose the command in quotation marks. This can make it difficult for an administrator with limited command shell experience to run the script; he or she might not know that the file name must be enclosed in quotation marks. If the script requires arguments that must also be enclosed in quotation marks, the command-line string becomes even more complicated, and the chances of making an error are even greater.

Tip

Tip

Because long command-line strings are difficult to remember, and even more difficult to type in correctly, you should also limit command-line arguments to single letters. Typing a command such as sample.vbs /? is easier and faster than typing sample.vbs /help. Similarly, use standard command-line arguments such as sample.vbs /?. Do not invent for your own “standard,” such as sample.vbs /more_info.

File or path names with spaces also make calling scripts and batch files from Windows Script Host (WSH) more difficult. As shown in the following code sample, starting a second script from WSH is easy when the path name does not contain spaces.

Const VbNormalFocus = 1
Set objShell = Wscript.CreateObject("Wscript.Shell")
intApplicationStarted = objShell.Run("C:ScriptsCreateUsers.vbs", _
    vbNormalFocus, True)

This same call is more difficult when there is a space in the path name because the Run method requires you to enter the path exactly as you would from the command prompt. When entering commands from the command prompt, you must enclose path names that include spaces in double quotation marks as follows:

" c:scriptscreate users.vbs"

Because the Run method also requires double quotation marks around the path name, you must use three sets of double quotation marks to start the application. This makes the statement more difficult to write and more difficult to maintain:

Const VbNormalFocus = 1
Set objShell = Wscript.CreateObject("Wscript.Shell")
intApplicationStarted = objShell.Run("""C:ScriptsCreate Users.vbs""", _
    vbNormalFocus, True)

As you might expect, it is also a good idea to avoid using spaces in folder names and to keep scripts in top-level folders whenever possible. Typing C:ScriptsCreateUsers.vbs is much faster and easier than typing “C:Program FilesAdministrative Tools Domain AdminsScriptsActive DirectoryUser ManagementCreate Users.vbs.

Naming Variables

If you were writing a novel, it is unlikely that you would give every character the same name. Although the storyline would not change, the plot in such a novel would be very difficult, if not downright impossible, to follow. That is at least one reason why novelists give their characters unique — and sometimes very meaningful — names.

In the same way, the use of unique and meaningful variable names can make your scripts much easier for others to read and understand. For example, the following script snippet includes a mathematical equation. You cannot easily tell if this equation is correct because the script uses variables named x and y. To make things even more confusing, the variable x is used not only as one of the items in the equation but also to represent the end result of the equation. This is allowed in VBScript but not recommended; reusing variables often causes confusion for people maintaining a script.

x = 2
y = 4
x = x * y

Compare the preceding script to the following script snippet. Although the scripts perform the identical function, meaningful variable names greatly improve readability. Furthermore, each variable represents only one item. As a result, it is very easy to look at this script and verify that the equation is correct.

Length = 2
Width = 4
Area = Length * Width

The preceding script uses variable names that clearly indicate what each variable represents. Keep in mind that even a variable with a name such as UserInfo can cause confusion because it does not indicate what kind of data should be stored in it. More descriptive names, such as UserMonthlySalary, UserIDNumber, and UserLastName, are less likely to cause confusion.

Using Hungarian Notation

It is also helpful if variable names indicate the type of data stored in the variable. Variable names such as strUserLastName indicate not only that the variable stores the user’s last name but also that this name is made up of string data. This type of naming scheme is commonly referred to as Hungarian Notation.

Note

Note

The term “Hungarian notation” was coined in honor of former Microsoft Distinguished Engineer Charles Simonyi, who first proposed this method of naming variables. The term was chosen both in deference to Simonyi’s Hungarian nationality and to the fact that resulting variable names — such as strUserLastName — did not always resemble English. The Hungarian Notation used in this chapter has been slightly modified from the original version to better meet the needs of script writers.

Hungarian notation is based on the premise that the name of a variable should tell you everything you need to know about that variable. In addition to describing the purpose of the variable, variable names have a prefix that indicates the type of data that the variable holds. This is especially important with VBScript because VBScript is a typeless language: VBScript variables can hold any type of data, making it easy to inadvertently store the wrong data in a variable.

For example, by looking at the variable named UserID, you cannot tell whether a value such as ACCT-1005 is a valid ID. If UserID can contain string data, ACCT-1005 might be valid. If UserID can contain only numeric data, however, ACCT-1005 is not valid.

By adding a prefix that indicates the type of data stored in the variable, you can more easily identify problems that occur when the wrong type of data is stored in a variable. Because VBScript is a typeless language, nothing prevents you from storing an improper value in the variable. However, when you are debugging a script that is not working properly, noticing that a variable named intUserID has the value ACCT-1005 immediately provides at least one reason why the script is not working. Assigning improper data types to a variable also causes problems when dealing with databases, which typically require the correct data type, and when carrying out mathematical equations, date conversions, or other functions that require specific kinds of data.

When naming variables using Hungarian Notation, format the variable names using Camel casing. With Camel casing, the first part of the variable name (the prefix that indicates the data type) should begin with a lowercase letter. All subsequent words in the name then begin with an uppercase letter. Consequently, variables have names such as intUserID and strUserLastName rather than IntUserID or struserlastname.

Table 18.2 lists various data subtypes recognized by VBScript, as well as the recommended prefix for variables designed to store each kind of data.

Table 18.2. Recommended Prefixes for VBScript Data Subtypes

Subtype

Description

Prefix

Example

Empty

Variable is not initialized. Value is 0 for numeric variables or a zero-length string (“”) for string variables.

  

Null

Variable intentionally contains no valid data.

  

Boolean

Contains either True or False.

bln

blnIsUSCititzen

Byte

Contains an integer in the range 0 to 255.

byt

bytDepartmentNumber

Integer

Contains an integer in the range –32,768 to 32,767.

int

intNumberOfDirectReports

Currency

–922,337,203,685,477.5808 to 922,337,203,685,477.5807.

cur

curAnnualSalary

Long

Contains an integer in the range -2,147,483,648 to 2,147,483,647.

lng

lngSalesByDepartment

Single

Contains a single-precision floating-point number in the range –3.402823E38 to -1.401298E–45 for negative values; 1.401298E–45 to 3.402823E38 for positive values.

sng

sngHourlyPayRate

Double

Contains a double-precision floating-point number in the range -1.79769313486232E308 to -4.94065645841247E–324 for negative values; 4.94065645841247E–324 to 1.79769313486232E308 for positive values.

dbl

dblMeritPayMulitplier

Date(Time)

Contains a date between January 1, 100 and December 31, 9999.

dtm

dtmHireDate

String

Contains a variable-length string. Strings can be made up of any alphanumeric characters.

str

strUserLastName

Object

Contains an object reference. An object variable represents an Automation object.

obj

objExcelSpreadsheet

Error

Contains an error number.

err

errFindFile

Array

Contains an array of variables. Because arrays are designed to hold multiple objects, array names should always be plural. To maintain consistency with the other naming conventions, arrays should have two prefixes: arr to indicate the array, and a second prefix to indicate the data type.

arr

arrstrUserAccountNames

Collection

Technically, a collection is not a variable subtype. However, it is listed in this table because you should use the col prefix to indicate collections. Collections are used extensively in system administration scripting.

col

colInstalledApplications

Guidelines for Naming Variables

Both Hungarian notation and Camel casing provide basic rules for creating variable names. In addition to these fundamental principles, some other suggested guidelines for naming variables in VBScript are listed in Table 18.3.

Table 18.3. Naming Guidelines for VBScript Variables

Variable Property

Guidelines

Length

Can be a maximum of 255 characters. However, variable names should be considerably shorter (32 characters or less). Shorter names are easier to read and easier to maintain. (Among other things, they take up less space on each line of code.)

First character

Must begin with an alphabetic character. A variable name such as User5 is allowed; a variable name such as 5User is not.

Special characters

Must not contain periods or spaces. Characters used in variable names should be limited to the following:

  • a–z

  • A–Z

  • 0–9

In addition, variable names should start with one of the lowercase alphabetic characters (a–z).

Case sensitivity

Not case sensitive. intUserID and INTUSERID refer to the same variable value. However, it is recommended that you use Camel casing for all your variable names.

Uniqueness

Must be unique in the scope in which they are declared. VBScript allows you to use the same variable name more than once, provided the variables have different scopes (for example, they are declared within separate procedures or in separate scripts used as part of a library). You must, however, use unique global variable names throughout a script.

Consistency

Many scripts require the same variables; for example, every Windows Management Instrumentation (WMI) script requires an object variable that represents the WMI service. Whenever possible, these names should be consistent among scripts; if you use the variable name objWMIService in the first WMI script you write, use that same variable name in any subsequent WMI scripts.

Reusing Variables

For the most part, you should not reuse variables during the execution of a script; each variable should represent only one item. For example, the following script code is valid. However, using the variable strName to represent, at various times, the user’s first name, the user’s middle initial, and the user’s last name causes confusion.

strName = "Robert"
strName = "M."
strName = "Smith"

To lessen confusion, use separate variables to represent each piece of data, as shown in the following script snippet. The code is easier to read and maintain, and there is no appreciable performance penalty for using three separate variables as opposed to reusing the same variable.

strFirstName = "Robert"
strMiddleInitial = "M."
strLastName = "Smith"

Theoretically, reusing variables can help conserve memory. In the preceding script snippet, the variables strFirstName, strMiddleInitial, and strLastName must all be held in memory at the same time, even if they are used just once and then discarded. However, the amount of memory saved by reusing the same variable as opposed to using three separate variables is negligible. Memory conservation is usually not an issue in system administration scripts.

Loop variables, such as those in a For-Next loop, are an exception to this rule about never reusing variable names. Many script writers use i as the primary loop variable in scripts; additional, nested, loop variables take the values j, k, l, m, and so on. For example, the following script code includes two separate loops, both of which use i as the loop variable:

For i = 1 to 10
    Wscript.Echo i
Next

For i = 1 to 10
    intTotalCount = intTotalCount + i
Next
Wscript.Echo intTotalCount

Naming Constants

Constants provide meaningful names for values that cannot be changed while a script runs. User-defined constants are typically named using all uppercase letters, with underscores separating individual words in the constant (for example, NUMBER_OF_DEPARTMENTS or STATE_SALES_TAX). This formatting convention indicates that NUMBER_OF_DEPARTMENTS is a constant and thus cannot be changed at any point during the running of a script. Trying to change the value of a constant while a script is running results in an error. Because constants are not the same thing as variables, it makes sense to format their names in a different manner.

Scripting languages, automation objects, type libraries and other similar entities typically include intrinsic constants as well. For those intrinsic constants, follow the naming convention used in the language or object’s documentation. For example, VBScript includes such intrinsic constants as VbGeneralDate (used in formatting dates) and VbAbortRetryIgnore (used in constructing message boxes). You should use these names rather than names such as VB_GENERAL_DATE.

Naming Functions and Procedures

Functions and procedures are sections of code that carry out specific actions; in most cases, functions and procedures are employed because these actions must be carried out repeatedly during the running of the script. For example, you might have a script that needs to connect to the WMI service on several different computers. Instead of repeating that same code over and over again, you might place this code in a function or procedure. Each time you need to connect to the WMI service on a new computer, you simply call the function or procedure, passing along the name of the computer as a parameter.

Because functions and procedures carry out actions, the names given to these elements should begin with a verb that indicates the action to be performed. This verb should then be followed by a noun representing the object on which the action occurs. For example, CalculateMeritPay indicates the action that is taken (Calculate) and the object that is acted upon (MeritPay).

It is recommended that you use Pascal casing, in which each word begins with an uppercase letter, to help distinguish functions and procedures from variables and constants. Thus, use ConvertToKilograms rather than convertToKilograms.

Constructing Scripts

One of the strengths of the telephone directory is that no user manual is required to read and use it; this is because the telephone directory is laid out in a consistent and logical manner. In most U.S. communities, for example, telephone directories have a set of blue pages that contain information about schools and government agencies. These directories always feature a set of white pages that contain residential phone numbers. Phone numbers are sorted by the name of the resident, and are presented in alphabetical order, from A to Z. Telephone directories also contain a set of yellow pages that include business listings, this time ordered by the business category (Hairdressers, Sporting Goods, and so forth). No matter how big the directory, you can find phone numbers with relative ease, because you know exactly where to look for that information. Unlike, say, mystery novels, telephone directories are specifically designed to keep the suspense to a minimum.

To make your scripts easy to read and maintain, consider using a similarly consistent and logical structure. Although scripts vary depending on their nature and complexity, longer scripts might include the following sections:

  • Initialization section. Used for declaring variables and defining constants, the initialization section should always come first in a script.

    This section might also be needed to parse command-line arguments and to ensure that the script is running under the proper script host. If necessary, these actions should always occur before the script does anything else. If your script requires three command-line arguments and cannot complete successfully without all three, the script should verify the existence of those arguments before doing anything else. Never carry out half the functions in a script before verifying whether the script can complete successfully.

  • Main body. To perform the primary actions of the script, some scripts include lines of code in the main body. Other scripts might use the main body as a starting point to call procedures and functions that perform the primary actions of the script. This approach is typically used for long scripts that carry out many activities.

  • Procedures and functions. It is useful to place blocks of code used repeatedly to perform specific actions (for example, creating a user account or converting Fahrenheit temperatures to Celsius) in a function or procedure. That way, the script writer does not have to write the same code multiple times in a script.

    It is also a good idea to list procedures and functions in the same order in which they are called. This is not required; placing a procedure in one location rather than another does not affect performance. In fact, before a script is run, the script host checks the file and identifies the location of all functions and procedures. As a result, the script host can find each function and procedure regardless of its location in the script, and regardless of the order in which the functions and procedures are called. However, placing procedures strategically makes it much easier for others to read and maintain your script.

Choosing a Script Construction

Although scripts often include an initialization section, a main body, and a set of functions and procedures, you can arrange those elements in different ways. As you might expect, some of these arrangements are better than others, depending on the length and complexity of the script. For example, scripts that span hundreds of lines and need to use the same code over and over benefit from a construction in which repeated code is placed in individual functions. By contrast, functions and procedures can needlessly complicate shorter scripts that perform only a single task. For example, placing the following code in a function does nothing to help the performance of the script and only makes a simple script much more complex:

Wscript.Echo AddTwoNumbers
Function AddTwoNumbers
    AddTwoNumbers=2+2
End Function

This same script can be written as follows:

Wscript.Echo 2+2

Table 18.4 lists advantages and disadvantages of several different scripting formats.

Table 18.4. Advantages and Disadvantages of Various Scripting Formats

Script Construction

Advantages

Disadvantages

Scripts that do not call any functions or procedures

  • Easiest way to write a script

  • Good choice for small scripts (less than 100 lines) that do not repeatedly call the same functions or procedures

  • Difficult to identify key components of the script

  • Can result in needless duplication if the same lines of code are used repeatedly

  • Difficult to create script libraries because there are no procedures in the script

Scripts that scatter functions and procedures throughout the script

  • None

  • Results in code that is extremely difficult to read and maintain

  • Not recommended under any circumstances

Scripts constructed as follows:

  1. Initialization section

  2. Main body of the script

  3. All functions and procedures

  • Easy to read and understand

  • Script logically organized

  • Requires readers to flip back and forth between the main body and called functions or procedures

  • Might not be needed in shorter scripts unless a function or procedure is called multiple times

Scripts constructed as follows:

  1. Initialization section

  2. All functions and procedures

  3. Main body of the script.

  • Makes it easy to identify the primary operations that are carried out by the script

  • Can be difficult to follow the scripting logic; anyone reading the script needs to search for the main body

Scripts constructed as follows:

  1. Initialization section

  2. A single statement calling the procedure Main (which contains the main body of the script)

  3. Remaining functions and procedures

  • Follows the formatting conventions used in many programming languages, including the Visual Basic .NET languages

  • Requires readers to flip back and forth between the main body and called functions or procedures

Creating a Script Template

Templates provide a framework for entering data. When you order office supplies, you probably fill out a form of some kind. Rather than obligate you to guess at the information, and the format, required, the form will be presented to you as a template, a framework that tells you what information is required and where this information must be placed.

Script writers also need to know what information is required when they write a script, and they need to know to know how this information must be presented. A script template can help script writers adhere to organizational standards.

A script template contains placeholders predefined to fit a standard format. For example, your template might include placeholders for the script header, the initialization section, the parsing of command-line arguments, and other key sections. You might create several different templates for various script types. For example, you might have a template for Active Directory Service Interfaces (ADSI) scripts that includes all the predefined constants and procedures required to connect to your directory service.

The following is a sample script template:

'*******************************************************************************
'* Microsoft Windows 2000 Scripting Guide
'* Chapter Name:   CHAPTER_NAME_GOES_HERE
'* Sample Listing: CHAPTER_NUMBER_GOES_HERE.LISTING_NUMBER_GOES_HERE
'* File:           NAME_OF_SCRIPT_FILE_GOES_HERE
'* Purpose:        This script demonstrates ADDITIONAL_DESCRIPTION_GOES_HERE
'* Usage:          COMMAND_LINE_USAGE_GOES_HERE
'* Version:        1.0 (MONTH_GOES_HERE 2002)
'* Technology:     VBSCRIPT_WMI_ADSI_ETC._GOES_HERE
'* Requirements:   Windows 2000
'*                 Windows Script Host 5.6 - CSCRIPT.EXE_OR_WSCRIPT.EXE
'* Copyright (C) 2002 Microsoft Corporation
'* History:
'*******************************************************************************

Option Explicit

'*******************
'* Define Constants
'*******************

'********************
'* Declare variables
'********************

These templates are particularly useful when they are available through the context menu in Windows Explorer. In Windows Explorer, you can right-click a blank area in the window, point to New, and then choose from various predefined document types (including Text Document, Bitmap Image, and Wave Sound). When you click one of these document types, a new, blank document of that type is created for you; for example, if you click Text Document, a new textonly document named New Text Document.txt is created for you.

By creating a script template and adding it to the Windows Explorer context menu, you can create a new script, based on the template, simply by clicking a blank area within Windows Explorer, pointing to New, and then clicking the template. By making a template readily, and universally, accessible, you make it more likely that system administrators and other script writers within your organization will use it.

To create a template and add it to the Windows Explorer context menu:

  1. Develop the format in Notepad, and then save the file in the systemrootShellExt folder. In the example shown, the file is saved as C:WindowsSystem32ShellExtTemplate.vbs.

  2. After you save the file, add the template to the New context menu. When the template is added to the New context menu, you can create a new VBScript file based on the template by right-clicking anywhere inside a folder, pointing to New, and then clicking VBScript Script File.

  3. To add your template to the context menu, create a new registry entry referencing the template you just created, as shown in Listing 18.1.

Example 18.1. Creating a Registry Entry

 1 Set objShell = CreateObject("WScript.Shell")
 2 objShell.RegWrite "HKCR.VBSShellNewFileName","template.vbs"

Using Functions and Procedures

Many shorter scripts (those with fewer than 100 lines) are written without using any functions or procedures. Instead, the actions carried out by the script are performed in linear fashion in the main body of the script. This is the recommended way to write short scripts; needlessly wrapping code in a function that is called only once merely makes the script longer, more difficult to read, and harder to maintain.

When working with longer scripts, however, you should place your code in separate functions and procedures whenever possible, and for the following reasons:

  • Functions and procedures easily identify the tasks performed by the script. Without functions and procedures, you must read each line of code just to determine the actions carried out by the script. By using procedures and functions, it is easier to skim through the code and pick out the major tasks.

  • Functions and procedures prevent needless duplication of code. If you find yourself repeating the same lines of code in a script, encapsulate them in a function or procedure. Needless duplication of code creates extra work and can lead to problems if you revise the code and miss one of the instances where the code is used in the script.

  • Functions and procedures make your code portable and reusable. After you write code that successfully performs a task, it is likely to be copied and used in other scripts in your organization. Placing your code in a function or procedure makes the relevant statements easier to identify and copy.

  • Functions and procedures allow your code to be encapsulated in code libraries. Include files are blocks of code that are imported into a script at run time. These files can be referenced in WSH scripts through the use of Windows Script Files.

    Include files enable you to create scripting libraries, collections of code snippets that carry out particular tasks. For example, you might have code that parses command-line arguments. The next time you write a script that requires command-line arguments, you can write a single statement that calls the appropriate script in the code library. Writing the initial code as a function or procedure makes it easier to remove the code from the script itself, place it in a code library, and make it readily available to other scripts and script writers.

  • Functions and procedures make your code easier to test, troubleshoot, and debug. Functions and procedures help you control the lines of code that are actually run during testing. For example, if you suspect your script is encountering problems with a certain block of code, you can comment out calls to other functions and procedures and then test only that block of code.

Calling Functions and Procedures

VBScript provides multiple ways to call functions and procedures. Some require parentheses around parameters; others do not. This is important, because improperly called functions and procedures can either result in errors within the script or, perhaps worse, cause subtle changes that affect the data returned by the script.

Valid ways of calling functions and procedures and passing parameters are shown in Table 18.5. For more information about passing parameters by value and by reference, see “VBScript Primer” in this book.

Table 18.5. Calling Functions and Procedures by Passing Parameters

Passing Mechanism

Syntax

By reference

  • MyProcedure strVariable

  • MyProcedure strVariable, intVariable

  • Call MyProcedure (strVariable)

  • Call MyProcedure (strVariable, intVariable)

  • Return = MyProcedure(strVariable)

  • Return = MyProcedure (strVariable, intVariable)

By value

  • MyProcedure (strVariable)

  • MyProcedure ((strVariable))

  • Call MyProcedure ((strVariable))

  • Return = MyProcedure((strVariable))

First parameter by reference, second parameter by value

  • MyProcedure (strVariable), intVariable

  • Call MyProcedure((strVariable), intVariable)

  • Return = MyProcedure((strVariable), intVariable)

Note

Note

Preparing for Visual Basic .NET. As shown in Table 18.5, the Call statement can be used with VBScript. Because Call is optional in VBScript, and because it cannot be used in Visual Basic .NET, it is recommended that you not use this statement when calling functions and procedures.

Calling functions and procedures in multiple ways can lead to considerable confusion and errors within the script. For example, an error occurs if you use a syntax that inadvertently passes a parameter by value when you intend to pass it by reference (or vice versa). To prevent problems with function and procedure calls, explicitly indicate the passing mechanism when writing the function or procedure. For example, the function shown in the following script snippet makes it clear that the parameter is being passed by value:

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

As a general rule, pass parameters by value rather than by reference. Passing a parameter by value ensures that your function will not inadvertently change the value of that parameter. This is especially important if the parameter is required elsewhere in the script.

Note

Note

Preparing for Visual Basic .NET. If you do not specify ByVal or ByRef when passing a parameter in Visual Basic .NET, the parameter is automatically passed by reference. This is different from previous versions of Visual Basic, in which the default mechanism for passing parameters is by value. To help make your intention clear, explicitly indicate the passing mechanism for all functions and procedures.

Formatting Code

Because scripts are written as plaintext files with the use of a simple text editor such as Notepad, you might not think that correctly formatting your scripts would be much of an issue; besides, Notepad does not provide you with many options for formatting text. However, while such things as font size and font color might be irrelevant when writing scripts, the way you format your scripts is still important. Although formatting does not affect the efficiency, robustness, or memory required to run a script, it can improve the ease with which others can read, understand, maintain, and modify the script.

Script formatting involves a number of important considerations, including the following:

  • The use of white space. White space helps delineate the key sections on a page and provides relief for eyes easily strained by a page of otherwise solid text.

  • The length of statements. Long lines, particularly those that scroll off the screen, are difficult to read and modify. Several different techniques can be employed to help limit the length of each line of code in your script.

Using White Space

White space, achieved with blank lines, blank spaces, and character indentation, provides visual cues that help delineate and identify program flow and program sections. Because white space does not affect performance, use it whenever and wherever it is required. For instance, consider the following script, with a file size of 75 bytes.

For i = 1 to 100
   intTotal = intTotal + i
Next
Wscript.Echo intTotal

As an experiment, insert 10,000 blank lines before and after each of the lines in the script. This increases the file size to 98 KB, most of it white space. When you run the revised version of this script, there is no appreciable performance difference between the 98-KB version and the 75-byte version. Because performance is not affected, you can insert as many blank lines or blank spaces as needed to improve readability. For example, you might want to use blank spaces:

  • Before and after operators.

  • In parameter lists.

  • To indent control structures, functions, and procedures.

Using Blank Spaces Before and After Operators

A blank space before and after each operator makes your code more readable. In most scripting languages, scripts run regardless of whether or not you put spaces before or after operators; however, the additional blank spaces generally make the script easier to read. For example, in the following line of VBScript code, notice how the k in the variable intCheck tends to run together with the >= sign and the <> sign. In addition, it is hard to separate the >= sign from the – sign that precedes the 3.

If intCheck>=-3 and intCheck<>7 then

Adding spaces before and after each operator makes the code easier to read and understand:

If intCheck >= -3 and intCheck <> 7 then

To further enhance readability, you might also enclose the If Then criteria in parentheses. In addition to enhancing readability, parentheses help enforce the order of precedence when using a conditional statement; the parentheses ensure that these statements are properly evaluated before the script takes any further action. Although parentheses are not required, they also make it easier to see the conditions being evaluated:

If (intCheck >= -3) and (intCheck <> 7) then

Using Blank Spaces in Parameter Lists

System administration scripts, including many WMI scripts that either create new items or reconfigure existing items, require you to supply parameters in a specific order. In many cases, this poses no problem. For example, in this hypothetical script, it is clear that the first parameter is “Large”, the second parameter “Heavy”, and the third parameter “Red”:

objThing.Change("Large","Heavy","Red")

But what if you want to change only the third parameter? In a situation such as that, you typically leave the first two parameters blank. When you do that, you must include two commas, making “Red” the third parameter:

objThing.Change(,,"Red")

When constructing parameter lists such as this, it is a good idea to put blank spaces before or after commas. In the following script, it is difficult to tell how many commas precede the value True. This is important in this example because each comma represents a placeholder for a service parameter, and True must be the sixth item in the list.

Set objServiceList = GetObject("winmgmts:").InstancesOf("Win32_Service")
For Each strService in objServiceList
    Return = strService.Change(,,,,,True,,,,,,)
Next

The commas are easier to count when separated by blank spaces, as shown in the following script snippet:

Set objServiceList = GetObject("winmgmts:").InstancesOf("Win32_Service")
For Each strService in objServiceList
    Return = strService.Change( , , , , , True , , , , , , )
Next

Indenting Control Structures, Functions, and Procedures

Control structures are coding conventions that determine the flow of a script. For example, If Then is a control structure because the flow of the script hinges on this structure: If the condition is True, the script branches in one direction; if it is False, the script branches in another direction.

Indentation is commonly used with control structure statements such as If Then and For Next. For example, the syntax shown in the following snippet is valid, but it is difficult to understand what the script is doing without reading the code several times and mentally tracing the program flow.

For Each strEvent in objInstalledLogFiles
If (strEvent.EventCode >= "529") and (strEvent.EventCode <= "539") Then
If (strEvent.EventCode <> "538") Then
intEventTotal = intEventTotal +1
End If
End If
Next

Compare that with the revised code shown in the following script snippet. This is the same code, but each new control structure statement is indented four spaces.

For Each Event in InstalledLogFiles
    If (strEvent.EventCode >= "529") and (strEvent.EventCode <= "539") Then
        If (strEvent.EventCode <> "538") Then
            intEventTotal = intEventTotal +1
        End If
    End If
Next

The indentation makes it easier to understand when each block of code runs. In the preceding example, you can see that the first If Then section runs only for events with an EventCode of 529 through 539. The nested If Then statement, which adds up the total number of events, runs only if the EventCode does not equal 538. Thus, the script tallies all the events with the EventCode 529, 530, 531, 532, 533, 534, 535, 536, 537, or 539. An event with the EventCode 538 is not included in the tally.

It is also recommended that you use indentation in all major sections in your scripts, including functions and procedures. As a rule of thumb, use four spaces for each indent; in some cases, identifying passages indented less than four spaces is difficult, while indenting more than four spaces usually creates more empty space than needed. For example, indent a function as follows:

Function ConvertToMiles(ByVal sngKilometers)
    ConvertToMiles = sngKilometers * 2.2
End Function

When formatting control structure code, be sure that you align all the control structure statements (such as Do and Loop). In the following example, the If, ElseIf, Else, and End If statements are all aligned. Alignment clearly indicates that three possible values for the variable strState are being evaluated.

If (strState = "WA") Then
    intStateIDNumber = 1
ElseIf (strState = "OR") Then
    intStateIDNumber = 2
Else
    intStateIDNumber = 3
End If

In addition, use spaces rather than tabs when indenting statements. Some text editors use different tab formats than others; as a result, your carefully aligned code might look quite different when viewed by someone using a different editor.

Setting Statement Breaks

Text editors allow you to create lines of seemingly infinite length (for example, you can type 1,024 characters on a single line in Notepad); in theory, you could type an entire script — and a reasonably lengthy one at that — on a single line in Notepad. There might be some advantage to creating long lines of code; nevertheless, it is strongly recommended that you limit the length of any statement in a script to a maximum of 80 characters. This is suggested for at least two reasons.

  • 80 characters is the default setting for the command shell. Unless you change this setting, a statement longer than 80 characters forces you to scroll horizontally as well as vertically to view all parts of the statement. Without scrolling horizontally, the following statement is cut off:

    Set colServiceList = GetObject("winmgmts:").ExecQuery("Select * from
    32_Servi
    

    Statements that require a user to scroll back and forth are more difficult to read and modify.

    In addition, many system administrators use programs such as Edit.exe for editing scripts. With many of these editors, lines are always wrapped at the 80th character, regardless of the screen resolution.

  • Notepad is often used for printing scripts. Notepad is not designed to work with lines with more than 80 characters. In fact, lines that exceed 80 characters are printed in the following fashion:

    Set colServiceList = GetObject("winmgmts:").ExecQuery("Select * from
    Win32_Servi
    ce Where State = 'Stopped' And StartMode = 'Auto' ")
    

If a statement has more than 80 characters, pick a logical spot to split the line, insert a statement break, and then continue the statement on the next line:

Set colServiceList = GetObject("winmgmts:").ExecQuery("Select * from " _
    & "Win32_Service Where State = 'Stopped' And StartMode = 'Auto' ")

Limiting Each Line to a Single Statement

Although many scripting languages allow you to place multiple statements on a single line, this can be confusing to anyone reading your script and should be avoided. For example, using VBScript you can place multiple statements on a single line by separating each statement with a colon. The following code sample shows four statements included on a single line of code:

For i = 1 to 100 : intTotal = intTotal + i : Next : Wscript.Echo intTotal

This code is valid, but the purpose of the script is not readily apparent. In fact, although it appears far more complicated, this script merely adds the integers from 1 to 100 and then echoes the result. The purpose is more apparent when each statement is placed on a separate line and formatted according to previous recommendations:

For i = 1 to 100
    intTotal = intTotal + i
Next
Wscript.Echo intTotal

Avoiding Single-Line If Then Statements

VBScript allows you to place If Then statements on a single line, a shortcut method that can save a few keystrokes when writing a script. However, the time saved writing the script could result in additional time required for someone editing your script, because single-line If Then statements are often hard to read. For example, determining the purpose and possible variable values is difficult in this line of code:

If (intDept = 1) Then strDepartment = "Accounting" Else strDepartment = "Other"

By comparison, the multiline If Then statement in the following script snippet clearly shows the purpose of the script: check the value of the variable intDept, and use that result to set the value of the variable strDepartment.

If (intDept = 1) Then
    strDepartment = "Accounting"
Else
    strDepartment = "Other"
End If

By avoiding single-line If Then statements, you also facilitate using statement blocks such as If End If and Do Loop. One common scripting mistake is to start a statement block but then fail to end it, such as using an opening statement like For i = 1 to 100 without using a closing statement such as Next.

Note

Note

Preparing for Visual Basic .NET. Single-line If-Then statements are not allowed in Visual Basic .NET. In Visual Basic .NET, you must use multiple-line If-Then statements.

Limiting Your Scripts to a Single Language

To help other administrators who maintain or modify your scripts, limit each individual script to a single scripting language. Windows Script Files provide a way to use multiple scripting languages within a single script; for example, you can write a script that carries out one function in VBScript, a second function in JScript®, and a third in any other registered ActiveX® scripting language. Although this provides considerable flexibility, anyone who maintains your script must be proficient in each of these languages. Use additional languages only if you cannot perform those operations any other way.

Note

Note

Preparing for Visual Basic .NET. Windows Script Files can be used with Visual Basic .NET, but only if all the jobs specified in the file use the same scripting language. Windows Script Files using multiple languages are not allowed.

Creating Scripts That Are Easier to Read

Most people think of scripts as something that you run, not something that you read. However, scripts are actually read far more than you might think. Obviously the original author of the script needs to read the code from time to time; however, so do others charged with maintaining and modifying the code, people hoping to learn something about scripting from reading the code, and system administrators who want to know more about the script before they begin using it.

A readable script, using consistent, predictable, and complete code, is easier to maintain and adapt to other uses. Readability might not be important for scripts that are designed to be run once and then discarded. However, if you expect a script to be used repeatedly, and if you expect that other administrators might need to modify your script, readability becomes a concern.

To make your scripts easier to read and understand:

  • Use the proper case for VBScript keywords.

  • Phrase If Then statements positively.

  • Order If Then and Select Case statements logically.

  • Use Boolean expressions rather than literal values in conditional statements.

  • Use parentheses in mathematical expressions.

  • Specify default properties.

Using the Proper Case

VBScript is a case-insensitive language; this means that you can type keywords and statements in uppercase, lowercase, or any combination without generating a syntax error. For example, the four case styles shown in the following are all syntactically equivalent:

wsCRIpt.EchO "This is a valid VBScript statement."
wSCRIPt.eCHo "This is a valid VBScript statement."
WSCRIPT.ECHO "This is a valid VBScript statement."
Wscript.Echo "This is a valid VBScript statement."

Although the four styles are syntactically equivalent, some are definitely easier to read than others, and the combination of the four different styles results in a document that is difficult to read. To enhance readability, type VBScript keywords, statements, and other elements using Pascal casing. To use Pascal casing, begin each individual word with an uppercase letter, and type remaining letters in lowercase. This is not only easier to read but also helps distinguish the VBScript elements from the rest of the text.

In the following script snippet, Pascal casing is used to distinguish keywords from variables and constants:

Const MANAGER_JOB_TITLE_ID = 100
Select Case
Case strDepartment = "Accounting"
    If intJobTitle = MANAGER_JOB_TITLE_ID Then
        intRatingScale = 1
    Else
        intRatingScale = 2
    End If
Case strDepartment = "Administration"
    intRatingScale = 1
Case Else
    intRatingScale = 2
End Select

Phrasing If Then Statements

Compared with programming languages such as C and C++, the syntax of scripting languages is almost conversational. Scripting languages allow you to write your script in a fashion somewhat similar to ordinary speech. You should take advantage of this when writing scripts and try to mimic typical conversational patterns and syntax whenever possible. This makes your scripts easier to read and understand because the code appears more like ordinary language than computer programming.

Note

Note

Brian Kernighan, author of a number of books on programming style and programming conventions, refers to this as the “telephone test.” He believes you should attempt to read your code to someone over the telephone. If the person on the other end does not understand what you are talking about, then you need to rewrite your code.

For example, consider the phrasing of If Then statements in your scripts. In conversation, people generally phrase these statements positively. Suppose that stores in the state of Washington charge a special sales tax to residents of the state but not to residents of other states. If you are a store owner in Washington, you would likely to explain your store’s sales tax policy by saying, “If the customer is from Washington state, then we charge them sales tax. Otherwise, we do not.”

Your If Then statements should use a similar syntax. For example, the following script uses valid VBScript syntax but is awkward to read (try reading it out loud):

If (Not State = "WA") Then
    SngSalesTax = 0
End If

A grammatical construction such as If Not State Equals Washington causes confusion. Instead of testing for the negative condition, test for the positive condition, as shown in the following snippet. Read this script aloud, and notice that it sounds less awkward.

If (State = "WA") Then
    SngSalesTax = .065
End If

Or you might use the does not equal (<>) operator:

If (State <> "WA") Then
    SngSalesTax = 0
End If

Using Boolean Expressions in Conditional Statements

When checking the value of a Boolean (True/False) expression, script writers often use a syntax such as this:

If blnIsUSCitizen = True Then
    Wscript.Echo "This person is a citizen."
Else
    Wscript.Echo "This person is not a citizen."
End If

On the surface, this statement appears to pose no problems. In reality, however, a statement like this can return the value False even when the expression is actually True. This is because VBScript sets the value of True to –1, while most programming languages set the value of True to 1. As a result, the following situation can occur:

  • Someone saves the value 1 (True) to a database using a Visual Basic program.

  • Someone else uses VBScript to check the value. The statement If blnIsUSCitizen = True can be translated to read, “If blnUSCitizen equals –1” with –1 representing the value of True in VBScript.

  • Because the value in this case is actually 1 rather than –1, the script decides that blnIsUSCitizen is False, even though it is actually True.

To avoid this problem, use syntax similar to that shown in the following script snippet. In this case, the statement “If blnIsUSCitizen Then” is used as the condition. This statement translates as, “If blnIsUSCitizen equals anything other than 0.” Because 1 is not 0, the condition is correctly interpreted as being True.

If blnIsUSCitizen Then
    Wscript.Echo "This person is a citizen."
Else
    Wscript.Echo "This person is not a citizen."
End If

By contrast, False is always assigned 0. When you check for a False value, you can look for either False or for 0. For the sake of consistency, however, it is generally best to test for False as shown in the following snippet:

If blnIsUSCitizen = False Then
    Wscript.Echo "This person is not a citizen."
Else
    Wscript.Echo "This person is a citizen."
End If

Using Parentheses in Mathematical Expressions

Parentheses serve two purposes in mathematical equations: they force the order of arithmetic precedence, and they enhance readability. These two functions are illustrated in the following script snippet. This script works as expected: It multiplies the regular price of an item by the tax rate, multiplies the standard discount rate by a preferred customer rate, and then subtracts the total discount from the total regular price. However, the equation is somewhat difficult to read, and the fact that it works is, in part, an accident: The different terms and operators used in the equation just happen to be arranged in a way that respects the order of precedence.

RegPrice = 100
Discount = 5
SalesTax = 1.05
PreferredCustomer = 2
Wscript.Echo RegPrice * SalesTax - Discount * PreferredCustomer

Although the equation works as expected, someone editing the script might have difficulty understanding how the calculation works. If so, he or she might try to group the items within parentheses. If those parentheses are misplaced, the equation yields unexpected results. For example, one placement produces a correct answer, but another placement produces an incorrect answer:

  • Correct: (100 * 1.05) – (5 * 2) = 95

  • Incorrect: 100 * (1.05 – 5) * 2 = –790

To avoid problems such as this, always use parentheses when performing calculations that have the following characteristics:

  • Have three or more elements

  • Use more than one type of operator (for example, an equation that includes both addition and division)

A revised version of the script, which groups the items into logical sets, is shown in the following snippet:

RegPrice = 100
Discount = 5
SalesTax = 1.05
PreferredCustomer = 2
Wscript.Echo (RegPrice * SalesTax) - (Discount * PreferredCustomer)

Specifying Default Properties in Statements

Automation objects often have default properties associated with them. A default is simply the property that is used unless a different property is specified. For example, in WSH the Arguments object has the default property Item. In VBScript, you can omit the property name when working with the default property of an item. Thus, you can write the following script to cycle through the arguments passed to that script:

Set objArgs = WScript.Arguments
For i = 0 to objArgs.Count - 1
     WScript.Echo objArgs(i)
Next

Although VBScript allows you to omit default properties, it is a good idea to always include the default property name as part of your script. This makes your code easier to read and understand, especially if you are working with an automation object whose default property is not widely known. To ensure that there is no doubt as to which property is being employed, you should use code similar to this, which clearly specifies the default property Item:

Set objArgs = WScript.Arguments
For i = 0 to objArgs.Count - 1
   WScript.Echo objArgs.Item(i)
Next

Note

Note

Preparing for Visual Basic .NET. Default properties are not supported in Visual Basic .NET. In Visual Basic .NET, you must always specify the property name.

Commenting Scripts

Comments provide clarification or additional information about a script and the script’s purpose. Comments serve a function similar to that of a narrator in a movie: The narrator tells you what the characters in the movie are thinking; comments tell you what the script writer was thinking when he or she wrote the code. Without comments, readers must guess at the script’s purpose and why it uses one approach rather than another. Comments improve a reader’s understanding of the script.

It is generally a good idea to comment all scripts except scripts that are used once and then discarded. At a minimum, you should comment the following:

  • The script itself, in the form of a script header.

  • Procedures and functions.

  • Large control structures that are not part of a procedure or function. For example, if your script connects to a directory service, comment the section of the script where you actually connect to the directory service, even if it is not encapsulated in a function or procedure.

  • Any line of the script that needs clarification. When in doubt, it is better that a script have too many comments than too few.

Adding comments

A good narrator tries to avoid the obvious and to tell you only what you might not be able to determine for yourself. If the main character in a movie is seen walking along a river, the narrator will rarely say something like, “And then the main character walked along the river.” Instead, the narrator might tell you why the main character is walking along the river, but only if the reason behind this might not be obvious, and only if this narration helps to clarify what is happening.

Comments should be added only in similar circumstances; that is, only if the reason for using the code is not obvious, and only if the comment helps clarify what happens within the script. You might consider these guidelines when adding comments to a script:

  • Comments should describe the general purpose of the script. Comments need not provide a step-by step description of what happens in the script and should not provide a primer in scripting; someone who does not understand what the Wscript.Echo method does should not be editing your script. Reserve comments for those items that need further clarification. Commenting every statement leads to two problems. First, you need to write the script twice — once for each statement and again for each comment. Second, you risk obscuring the important comments if you include too many unneeded comments. The following type of comment should be avoided:

    ' Echo the date
    Wscript.Echo Date
    

    In addition, be careful not to editorialize when commenting the code. Comments should explain what the code does, not offer judgments as to the quality of that code. Raise quality issues in a different forum. The following type of comment does little to assure the reader that this is a script worth running:

    ' Echo the date even though it seems silly to echo the date at this point
    Wscript.Echo Date
    
  • Comments are not a solution for a poorly constructed script. If a section of your script is too complicated to summarize in a few sentences, consider rewriting that section. As a general rule, well-written code requires minimal commenting. If you find yourself having to explain over and over why you wrote a particular section of code in a particular way, you might stop and think about whether there is a better way to carry out the same function.

  • Use complete sentences, and proofread for spelling and grammar. Do not add terse and cryptic comments to your code. A comment such as “Get user info” adds no value. Instead, use a comment that gives the reader useful information. The following comment makes it clear user information is being retrieved:

    '    Retrieve the account name, department, and job title for each user.
    

    You should also ensure that comments are grammatically correct and spelled correctly. Poor grammar and spelling not only make the code harder to read, but do little to inspire confidence in your professionalism, and to offer assurance that the code has been properly checked and tested.

  • Write comments as you write the script. Although it is customary to write comments after the code is complete, this approach can lead to the following problems:

    • Finding time to go back and add the comments is difficult. After a script is working, it is usually more important to begin using that script than it is to delay its implementation while you add comments. If you save comments until the end, you are less likely to add them.

    • You might forget what the code does. Comments not only help other people understand your code, they also help you understand your own code. For example, if you copy a procedure from another source and fail to comment it, you might not remember what the procedure does, and why you included it.

    • Comments added after a script is written are often included as an afterthought and do little to improve the readability of the script. Comments that do not enhance the clarity of the code can be worse than no comments; they can be a distraction to anyone trying to read and understand the script.

    Interestingly enough, some script writers actually write comments before writing code. This enables them to record their thoughts and forces them to construct a logical flow for the program before writing code on a line-by-line basis.

  • Comments should not contradict the script. Because comments clarify your script, ensure that the comments and the script do not conflict. For example, the following script snippet reveals a conflict between the comments and the script itself. According to the script, the variable strUserLocationIdentifier is based on the user’s home state; according to the comments, however, the value of this variable is based on the user’s home city:

    '* Set the location identifier for the user based on the user's home city
    If strUserHomeState = "WA" Then
        strUserLocationIdentifier = "Local"
    Else
        strUserLocationIdentifier = "Off-site"
    End If
    

    Contradictions like this often occur when a section of a script is modified but the accompanying comments are not. Any time you make a change in your script, make sure that the comments are still valid.

Adding Comments to a Script

Comments are statements that are ignored by the script host when the script runs. To ensure that the script host does not attempt to interpret a comment as a command, you must preface each comment with a comment delimiter. VBScript allows two delimiters: the single quotation mark (’) and the REM statement. For example, both of the following lines of code are valid comments:

' This is a comment using a single quote delimiter.
REM This is a comment using the REM statement as a delimiter.

Although most script writers use the single quotation mark, you might consider following the single quotation mark with an asterisk when commenting your code:

'* This is another comment.

This approach has two advantages over the single quotation mark alone. First, it makes the comment stand out from the rest of the text; a single quotation mark by itself can be difficult to see at times. Second, the single quotation mark is a valid VBScript character that has uses beyond delimiting comments. This can be a problem if you use an automated procedure to extract comments from a script; such a procedure might also retrieve items that are not comments. Because you are less likely to use ’* anywhere else in your script, it makes a good delimiter.

Always include comments on separate lines except for comments that accompany the declaration of a variable. Comments included on the same line as a VBScript statement (end-of-the-line comments) are valid but can be difficult to read. For example, compare the two commenting styles in the following script snippet; in the first half of the script, comments are placed on the same line as a VBScript statement, while in the second half of the script, the comments are placed on separate lines. Most people find it easier to quickly identify the comments used in the second half of the script.

On Error Resume Next
Set WshNetwork = WScript.CreateObject("WScript.Network")
WshNetwork.MapNetworkDrive "Z:", "\RemoteServerPublic" '* Map drive Z.
If Err.Number <> 0 Then '* Check to make sure the operation succeeded.
    Err.Clear
    Wscript.Echo "The drive could not be mapped."
End If

'* Map drive Z.
WshNetwork.MapNetworkDrive "Z:", "\RemoteServerPublic"

'* Check to make sure the operation succeeded.
If Err.Number <> 0 Then
    Err.Clear
    Wscript.Echo "The drive could not be mapped."
End If

Formatting Comments

In many scripting books, comments are set off from the rest of the text by use of a frame (typically a box drawn with asterisks). These frames make it easy to identify the comments; however, the frames themselves can be difficult to maintain. For example, consider the frame used to surround the comments in this script snippet:

'***************************************************************************
'*   This is a comment enclosed within a frame. These frames might look    *
'*   nice, but they can be difficult to maintain.                          *
'***************************************************************************

Although the frame might have a certain aesthetic appeal, if you edit the comment, the line lengths will change and the frame might look like this:

'***************************************************************************
'*   This is a comment enclosed within a frame. Frames might look    *
'*   nice, but they are difficult to maintain.                          *
'***************************************************************************

If you like the idea of clearly delineating comments, you might want to use only a top and bottom border instead of a complete frame, as shown in the following snippet. The comments are still easy to identify, but the frame requires less maintenance.

'**************************************************************************
'*   If you feel the need to include a frame of some kind, just use a
'*   border on the top and bottom. This is much easier to maintain.
'**************************************************************************

Creating Script Headers

Whenever you document something, it is a good idea to make clear what you are documenting; do not require readers to read the entire manual to determine whether this really is the information they are looking for. The same is true of scripts. If your scripts are shared with others, it is strongly recommended that you include information about the script, and that you include it at the very beginning of the script in the form of a script header.

Script headers are a collection of comments that explain the purpose and history of a script. Script headers detail such things as:

  • The name of the script.

  • The date the script was created.

  • The author of the script.

  • The purpose of the script.

  • A history of revisions made to the script.

For example, an organization might use the following standard script header:

'* Script name:   NewUsers.vbs
'* Created on:    8/15/2000
'* Author:        Ken Myer
'* Purpose:       Creates new user accounts and new Exchange 2000 mailboxes.
'*                Adds new users to security groups based on job title and
'*                department.
'* History:       Pilar Ackerman 10/15/2000
'*                Modified to reflect new area code.

Simply by reading the header, you can tell what a script does, when it was written, how often it has been modified, and who to contact if you have questions about the script or the script code.

Creating Function and Procedure Headers

Each function and procedure in a script should also have a header that describes the purpose of that function or procedure. Information typically included in a function or procedure header is shown in Table 18.6.

Table 18.6. Function and Procedure Headers

Header Item

Description

Name

Label applied to the function or procedure within the script. Follow the guidelines described in “Naming Scripts” earlier in this chapter.

Purpose

General explanation of the task performed by the function or procedure.

Arguments supplied

Data(usually in the form of variables) passed to the function or procedure.

Return value

Result returned from the function or procedure.

Function is called by

Name of the function or procedure that calls this section of the script. Omit if called from within the main body of the script.

Function calls

Name of any functions or procedures that are called from within this section of the script. Omit if no such calls are made. Both this header item and the preceding header item help you to trace program flow.

A sample function header might look like this:

'* Function Name:           CalculateTotalPay()
'* Purpose:                 Calculates total pay for an employee based on
'*                          current salary plus merit increase.
'* Arguments supplied:      intUserIDNumber, curCurrentSalary, intMeritRank
'* Return Value:            curRevisedSalary
'* Function is called by:   SalaryAdjustment
'* Function calls:          ConvertMeritRank

Creating Script Documentation by Using Comments

Scripts are often short pieces of code designed to carry out a single function. Because of this, formal documentation is not always required; the script comments usually provide sufficient information for anyone using the script. For longer scripts, however, it might be useful to provide separate documentation rather than requiring users to read through the script, searching for comments.

Well-commented scripts can actually help you write the documentation for your scripts. By including comments such as who wrote the script, when it was written, why it was written, and the purpose of procedures and functions, you already have much of the written documentation for the script as well. You only need to copy those comments from the script to the script documentation.

One way to create separate documentation for a script is to use an automated procedure to extract the comments. These comments can then serve as the basis for the written documentation. For example, the script shown in Listing 18.2 extracts comments by doing the following:

  1. Opens a script file (C:ScriptsSystemMonitor.vbs).

  2. Reads through each line of the script, looking for comments. These are readily identifiable because the script uses a unique identifier (’*) to delineate comments.

  3. Saves the line to a text file (C:ScriptsComments.txt) if the line is a comment. If the line is not a comment, the script proceeds to the next line of the script file. This process continues until all the lines in SystemMonitor.vbs are read and processed.

Example 18.2. Extracting Comments

 1 Const ForReading = 1
 2 Const ForWriting = 2
 3 Set objFSO = CreateObject("Scripting.FileSystemObject")
 4 Set objScriptFile = objFSO.OpenTextFile _
 5     ("c:scriptsService_Monitor.vbs", ForReading)
 6 Set objCommentFile = objFSO.OpenTextFile("c:scriptsComments.txt", _
 7     ForWriting, TRUE)
 8 Do While objScriptFile.AtEndOfStream <> TRUE
 9     strCurrentLine = objScriptFile.ReadLine
10     intIsComment = Instr(1,strCurrentLine,"'*")
11     If intIsComment > 0 Then
12         objCommentFile.Write strCurrentLine & VbCrLf
13     End If
14 Loop
15 objScriptFile.Close
16 objCommentFile.Close

Using Comments as a Debugging Aid

If you have ever written a lengthy report or memorandum, you might have found it useful to type notes to yourself as you worked on the document. For example, you might have typed something such as, “Need to add a section here about the budget problems from a year ago” or “These two paragraphs need to be rewritten.” Notes such as these are useful reminders and also serve as a sort of status report, letting you know what is and is not working, and how you are progressing.

Comments allow you to put this same type of status report in scripts still in development. For example, suppose you are working on a script and you encounter a problem. If you are unable to immediately solve the problem, insert a comment labeled BUG to indicate that this section of code needs further work. The next time you work on that script, search for BUG to locate any sections of the code that are not working properly. If you are able to fix the problem, remove the BUG comment.

For example, you might use the following comment labels:

  • BUG. Indicates code that is not working correctly.

  • INCOMPLETE. Indicates code that is not yet finished.

  • PERFORMANCE. Indicates code that works but whose performance might be improved.

  • REVIEW. Indicates lines of code that need further review to ensure they are working correctly.

The following script snippet illustrates comments used as a debugging aid.

'* BUG (DMS 3/10/02)    This should loop through all 100 sales districts,
'* BUG (DMS 3/10/02)    calling the function RetrieveInformation for each one,
'* BUG (DMS 3/10/02)    but it only does the first 90 sales districts and then
'* BUG (DMS 3/10/02)    stops.

For i = 1 to 100
    RetrieveInformation(i)
Next

The debugging comments in the preceding snippet include the reviewer’s initials and the date the comment was added. This facilitates communication between the script writer and the reviewer. In addition, it helps ensure that no unneeded comments are left in the finished code; a debugging comment added years earlier is unlikely to still be valid and should be deleted.

Removing Debugging Comments

To ensure that you remove all debugging comments from a script, you can use a second script to remove them. The script shown in Listing 18.3 removes debugging comments by doing the following:

  1. Opens the script C:ScriptsCreateUser.vbs.

  2. Reads each line in the file and checks for the presence of the string ’* BUG anywhere in the line. If the string is found, the line is discarded as a debugging comment. If the string is not found, the line is retained as part of the variable strSavedLines.

  3. Overwrites the existing version of the script using the variable strSavedLines, which contains all the non-debug lines in the script.

Example 18.3. Removing Debugging Comments

 1 Const ForReading = 1
 2 Const ForWriting = 2
 3 Set objFSO = CreateObject("Scripting.FileSystemObject")
 4 Set objTextFile = objFSO.OpenTextFile("C:ScriptsCreateUser.vbs", ForReading)
 5 Do While objTextFile.AtEndOfStream <> true
 6     strNextLine = objTextFile.Readline
 7     intCheckForBugComment = Instr(strNextLine, "'* BUG")
 8     If intCheckForBugComment = 0 Then
 9         strSavedLines = strSavedLines & strNextLine & VbCrLf
10     End If
11 Loop
12 Set objTextFile = objFSO.OpenTextFile _
13     ("c:scriptsCreateUser.vbs" , ForWriting)
14 objTextFile.Write strSavedLines
15 objTextFile.Close

Debugging and Troubleshooting Scripts

Debugging code can be a long and tedious process. This is especially true with scripts because most scripts are written in text editors that do include a debugger. However, you can employ several techniques to facilitate debugging and troubleshooting.

Note

Note

You can also use the Microsoft Script Debugger or third-party debugging tools to troubleshoot scripts.

Adding a Trace Routine to a Script

Trace routines allow you to track a program as it runs and help you identify lines of code that produce unexpected results. You can add a rudimentary trace routine to your scripts by having the script periodically report its status.

For example, you might echo the value of your variables each time those variables are changed in some way. This enables you to identify any spot in the script where a variable is not assigned the correct value (a common cause of run-time errors). Or you might display a message each time you enter a procedure or function. If a message does not appear, you know that the function or procedure was not called.

The following script projects the number of support calls to a help desk by doing the following:

  1. Requests the total number of calls for two weeks.

    Assume that there are 93 calls in the first week and 96 in the second week.

  2. Adds the number of calls for the first two weeks together.

    The total of calls for the first two weeks is 189.

  3. Multiplies the two-week total by 26, giving a projection of the total number of support calls for the next year (52 weeks).

  4. Divides the total number of calls by 8 support technicians to determine the anticipated number of calls each technician will need to handle in the coming year.

intWeekOneCalls = InputBox("Enter the number of support calls for Week 1:")
intWeekTwoCalls = InputBox("Enter the number of support calls for Week 2:")
intTotalCalls = intWeekOneCalls + intWeekTwoCalls
intProjectedNumberOfCalls = intTotalCalls * 26
sngCallsPerTechnician = intProjectedNumberOfCalls / 8
Wscript.Echo sngCallsPerTechnician

The script returns a value of 30,537 as the number of calls each technician can expect to handle. Unfortunately, the correct answer is 614.25.

To make matters worse, determining where the problem lies is difficult, because no error messages are returned, and both the scripting syntax and mathematical formulas appear correct.

One solution is to insert a trace routine that periodically reports the value of the variables used in the script. For example, the following revised script uses the Echo statement to display the value of a variable each time that value changes.

intWeekOneCalls = InputBox("Enter the number of support calls for Week 1:")
Wscript.Echo intWeekOneCalls
intWeekTwoCalls = InputBox("Enter the number of support calls for Week 2:")
Wscript.Echo intWeekTwoCalls
intTotalCalls = intWeekOneCalls + intWeekTwoCalls
Wscript.Echo intTotalCalls

intProjectedNumberOfCalls = intTotalCalls * 26
Wscript.Echo intProjectedNumberOfCalls

sngCallsPerTechnician = intProjectedNumberOfCalls / 8
Wscript.Echo sngCallsPerTechnician

When this script runs, you will see that the sum of the week 1 and week 2 totals (93 + 96) does not equal 189. Instead, VBScript returns the value 9396. Why? B intWeekTwoCalls ecause it is treating the two variants as string variables rather than integers. As a result, the variables are concatenated (93 & 96 = 9396) and not added (93 + 96 = 189). You have now located the problem.

To solve this problem, you can use the data type features in VBScript to cast these two variables as integers, as shown in the following code snippet:

intWeekOneCalls = InputBox("Enter the number of support calls for Week 1:")
intWeekTwoCalls = InputBox("Enter the number of support calls for Week 2:")
intTotalCalls = Cint(intWeekOneCalls) + Cint(intWeekTwoCalls)
Wscript.Echo intTotalCalls

intProjectedNumberOfCalls = Cint(intTotalCalls) * 26
Wscript.Echo intProjectedNumberOfCalls

sngCallsPerTechnician = Cint(intProjectedNumberOfCalls) / 8
Wscript.Echo sngCallsPerTechnician

Tip

Tip

You can also have your script periodically write trace information to a log file. This enables you to run the script without interruption and then later read the log file and determine whether the script ran as expected.

Incrementally Running a Script

Suppose you woke up this morning and discovered that your car would not start. How would you handle this situation? You could call a tow truck and have the car towed to the nearest mechanic. Or, if you are a do-it-yourselfer, you could immediately take the engine apart and begin looking for broken or worn-out items.

Of course, both those approaches might be a bit extreme; after all, what if the car is simply out of gas? Instead of taking the entire cart apart, you are likely to take a a step-by-step approach to trying to figure out what it wrong. For example, you might check whether there is gas in the gas tank or whether the battery is still charged. Most likely, your diagnosis and troubleshooting will involve incremental steps, identifying possible problems, and then investigating each one in turn.

This same technique can be used for debugging scripts. Rather than read through each line of code, trying to determine where the problem might be, you might instead run part of the script and then quit. This can help you determine the exact location where the problem occurs. For example, you might run half the script and then quit; if the script runs without errors, you can generally assume that the problem is in the second half of the script. You can then continue to run a selected portion of the script until you finally locate the problem.

To run only part of a script, insert a Wscript.Quit statement at the point where you want the script to stop running. For example, the following script snippet creates a new organizational unit and then adds a new user to it. To troubleshoot the script, first verify that the section that creates the organizational unit works as expected. To ensure that only this portion of the script is run, insert Wscript.Quit at the end of this section.

Set objRootDSE = GetObject("LDAP://RootDSE")
objDomain=rootDSE.Get("DefaultNamingContext")
strOUName = "Accounting"
Set objContainer = GetObject("LDAP://" & objDomain)
Set objOU = objContainer.Create("organizationalUnit", "OU=" & strOUName)
objOU.SetInfo

Wscript.Quit

strUserAccountName = "rsmith"
strSAMAccountName = "rsmith"
set objUser = objOU.Create("user", "cn=" & strUserAccountName )
objUser.Put "samAccountName", strSAMAccountName
objUser.SetInfo

After creating the organizational unit, the script stops before creating any user accounts. If the organizational unit is not created, you can then focus your debugging efforts on that section of the script before moving on to the section that creates user accounts.

The Wscript.Quit method also allows you to check the syntax of your script without actually running that script. To check syntax without running the script, insert Wscript.Quit as the first line. When you run the script, the script host checks the syntax and reports any errors. If the script host discovers no syntax errors, it attempts to run the script. Because the first command is Wscript.Quit, it runs only that line and then stops.

Turning Off Error Handling as a Debugging Tool

The primary advantage of error handling is that it allows a script to proceed from start to finish without displaying any error messages, and without abruptly terminating if an error condition occurs. At the same time, the primary disadvantage of error handling is that error handling allows a script to proceed from start to finish without displaying any error messages, and without abruptly terminating if an error condition occurs. After all, without seeing an error message, you will not know where the error occurred in your script. In fact, you might not even know that an error did occur.

Because of this, it is often a good idea to turn off error handling when writing and debugging a script. For example, the following script sample creates a new organizational unit and then adds user to it. If the script is run by a user who does not have the right to create a new organizational unit, nothing happens: neither the new organizational unit nor the new user is created, nor does an error message appear.

On Error Resume Next
Set objRootDSE = GetObject("LDAP://RootDSE")
ObjDomain = rootDSE.Get("DefaultNamingContext")
strOUName = "Accounting"
Set objContainer = GetObject("LDAP://" & objDomain)
Set objOU = objContainer.Create("organizationalUnit", "OU=" & strOUName)
objOU.SetInfo
strUserAccountName = "rsmith"
strSAMAccountName = "rsmith"
set objUser = objOU.Create("user", "cn=" & strUserAccountName )
objUser.Put "samAccountName", strSAMAccountName
objUser.SetInfo

This script is difficult to troubleshoot because it is syntactically correct; under the right circumstances, this script works every time. In this case, the script is correct; the failure occurs not because of a defect in the code, but because the user is not authorized to create a new organizational unit. This problem can be very difficult to identify; when a script fails to work, the usual assumption is that there is something wrong with the script code.

To catch run-time errors such as this, comment out the On Error Resume Next statement. When you run the script without error handling enabled, you immediately receive an error message such as the following:

C:ScriptsNewOU.vbs(6, 1) Microsoft VBScript runtime error: Object required:
'rootDSE'

This error message tells you that the script failed because it could not return a reference to the rootDSE object. This means that either the domain does not exist or the user is not authorized to bind to the domain root. With error handling enabled, this problem is more difficult to diagnose.

Testing Scripts

When you buy a new car, you have some assurance that you will not turn the key and have the back end of the car burst into flames. This is because cars are thoroughly tested before they are placed into production and before they are sold to consumers. No car maker would last long if it simply slapped a new car together and then sold it to consumers on a “use at your own risk” basis.

Admittedly, it is unlikely that any of your scripts will burst into flames the first time they are run. On the other hand, scripts are powerful tools for system administration; this means they can do a number of good things if written correctly, and a number of bad things if written incorrectly. To make sure your scripts are working properly, and to make sure there are no surprises when you use them in a production environment, always test your scripts before deploying them. Although a complete guide to software testing is beyond the scope of this chapter, you can benefit from following these general guidelines:

  • Test in iterative fashion. Do not wait until your script is finished before you begin testing it. For example, suppose you have a script that:

    • Reads in a Microsoft Excel spreadsheet containing information about new users in your domain.

    • Creates new user accounts based on that information.

    • Creates new Microsoft Exchange mailboxes based on that information.

    • Adds the new users to the appropriate security groups.

    • Creates custom logon scripts based on the security groups the users belong to.

    If the script is written in this same order, you should test the code as soon as the procedure to read the Excel spreadsheet is written. If that procedure does not work, the rest of the script fails as well. After you have written and debugged the procedure for reading in the Excel spreadsheet, move on to the procedure for creating new user accounts.

    In addition, test any new statements added to your script before the script is released. Never assume that the new statements work and thus require no testing.

  • Cover all possibilities when testing. Always test whether the script can be made not to work. For example, suppose your script requests that the user enter an ID number. What happens if the user enters an ID number that does not exist, or an ID number that contains an invalid character? What happens if the user doesn’t even enter an ID number but merely presses ENTER? What happens if the user clicks Cancel instead? Make sure your testing accounts for all these possibilities.

  • Verify that the output you receive is correct. Sometimes the problem with a script is subtle and difficult to detect. For example, suppose you have a script that retrieves user information from Active Directory. You enter a user name, and the script responds by showing you the person’s address, phone number, and job title. Do not assume that the script is working just because you received information of some kind; what if the script returns the address, phone number, and title of someone other than the person requested? Double-check the returned information against some other source, such as the form originally filled out by the employee.

  • Have someone else test your script. As much as possible, you should avoid testing scripts that you write, simply because you know what kind of input the script expects at any given time. As a result, you tend to do the expected. If the script requires you to enter a drive letter, you are likely to type a valid drive letter using a valid format (for example, C:). Someone who does not know how the script works might type something else, perhaps an invalid drive (6:), or the wrong format (C). This provides a more realistic test of how the script will actually be used.

  • Test your scripts on multiple platforms. You should test your scripts on each platform used in your organization. For example, the Microsoft® Windows XP® family includes a different version of WSH than the version that shipped with Windows 2000. Unless you upgrade WSH on the Windows 2000–based computers, you might discover that scripts that work on Windows XP development computers do not work anywhere else. Likewise, there are differences between WMI on Microsoft® Windows NT® and WMI on Windows 2000 or the Windows XP family. Do not assume that just because each of these platforms contains a WMI class named Win32_OperatingSystem the properties and methods of these classes are identical. Because of differences between platforms, a script that works on one platform might not work on others.

    Because of such differences, you might need to design scripts only for a specific platform. In that case, your script should include code that verifies that it is running on the correct platform. The WMI script shown in Listing 18.4 retrieves the operating system platform.

Example 18.4. Retrieving the Operating System Platform

1 strComputer = "."
2 Set objWMIService = GetObject("winmgmts:" _
3     & "{impersonationLevel=impersonate}!\" & strComputer & "
ootcimv2")
4 Set colOS = objWMIService.ExecQuery _
5     ("SELECT * FROM Win32_OperatingSystem")
6 For Each objOS in colOS
7     Wscript.Echo objOS.Caption & ", " & objOS.Version
8 Next

If you do not have WMI installed on all the computers in your organization, the VBScript shown in Listing 18.5 can query the registry and return the operating system.

Example 18.5. Querying the Registry and Returning the Operating System

1 Set WshShell = WScript.CreateObject("WScript.Shell")
2 Set WshSysEnv = WshShell.Environment("SYSTEM")
3 strOS = WshSysEnv("OS")
4 strVersionNumber = WshShell.RegRead("HKLMSoftwareMicrosoft" _
5     & "Windows NTCurrentVersionCurrentVersion")
6 strBuildNumber = WshShell.RegRead("HKLMSoftwareMicrosoft" _
7     & "Windows NTCurrentVersionCurrentBuildNumber")
8 strActualOS = strOS & ", " & strVersionNumber & ", " & strBuildNumber
9 Wscript.Echo strActualOS
  • Test your scripts on all relevant computers. If your script must run on computers with different hardware, make sure you test it on each of those hardware platforms. Depending on the task you are trying to perform, differences in computer hardware, such as processor speed, memory, hard disk speed, and network adapters can affect the operation of the script. Scripts typically do not depend on hardware, but it is better to test for the exceptional situation.

  • Record problems you encounter and their resolution. Keep track of common coding problems and how they are resolved; it is likely that someone else might encounter this same problem. Methods of recording this information range from the formal, such as storing problems and solutions in a database, to the informal, such as creating a Frequently Encountered Problems Web page.

..................Content has been hidden....................

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