s & .NET w



a z i n e ’s

Guide to

Automating Administrative

Don Baker, Don Jones, Lary Lai, Dick Lewis, Mike Otey, John Savill, Bob Wells


Windows & .NET Magazine’s Guide to Automating Administrative Tasks with VBScript
By Don Baker, Don Jones, Larry Lai, Mike Otey, John Savill, Bob Wells

A Division of Penton Media

Copyright 2002 Windows & .NET Magazine All rights reserved. No part of this book may be reproduced in any form by an electronic or mechanical means (including photocopying, recording, or information storage and retrieval) without permission in writing from the publisher. It is the reader’s responsibility to ensure procedures and techniques used from this book are accurate and appropriate for the user’s installation. No warranty is implied or expressed.

About the Authors
Don Baker (dbaker@internosis.com) is a consulting engineer with Internosis, an e-business consultancy in Arlington, Virginia. He is an MCSE and an MCP and specializes in Systems Management Server and automated software deployment. Don Jones (http://www.braincore.net) is a founding partner of BrainCore.Net. He is the author of Application Center 2000 Configuration and Administration (Hungry Minds). Michael Otey (mikeo@teca.com) is senior technical editor for Windows & .NET Magazine and president of TECA, a software-development and consulting company in Portland, Oregon. He is coauthor of SQL Server 2000 Developer’s Guide (Osborne/McGraw-Hill). John Savill (john@savilltech.com) is a qualified consultant in England and an MCSE. He is the author of The Windows NT and Windows 2000 Answer Book (Addison Wesley). Bob Wells (bobwells@winnetmag.com) is a contributing editor for Windows & .NET Magazine. He is a programming writer at Microsoft, where he is contributing to a new System Administration Scripting Guide that Microsoft will include in its next Windows Server resource kit.


Table of Contents
Chapter 1: Scripting 101: Declaring and Initializing Variables . . . . . . . . . . . . . 1
VBScript Basics . . . . . . . . Declaration and Initializing VBScript Directives . . VBScript Variables . . . Variable Initialization . Constant Definitions . . Demo.vbs End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 4 4 6 6 6 7

Chapter 2: Scripting 101: Working with Objects . . . . . . . . . . . . . . . . . . . . . . . 9
Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Objects vs. Utilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 What Is an Object? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Creating Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 CreateObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 GetObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

Chapter 3: VBScript Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Chapter 4: Scripting Command-Line Switches . . . . . . . . . . . . . . . . . . . . . . . . 21
Adding Flexibility with Switches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Handling Omitted Switches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

Chapter 5: Adding Users in Bulk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Getting Started . . . Writing the Script . The Work Begins . Do It with a Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 27 29 31

Chapter 6: VBScript to Generate a List of User Last Logon Times for a Domain . . 33 Chapter 7: VBScript to Datastamp Log Files . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Chapter 8: Simplify Win2K Desktop Deployment . . . . . . . . . . . . . . . . . . . . . . 37
The Deployment Environment . . . . . . . . Create a Hard Disk Image . . . . . . . . . . . Write a FinalSetup.vbs Script . . . . . . . . . Prepare the Disk Image . . . . . . . . . . . . Unattended . . . . . . . . . . . . . . . . . . GuiUnattended . . . . . . . . . . . . . . . GuiRunOnce . . . . . . . . . . . . . . . . . UserData . . . . . . . . . . . . . . . . . . . . Identification . . . . . . . . . . . . . . . . . Networking . . . . . . . . . . . . . . . . . . Clone the Master Image to a Workstation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 37 38 42 43 43 44 44 44 44 45


Chapter 1

Scripting 101: Declaring and Initializing Variables
By Bob Wells
The structure of a script consists of three sections: declarations and initializations, the body of the script, and function and subroutine definitions. In this chapter, I examine the declaration and initialization section in detail. Before we start, you need to know some VBScript basics to ease your transition to Windows Script Host (WSH).

VBScript Basics
As with any other programming or scripting language, VBScript has some general ground rules that apply to every script. At first glance, these rules might not appear to be significant, but knowing these language tidbits can often shave a few minutes off a frustrating debugging exercise. The first ground rule is that VBScript isn’t case-sensitive. For example, declaring a variable as strTempFile and later referencing it as STRtEMPfILE is perfectly legal. Likewise, VBScript statements, function names, and subroutine names are case-insensitive. Despite this flexibility, I encourage you to pick a case variation that suits your needs and stick with it. Although VBScript is case-insensitive, some situations (e.g., comparing two string values) require you to be case-conscious. You also need to be case-conscious when you use Active Directory Service Interfaces (ADSI) namespace identifiers (e.g., LDAP to specify Lightweight Directory Access Protocol, WinNT to specify Windows NT, NDS to specify NetWare 4.x, NWCOMPAT to specify NetWare 3.x). You must key ADSI namespace identifiers according to the ADSI specification, or a runtime error will occur when your script attempts to bind to an ADSI namespace. (For more information about ADSI namespace usage, read the Active Directory Programmer’s Guide available at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netdir/ad/ using_active_directory.asp. The second VBScript rule is that you can use either an apostrophe (‘) or a Rem statement to include comments in a script. I use both styles in Listing 1’s demo.vbs script. You can insert comments on a separate line or at the end of a line of code, but you can’t insert comments at the end of a code line that contains a line-continuation marker (_). (To download an executable version of Listing 1, go to http://www.winnetmag.com/files/5505/5505.zip.) The third rule is that VBScript doesn’t care about extra white space. Adding extra spaces or blank lines can often improve the code’s readability. VBScript ignores the blank lines and the extra spaces between the variable name and the assignment operator (=). The fourth rule is that although VBScript doesn’t impose a maximum line length, breaking long lines into multiple short lines can improve the readability of a script. In VBScript, you can use the line-continuation marker to let a statement or line of code span multiple lines. I often use the linecontinuation marker when I initialize dynamic arrays (e.g., at callout A in Listing 1) or to pass a


Guide to VBScripting

long string to WScript’s Echo method (WScript.Echo), which I demonstrate several times in the body of the demo.vbs script. When you use the line-continuation marker, you need to include one space immediately before the underscore, or you will encounter an error with some statement types. On the flip side of rule 4 is the fifth VBScript ground rule, which lets one line of code contain multiple statements. In VBScript, you can use a colon (:) to separate multiple statements within a line of code. In the code line
Dim strText: strText = “WSH ROCKS!” : WScript.Echo strText

statement 1 is Dim strText, statement 2 is strText = “WSH ROCKS!”, and statement 3 is WScript.Echo strText. Although VBScript supports this capability, I generally don’t use it. The sixth VBScript rule is that you must terminate each line of VBScript code with a new-line character. Pressing Enter automatically inserts the new-line character. VBScript doesn’t use the visible line terminator (;) that JScript and Perl use. The seventh and final ground rule I want to establish governs VBScript identifiers (e.g., variables, constants, function names, subroutine names). Identifiers can’t exceed 255 characters in length and must begin with an uppercase or lowercase letter of the alphabet.

Listing 1
’ * ‘ demo.vbs ‘ * ‘ Declaration and Initialization Section ‘ * Rem *** Script Directives *** Option Explicit On Error Resume Next Rem Dim Dim Dim *** Variable Declarations *** objFileSystem, objShell, objLogFile, objTempFile arrStrings, intReturnCode strBuffer, strLogFile, strTempFile

Rem *** Constant Definitions *** Const OpenFileForReading = 1 Rem *** Variable Initialization *** intReturnCode = 0 strBuffer = “” strLogFile = “c:\” & Split(WScript.ScriptName, “.”)(0) & “.log” strTempFile = “c:\temp.txt” arrStrings = Array(“abcdefghijklmnopqrstuvwxyz”, _ “ABCDEFGHIJKLMNOPQRSTUVWXYZ”, _ “the quick brown fox jumps over the lazy dogs back”) ‘ Script Body Section ‘ * ‘ Using If-Then-End If to demonstrate the On Error Resume Next statement. If Err.Number Then WScript.Echo “Bob’s Friendly Error Message” & vbNewLine & _ “Error Number: “ & Err.Number & vbNewLine & _ “Error Description: “ & Err.Description Err.Clear End If ‘ Create scripting runtime FileSystemObject. ‘ Create TextStream object for script’s log file. ‘ Write starting line to log file.




Chapter 1 Scripting 101: Declaring and Initializing Variables


Listing 1: continued
Set objFileSystem = CreateObject(“Scripting.FileSystemObject”) Set objLogFile = objFileSystem.CreateTextFile(strLogFile, TRUE) objLogFile.WriteLine(Now & “:” & vbTab & WScript.ScriptFullName & “ Started.”) ‘ Create WSH Shell object. ‘ Run external command redirecting commands output to temporary file. ‘ Check for error condition and write appropriate message to log file. Set objShell = WScript.CreateObject(“WScript.Shell”) intReturnCode = objShell.Run(“cmd /C ipconfig.exe >” & strTempFile, 0, TRUE) If Err.Number Then objLogFile.WriteLine(Now & “:” & vbTab & Err.Number & “: “ & Err.Description) Err.Clear Else objLogFile.WriteLine(Now & “:” & vbTab & “Run completed successfully.”) End If ‘ Create TextStream object to read in c:\temp.txt file, which the run command created. ‘ Read entire file into buffer using the TextStream objects ReadAll method. ‘ Check for error and write appropriate message to log file. ‘ Close TextStream object. Set objTempFile = objFileSystem.OpenTextFile(strTempFile, OpenFileForReading) strBuffer = objTempFile.ReadAll If Err.Number Then objLogFile.WriteLine(Now & “:” & vbTab & Err.Number & “: “ & Err.Description) Err.Clear Else objLogFile.WriteLine(Now & “:” & vbTab & “Open and read “ & strTempFile & _ “ completed successfully.”) End If objTempFile.Close ‘ Display the buffer contents that are the redirected result of the run ‘ command. WScript.Echo strBuffer ‘ Delete c:\temp.txt file. ‘ Check for error and write appropriate message to log file. objFileSystem.DeleteFile(strTempFile) If Err.Number Then objLogFile.WriteLine(Now & “:” & vbTab & Err.Number & “: “ & Err.Description) Err.Clear Else objLogFile.WriteLine(Now & “:” & vbTab & “Delete “ & strTempFile & _ “ completed successfully.”) End If ‘ Write closing line to log file and close log file. objLogFile.WriteLine(Now & “:” & vbTab & WScript.ScriptFullName & “ Finished.”) objLogFile.Close ‘ Create TextStream object to read in log file. ‘ Read entire file into buffer using the TextStream objects ReadAll method. ‘ Close TextStream object. Set objLogFile = objFileSystem.OpenTextFile(strLogFile, OpenFileForReading) strBuffer = objLogFile.ReadAll objLogFile.Close ‘ Display the buffer’s content, which is the log file in this case. WScript.Echo strBuffer ‘ Delete the log file, release object references, and exit script. objFileSystem.DeleteFile(strLogFile) Set objShell = Nothing Set objFileSystem = Nothing WScript.Quit(0) ‘ * ‘ * demo.vbs end


Guide to VBScripting

Declaration and Initialization
The primary elements that make up declaration and initialization are script directives, variable declarations, variable initialization, and constant definitions. All these elements (with the possible exception of initialization) are optional. However, using these language features can help you produce scripts that are easier to debug and maintain.

VBScript Directives
VBScript includes two directives or statements that significantly affect the runtime behavior of scripts. The two directives are the Option Explicit statement and the On Error Resume Next statement. The Option Explicit statement requires that you declare (i.e., define) all script variables via a VBScript Dim statement before you use the variables in an expression. When you use Option Explicit, the declaration requirement applies throughout the entire script, including variables in the main body of the script and variables in user-defined functions and subroutines. You must include the Option Explicit statement before any other statements in your script. Therefore, you always find Option Explicit at the top of scripts that use it. The benefit of using Option Explicit is that an error occurs whenever the VBScript interpreter encounters an undefined variable during script execution. Using Option Explicit helps you isolate typographical mistakes and variables with incorrect scopes (a scope defines the visibility of a variable to other parts of the script such as functions and subroutines). The On Error Resume Next statement controls how a script notifies you when a script encounters a runtime error. When a script runtime error occurs and the script doesn’t use the On Error Resume Next statement, WSH displays a dialog box containing the execution error information and aborts the script. Figure 1 shows an example WSH Script Execution Error dialog box. Including an On Error Resume Next statement in a script effectively tells VBScript to continue execution despite a runtime error: VBScript’s built-in Err object is set with the corresponding error information, and the script continues. The On Error Resume Next statement lets you trap errors and respond to them programmatically. You can choose to display a friendly, descriptive message or write the error information to a log file. Figure 2 shows an example customized error dialog box. Using On Error Resume Next to help trap errors is important. Suppose I comment out (i.e., add Rem to the beginning of) the line that declares variable intReturnCode at the beginning of demo.vbs:
Rem Dim arrStrings, intReturnCode

If I execute this modified script, the script will run to completion, but it might not produce the desired result. The script won’t display the standard WSH Script Execution Error message on the console; instead, the script will produce Figure 2’s custom error dialog box, which states that a script variable is undefined. The If-Then-End If statement at callout B in Listing 1 acts as a rudimentary error-handling routine that echos the contents of the VBScript Err object’s Number and Description properties. After you click OK to acknowledge the error dialog box, the script continues because the On Error Resume Next directive tells the script to proceed despite the error. (If I commented out the On Error Resume Next directive and ran the script again, Figure 1’s WSH Script Execution Error dialog box would display. More important, the script would abort as soon as I clicked OK in Figure 1’s dialog box; I’d have no control over the remainder of the script. The

Chapter 1 Scripting 101: Declaring and Initializing Variables


only benefit of not using On Error Resume Next is that the error information in the WSH Script Execution Error dialog box can be more descriptive than the information in the Err object’s Description property. Figure 1 not only includes the name of the undefined variable, it also provides the specific line number for the error. In Figure 2, the error description simply states that the script tried to use an undefined variable.)

Figure 1
Example WSH Script Execution Error dialog box

Figure 2
A customized error dialog box

Why does an error occur? In demo.vbs, I initialize intReturnCode to 0 in line 19 of the script, a few lines before callout A. But because I specified the Option Explicit directive at the start of the script and commented out intReturnCode’s declaration statement, an error occurs when the script tries to execute the line that initializes the undeclared intReturnCode to 0. I can eliminate this error and other potential variable declaration errors by removing the Option Explicit statement. However, a better way to fix the problem is to define the undeclared variable (in this case, remove the Rem from the declaration statement):
Dim arrStrings, intReturnCode


Guide to VBScripting

Unlike Option Explicit, which affects the entire script, On Error Resume Next applies to only the portion of the script (e.g., the script’s body, a function, a subroutine) that defines it. On Error Resume Next’s narrow scope lets you control precisely when and where it’s in effect. (VBScript doesn’t support Visual Basic’s—VB’s—On Error Goto Label construct.) When you use the Option Explicit and On Error Resume Next directives correctly, they can make VBScript routines more robust and user-friendly and play an important role in debugging scripts.

VBScript Variables
Earlier, I said declaring variables in VBScript is optional—and it is. However, engaging in the exercise of defining your script’s variables can be well worth your time and effort for several reasons, such as debugging and script maintenance. Declaring your script’s variables also requires you to consider the environment and objects with which your script interacts, which can result in betterdesigned scripts that do what you intend them to do. A VBScript variable is simply a named memory location that contains some value. The value might be a number, a string, or some other data type that the language supports. The variable name is the mechanism through which you assign a value to a variable. Declaring VBScript variables identifies the names that you’ll use to refer to the data your script manipulates. You use the Dim statement to declare VBScript variables. In demo.vbs, the variable declarations follow the script directives at the beginning of the script. VBScript supports one primary data type: variant. You can think of variants as general-purpose variables that aren’t bound to a specific type. When you declare a VBScript variable using Dim, you don’t supply additional type information as you do in VB. Instead, VBScript determines a variable’s type at runtime, based on the variable’s contents and the usage context. VBScript supports many different data subtypes and corresponding data conversion functions that let you morph variants to your heart’s content. For more information about VBScript variables, download the free VBScript HTML Help file (vbsdoc.exe) at http://msdn.microsoft.com/scripting/vbscript/download/vbsdoc.exe.

Variable Initialization
Variable initialization is nothing more than assigning a known value to a variable before you use the variable. In demo.vbs, I initialize several variables before using the variables in the script’s body. You can initialize a variable to one value, such as a number or a string, or to a more complex value constructed from multiple elements. For example, in the variable initialization section of demo.vbs, the script assigns to strLogFile the concatenation (from VBScript’s & operator) of three substrings. The first substring (“c:\”) and third substring (“.log”) are straightforward. The second or middle substring retrieves the value of WScript’s ScriptName property (in this case, “demo.vbs”) and splits the string into two strings at the dot (.). In this example, I append the subscript (0) to the end of the function call to force the Split function to return only the first element (“demo”) of the array of elements that Split ordinarily returns. The end result is a log file name that is identical to the script name without the extension.

Constant Definitions
Constants are named values that don’t change throughout the execution of a script. VBScript supports two types of constants: literal and intrinsic. Literal constants are constants that you define for the purpose of making your script more intelligible. Let’s say that you write a script that uses the

Chapter 1 Scripting 101: Declaring and Initializing Variables


value of Pi. Rather than hard-coding the numeric value each time the script needs it, you might define the following literal constant: Const Pi = 3.1415926535897932384626433832795. When you need to reference the value of Pi in your script, you simply use the constant’s name (i.e., Pi). In this example, referring to the name is much easier than (and significantly reduces potential typing errors from) coding the numeric value each time the script needs to use Pi. Furthermore, if you need to change the value of a constant at some later date, you need to update the constant’s value in only one place in the script: in the line where you defined the constant. You can assign only scalar values to VBScript literal constants. The language doesn’t support constants constructed from other constants or expressions. Intrinsic constants are constants that are built into the VBScript language to make the language easier to use and make your scripts easier to read and maintain. Some of my favorite intrinsic constants include the escape sequence constants such as vbNewLine and vbTab that represent ANSI character codes, which you’ll find sprinkled throughout demo.vbs. The VBScript Language Reference and the Scripting Run-Time Reference in the VBScript Help file provide a complete list of constants. VBScript doesn’t let you use constants that other objects, such as the scripting runtime’s FileSystemObject and ADSI, define. However, Microsoft plans to include this capability in the next release of WSH. In the interim, if you want to use constants that other objects define, simply duplicate the constant definition in your script. In the constant definitions section of demo.vbs, I define the local constant OpenFileForReading to mimic the behavior of FileSystemObject’s OpenFileForReading constant.

Demo.vbs End
Before I go, I should tell you what the demo script in Listing 1 does. Demo.vbs simply runs NT’s ipconfig.exe utility, writes the commands’ output to a file, opens and displays the file, deletes the file, and records the success or failure of each major action along the way to a log file. Then, demo.vbs opens the resulting log file, displays the file, deletes the file, and exits. In Chapter 2, I’ll examine one of the more important elements generally found in the body of a script: object handling.


Chapter 2

Scripting 101: Working with Objects
By Bob Wells
In Chapter 1, I explained how to declare and initialize variables in Windows Script Host (WSH) scripts that you create using VBScript. In this chapter, I discuss the most important job you perform in the script body: working with objects.

Regardless of the tasks your scripts perform, at some point you’ll create and interact with objects. Whether implicitly or explicitly, all WSH scripts create objects. Understanding what an object is, what an object provides, and how to create and interact with objects is key to successfully using WSH. Although working with objects might be old hat to those of you with a Visual Basic (VB) background, quite the opposite is true for individuals with only a batch file background.

Objects vs. Utilities
WSH scripts and batch files have numerous significant differences, but none more important than the transition away from utilities and toward objects. Historically, developers used batch files to glue together system, resource kit, and third-party command-line utilities. Provided that you found utilities to support all the tasks that you wanted to perform, this approach worked fine. Unfortunately, developers often hit roadblocks along the way. If the utility the batch file called failed to provide some required option, the developer had to scour the Internet for another utility to fill the void. Sound familiar? Figure 1 shows how Windows NT’s graphical applications and utilities call Win32 APIs to run the task that the application or utility performs. Unfortunately, Win32 APIs aren’t suitable for ad hoc scripts, and calling Win32 APIs from scripts is almost impossible. Therefore, NT limited scripting to the functionality that the utilities provided. This limitation led to a perception that Windows was scripting-challenged (especially compared to UNIX). In fact, the limited functionality led to a proliferation of third-party and open-source scripting tools to address the lack of Win32 scriptability. So what has changed? As I’ve mentioned, part of the solution is WSH. WSH interacts with objects, specifically COM automation objects, to eliminate the need to find utilities that support all the tasks you want to perform. WSH provides a lightweight scripting environment capable of calling the same set of system services that the native NT graphical applications call. The more significant piece of the solution pertains to the API. As Figure 2 shows, Microsoft is using COM to expose almost all system services in such a way that scripts and more traditional system programming languages (e.g., C++) call the same set of interfaces to perform a desired task. How is this standardization possible? With COM, system and application developers can develop their applications so that COM-aware scripting environments, such as WSH, can interact with and control COM-enabled services and applications.


Guide to VBScripting

Figure 1
How NT’s graphical applications and utilities call Win32 APIs to run the task that the application or utility performs
Server Manager User Manager C:\>utilities OK Scripts

Win32 API

Event Log



Service Control Manager

System Data

Performance Data

Figure 2
COM exposing system services
MMC MMC Active Directory Snap-In C:\>utilities OK Batch Files WSH Scripts

COM-Enabled System Services (ADO, ADSI, CDO, WMI) Win32 API

Event Log


Active Directory


Service Control Manager

System and Performance Data

For example, in addition to abstracting multiple and disparate directory services, Active Directory Service Interfaces (ADSI) lets scripts call the same ADSI services that the native Windows 2000 Active Directory (AD) management tools use. Similarly, Windows Management Instrumentation

Chapter 2 Scripting 101: Working with Objects


(WMI) provides COM-savvy scripts with the ability to fetch, read, and set the same set of Win32 system and performance data that Microsoft Systems Management Server (SMS) 2.0 and other WebBased Enterprise Management (WBEM)-enabled systems management applications access. You leverage these services by utilizing these COM objects in your scripts. The first step is to understand what an object is.

What Is an Object?
An object is simply a thing. Each object has a set of characteristics or attributes that describe the object and the actions it performs. Network OSs, messaging systems, applications, and programming languages generally classify objects in terms of a user, a group, a computer, a printer, a network, or an application. These entities are all objects in the IT world. Each of these objects has a set of properties that uniquely describe it. Each object might also perform an action using its methods. So in the computer world, properties describe an object, and objects use methods to perform actions. Let’s consider a user object. Properties of a user object might include username, full name, and password. A user object might use methods to create, delete, or set passwords. You must have a user object before you can interact with it, so let’s take a look at how to create objects.

Creating Objects
You might be interested to learn that WSH is a set of objects. WSH includes three primary objects: WScript, Network, and Shell. The WScript object provides the script execution environment. The Network object provides properties and methods typically associated with network and printer connections. The Shell object provides methods and properties associated with the desktop shell, environment variables, and the registry. Before you can access an object’s methods and properties, you must first create an instance of the object. As part of the script execution environment, WScript includes two methods—CreateObject and GetObject—that you can use to create objects. Using these methods is a concept that represents a radical departure from calling utilities inside of batch files. VBScript also provides CreateObject and GetObject as functions, which you’ll generally want to use rather than the WScript methods. Using WScript’s CreateObject or GetObject methods adds an additional layer of work and is slightly less efficient than using the analogous VBScript functions. WScript includes these methods to support third-party script engines that might not provide a mechanism to create objects. Before we look at some examples, you need to be aware of a couple of exceptions to the requirement that you create the object before you use it. The first exception concerns WScript. Recall that I said every WSH script creates objects. Running a WSH script implicitly creates a WScript object. Therefore, you can call any WScript method or read any WScript property without an explicit call to CreateObject. The VBScript Err object is the second object that WSH scripts automatically create. For more information about the VBScript Err object, see Chapter 1. Now, let’s create some objects. A good place to start is with syntax and definitions.


Guide to VBScripting

The CreateObject function creates and returns a reference to an automation object. The function has one mandatory parameter and one optional parameter. In the code line
Set objReference = CreateObject(“strApp.strObj” [, “strServerName”])

the mandatory two-part parameter is strApp.strObj, which identifies the name of the application or component containing the target object and the class name of the object to create. This application and object pair is also a programmatic identifier (ProgID). The optional parameter, strServerName, identifies the name of the server on which the target object resides. When supported, strServerName lets you create and interact with objects installed on remote machines. Microsoft added this feature to the CreateObject function in VB 6.0 and expects to include the feature in the Win2K WSH and VBScript releases. CreateObject returns a reference to the newly created object. Typically, you assign the reference to an object variable so that you can interact with the object within your script. In the previous code line, VBScript’s Set statement assigns the object reference that CreateObject returns to the objReference variable. After you obtain a valid object reference, you can invoke the object’s methods and read or set the object’s properties. The objects.vbs example script in Listing 1 demonstrates how to use the WScript CreateObject method and VBScript’s CreateObject function. At callout A, you can see that WScript methods (WScript.Echo) and properties (e.g., WScript.Application, WScript.Fullname) are available immediately without an explicit call to CreateObject. At callout B, the script uses WScript’s CreateObject method to create a WSH Network object. In the string “WScript.Network”, WScript is the application name and Network is the object name. The Set command initializes the objNetwork variable with the object reference that the CreateObject method returns. After you have a valid object reference, you can interact with the object. For example, in callout D, the script echoes the values of the Network object’s ComputerName, UserDomain, and UserName properties. As you can see at callout D, you access the object’s properties using the object reference (objNetwork), followed by a dot (.) and the name of the property that you want to read. Many properties are read-only, as is the case with the Network object’s three properties. Callout C demonstrates how to create a WSH Shell object using VBScript’s CreateObject function. Notice the WScript ProgID doesn’t preface CreateObject. Therefore, callout C calls the VBScript CreateObject function rather than WScript’s CreateObject method. In this case, WScript is the application name, Shell is now the object name, and objShell gets initialized to the object reference that CreateObject returns. At callout E, the script invokes the Shell object’s Run method. Similar to how you work with the object’s properties, you invoke the object’s methods using the object reference, followed by a dot (.) and the method name and any parameters that the method supports. The script’s comments contain details about the different ways to invoke the Run method. So what are some of the common objects you’ll likely encounter as you venture deeper into WSH? Table 1 identifies several common application and object names that you might use with CreateObject. As you’ll see next, you can also use GetObject to breathe life into scripts.

Chapter 2 Scripting 101: Working with Objects


Option Explicit Dim objNetwork, objShell, intRC ‘ Constants that the Shell Object’s Run method uses. Const vbHide = 0 Const vbNormalFocus = 1 Const vbMinimizedFocus = 2 Const vbMaximizedFocus = 3 Const vbNormalNoFocus = 4 Const vbMinimizedNoFocus = 6 ‘ ‘ ‘ ‘ * * * * Every WSH script automatically creates a WScript object. You can immediately invoke WScript methods and read WScript properties as WScript’s Echo method and numerous properties demonstrate.

WScript.Echo “WScript Properties” & vbNewLine & _ “________" & vbNewLine & _ “Application: “ & WScript.Application & vbNewLine & _ “Fullname: “ & WScript.Fullname & vbNewLine & _ “Interactive: “ & WScript.Interactive & vbNewLine & _ “Name: “ & WScript.Name & vbNewLine & _ “Path: “ & WScript.Path & vbNewLine & _ “ScriptFullName: “ & WScript.ScriptFullName & vbNewLine & _ “ScriptName: “ & WScript.ScriptName & vbNewLine & _ “Version: “ & WScript.Version & vbNewLine ‘ * To interact with other objects, you must first create a reference ‘ * to the target object. Here I use WScript’s CreateObject method. Set objNetwork = WScript.CreateObject(“WScript.Network”) ‘ ^^^^^^^^^^ ^^^^^^^.^^^^^^^ ‘ The object reference variable is first followed by strApp.strObj. ‘ * Here I use the more efficient VBScript CreateObject function. Set objShell = CreateObject(“WScript.Shell”) ‘ * After you have a valid object reference, you access the object’s ‘ * properties using the objRef.PropertyName syntax. WScript.Echo “Properties of the Network Object (read-only)” & _ vbNewLine & “__________________” & vbNewLine & _ “ComputerName: “ & objNetwork.ComputerName & vbNewLine & _ “UserDomain: “ & objNetwork.UserDomain & vbNewLine & _ “UserName: “ & objNetwork.UserName ‘ * You invoke the objects methods using the ‘ * objRef.MethodName([Parameter List]) syntax. objShell.Run “notepad.exe”, vbMaximizedFocus, FALSE intRC = objShell.Run(“wordpad.exe”, vbMinimizedFocus, FALSE) ‘ ‘ ‘ ‘ ‘ ‘ * * * * * * Notice the difference between the two invocations of the Shell’s Run method. The first invocation doesn’t enclose the parameter list in parentheses. The second invocation encloses the parameter list. The rule is that when using a method as an RValue (appearing on the right-hand side of an assignment statement), you can enclose the parameter list in parentheses; otherwise you can’t. When you’re done with an object, you release the reference using VBScript’s Nothing keyword. objNetwork = Nothing ObjShell = Nothing




‘ * ‘ * Set Set


Guide to VBScripting

Table 1:
Common CreateObject Application and Object Names
Application.Object Excel.Application Word.Application Scripting.Dictionary Scripting.FileSystemObject WScript.Network WScript.Shell ADODB.Connection CDONTS.NewMail Function Returns a reference to a Microsoft Excel automation object Returns a reference to a Microsoft Word automation object Creates a Scripting runtime Dictionary object Creates a Scripting runtime FileSystemObject Creates a WSH Network object Creates a WSH Shell object Establishes a connection with a data source using ActiveX Data Objects Creates a Collaboration Data Object NewMail object to construct and send an email message

Similar to CreateObject, GetObject also returns a reference to an automation object. However, GetObject supports a slightly different set of parameters:
Set objReference = GetObject([“strMoniker”] [,”strClass”])

The brackets ([]) mark both parameters as optional. You can supply both parameters, but you must pass GetObject at least one of the two parameters. The first parameter, strMoniker, identifies a persistent object that the target automation server or component understands, such as a Microsoft Excel spreadsheet file. In the case of ADSI, the moniker is one of the four ADSI namespaces: LDAP, WinNT, NDS, or NWCOMPAT. WMI’s moniker is WINMGMTS. The second parameter, strClass, is identical in terms of content and syntax to the two-part strApp.strObj ProgID that CreateObject uses. The code lines in Figure 3 are all valid forms of GetObject. When do you use CreateObject, and when do you use GetObject? Experience has taught me to use GetObject primarily with ADSI and WMI. I use CreateObject for everything else. However, exceptions exist. Table 2 shows various scenarios in which you might use CreateObject and GetObject. Where do you learn the details about the object’s methods and properties? And how do you determine a specific object’s application name, class, or object name? Your first stop should be the documentation that accompanies the technology. You’ll also want to make sure you check out the Microsoft Developer Network (MSDN) Library at http://msdn.microsoft.com/library. This library is also available on CD-ROM via subscription. Now is the time to start looking for opportunities to replace those old batch files that call option-inhibited command-line utilities. As you do, recognize that learning this new object-based approach to scripting might require extra effort on the front end. The payoff will come when you realize the degree to which you’re able to automate the back end. Today’s Win32 platform is most

Chapter 2 Scripting 101: Working with Objects


certainly scriptable—and tomorrow’s becomes even more so as these services and technologies converge in Win2K. Create an object, and have fun!

Figure 3:
Valid Forms of GetObject
Rem Examples using WScript’s GetObject method:
Set Set Set Set objExcel = WScript.GetObject(“c:\users.xls”) objExcel = WScript.GetObject(“c:\users.xls”, “Excel.Application”) objNetwork = WScript.GetObject(“”, “WScript.Network”) objDomain = WScript.GetObject(“WinNT://LAB,Domain”)

Rem Examples using VBScript’s GetObject function: Set objExcel = GetObject(“c:\users.xls”) Set objExcel = GetObject(“c:\users.xls”, “Excel.Application”) Set objNetwork = GetObject(“”,”WScript.Network”) Set objWMI = GetObject(“winmgmts:” & _ “{impersonationLevel=impersonate}!\\” & “dell610”)

Table 2:
Scenarios for Creating Objects
Method CreateObject Description Creates a new instance of an automation server Creates additional instances (multiple copies) of an already running server Creates a reference to an object installed on a remote machine (not yet available in WSH and VBScript) Obtains a reference to an automation server that is already running without creating additional instances Creates a new instance of an automation server based on a registered file type or moniker



Chapter 3

VBScript Techniques
By Mike Otey
VBScript, one of the most powerful and underused features in the Windows environment, provides the same powerful programming tools (e.g., variable support, structured program control, the ability to leverage COM objects) that you’ll find in full-featured development languages. Some basic but essential scripting techniques include the following. 1. Comments—Liberal use of comments lets you quickly decipher a script’s purpose months after you’ve forgotten about the script. The apostrophe (‘) is the VBScript comment indicator.
’vbsample.vbs -- Sample Script for Windows & .NET Magazine

2. Option Explicit—You should add the Option Explicit statement to the beginning of a script to force yourself to explicitly declare all variables in the script. Variables are named storage locations that contain values the script can modify. By default, scripts automatically create a variable on the first reference to it. However, this capability can mask minor typos in the script. Using Option Explicit can save you hours of debugging. 3. Dim—Dim statements let you create variables. Variable names must begin with an alphabetic character, can’t contain dots, and must contain fewer than 256 characters. Many languages require you to specify the data type of each variable. However, VBScript supports only the variant data type (which can contain all types of data), so you don’t need to declare a specific data type for VBScript variables.
Dim myVariable

4. Assignment—After you create a new variable, you can use the Assignment operator (=) to assign a value to it. The following line assigns the value of 3 to myVariable.
myVariable = 3

5. Math—VBScript provides support for the full range of mathematical expressions. The following line shows how to perform simple math calculations and divide the result by the contents of myVariable.
myVariable = ((5 + 1) * 2)/myVariable

6. Concatenation—A common VBScript technique is to combine the contents of different strings. You can use the Concatenation operator (&) to create a new string.
myVariable = “The value of myVariable is:” & myVariable

7. Constants—Constants, like variables, are named values that you can use in your scripts. Although you can alter the contents of variables, you can’t alter the value of a constant.
const myTitle = “VBSample”


Guide to VBScripting

8. MsgBox—You can use the MsgBox function to display messages to the user. The following example shows how to display the message contained in myVariable. The message box’s title bar displays the value in myTitle.
MsgBox myVariable,,myTitle

9. InputBox—You use the InputBox function to prompt the user for simple input values. The function automatically assigns the value that the user enters to the variable on the left of the equal sign. Note that the InputBox function encloses its argument in parentheses.
myVariable = InputBox(“Input 1”)

10. If...Then...Else—The If statement is an important logical-flow control function. Present in virtually all scripts, the If statement evaluates a condition, then performs an action based on whether the condition is met.
If myVariable = 1 Then MsgBox “The value was 1” Else MsgBox “The value was not 1” End if

If you want to step up a level and begin writing productive administrative scripts, you can use the following more advanced VBScript techniques. 1. On Error—The On Error statement lets a script trap runtime errors and continue executing. You can test for errors in the script after each statement has executed.
On Error Resume Next

2. InStr—This function lets you locate a substring in a string. The function returns the starting position of the substring or a 0 if the function doesn’t find the string. In
nPos = InStr(“123345”, “33”) nPos has a value of 3 because “33” begins in the third position of “123345.”

3. The Do Loop—This basic mechanism for repeatedly executing a set of statements comes in two forms: a Do Until Loop and a Do While Loop. The most important distinction between the two loops is that the Do Until Loop always executes at least once.
Do Until myValue > 1 myValue = myValue + 1 Loop

4. Subroutines—Modularizing your code into subroutines lets you organize your scripts and create reusable routines. You can define subroutines anywhere in a script. You use subroutines when you don’t need to return a value to the calling code.
Sub mySub(myValue) MsgBox “Inside mySub” & myValue End Sub

5. Functions—Functions are routines that return a value to the calling code. The routine assigns the returned value to the function name.

Chapter 3 VBScript Techniques


Function myFunction(myParm) myFunction = myParm + 1 End Function

6. MsgBox—In addition to displaying a message, the MsgBox function can display buttons and return the value of the selected button. The following example displays a message box that contains Yes, No, and Cancel buttons. The script assigns the selected button’s value to nButton.
nButton = MsgBox(“Click a button”, vbYesNoCancel)

7. Select Case—Use the Select Case statement to compare an expression against several other expressions.
Select Case nButton Case vbYes MsgBox “Yes” Case vbNo MsgBox “No” Case Else MsgBox “Cancel” End Select

8. CreateObject—This statement lets you create an instance of a COM object inside a script. You must use the Set statement, in conjunction with the CreateObject statement, to create objects. To destroy an object, set it to Nothing. The following example creates an instance of Microsoft Word.
Set myWord = CreateObject_ (“Word.Application”)

9. Object properties—To read and write to one of an object’s properties, type the object’s name, a dot, and the property name. The following example sets the Word object’s Visible property to True, causing the system to display the Word application.
myWord.Visible = True

10. Object methods—You also use the dot notation to access an object’s methods. The following example uses the Add method to create a new Word document and shows how to use the TypeText method to insert text.
myWord.Documents.Add myWord.Selection.TypeText “Text from vbsample.vbs”


Chapter 4

Scripting Command-Line Switches
By Don Jones
Most Windows administrators rely heavily on running commands at the command line to accomplish day-to-day administrative tasks and to automate tedious, repetitive tasks. The best part about such commands is their switches (aka parameters), which let you customize the commands’ behavior. The Net Use command, for example, wouldn’t be as useful without its /persistent switch, and the Xcopy command would be useless without its many switches. Many administrators also use VBScript to make their lives easier—if you’re reading this, you’ve probably written a few scripts of your own. But administrators often don’t maximize the flexibility of their scripts, because most administrators don’t write their scripts to take advantage of command-line switches. For example, let’s assume you’ve written a script called DisableUser.vbs, which Listing 1 shows, that lets you easily handle employee resignations. (To download executable code for the chapter, go to http://www.winscriptingsolutions.com/Files/07/24944/24944.zip.) The script disables a user account and the user’s home directory share while leaving the user’s user account and home directory intact for archival and administrative purposes.

Listing 1
Dim oUser, oShareSvc, sServer, sUser, sUserDomain sServer = “Server1” sUser = “DonJ” sUserDomain = “MYDOMAIN” ‘ Get a reference to the Server service and user account. Set oShareSvc = GetObject(“WinNT://” & sServer & _ “/LanManServer”) Set oUser = GetObject(“WinNT://” & sUserDomain _ & “/” & sUser & “,user”) ‘ Disable the home directory share (assumes ‘ the share name is the same as the user’s name). oShareSvc.Delete “fileshare”, sUser ‘ Disable the user account. oUser.Put “UserFlags”, oUser.UserFlags Or 2 oUser.SetInfo WScript.Echo “Complete.”

Adding Flexibility with Switches
The problem with DisableUser.vbs is that its important values—the user’s account name, the name of the server that contains the user’s home directory, and the user’s domain name—are hard-coded in the script. Before running the script, you need to open the script in Notepad, modify those


Guide to VBScripting

values, and save the revised script. To eliminate having to open and modify the script each time you use it, you can use command-line switches. When your script supports command-line switches, you can launch the script and provide the necessary values at the command line. For example, the command
wscript disableuser.vbs -u:donj -s:server1 -d:mydomain

launches DisableUser.vbs and passes three values—the account name (donj), the server name (server1), and the domain name (mydomain)—to the script. The main Windows Script Host (WSH) object, WScript, passes the three command-line parameters to the script. WScript includes a collection named Arguments, which contains one item for each parameter passed to the script. WSH parses the Arguments collection and uses spaces as switch separators. Thus, WSH considers every string of characters that has a space on both ends to be a separate parameter. Your script can easily access the switches through a For Each...Next statement such as
Dim oArg For Each oArg in WScript.Arguments WScript.Echo oArg Next

To see how such a loop works, type those four lines into the file C:\test.vbs. Then, open a command-shell window and change to the C directory. Type
wscript test.vbs 1 2 3

on the command line. Press Enter, and you’ll see a pop-up dialog box that contains all three switch values: 1, 2, and 3. To put this capability to use in a batch file, you need to use a Select Case statement to analyze each switch and assign its value to a variable. Listing 2 shows sample code that looks for the s: and -u: switches and assigns their values to the sServer and sUser variables, respectively.

Listing 2
Sample Switch-Analysis Code
Dim oArg, sUser, sServer, sSwitch, sValue For Each oArg in WScript.Arguments sSwitch = LCase(Left(oArg,3)) sValue = Right(oArg,Len(oArg)-3) Select Case sSwitch Case “-s:” sServer = sValue Case “-u:” sUser = sValue End Select Next ‘ Display the results of the switch analysis. WScript.Echo “Server: “ & sServer & _ vbCrLf & “User: “ & sUser

The code starts with a For Each...Next loop that examines each command-line argument individually. For each argument, the script sets the sSwitch variable to the leftmost three characters of the argument string (e.g., -s:). The LCase() function makes the argument lowercase so that the

Chapter 4 Scripting Command-Line Switches


script treats -s and -S the same. The code sets the sValue variable to the remainder of the argument string (i.e., the switch’s value) by assigning to the variable the value of the string minus the first three characters. Finally, the sample code uses a Select Case construct to analyze each argument and assign each switch’s value to the correct script variable. When the argument’s first three characters are -s:, the script assigns the switch value portion of the argument to the sServer variable. When the argument’s first three characters are -u:, the script assigns the switch value to the sUser variable. The Select Case statement lets you type the command-line switches in any order, just like most command-line tools do.

Handling Omitted Switches
An ideal script would accept command-line switches and use pop-up dialog boxes to prompt you for missing information. Such a script would provide the best of both worlds: command-line switches so that you can easily launch the script and provide the necessary parameter values, and pop-up dialog boxes for any required switches that you’ve forgotten. Incorporating this capability is easy: Just add a few Do...Loop statements that check for missing values after the For Each...Next loop. Listing 3 shows an example. You’ll need to add a Do...Loop construct for each required parameter.

Listing 3
Checking for Missing Values
Do While sUserName = “” sUserName = InputBox(“User name to disable?”) Loop

Listing 4 shows a new version of DisableUser.vbs called NewDisableUser.vbs that incorporates both command-line and pop-up dialog capabilities. Callout A in Listing 4 shows the added code. NewDisableUser.vbs includes three command-line switches: • -s: for the name of the server that contains the user’s home directory • -u: for the user account name • -d: for the name of the domain in which the user’s account is located This script works in Windows 2000 and Windows NT domains. Note that NewDisableUser.vbs uses Microsoft Active Directory Service Interfaces (ADSI). As you can see, adding command-line switch capability to your scripts makes them much more flexible. When a script supports command-line switches, you don’t have to open and modify the script each time you use it. Instead, you can quickly launch a script and provide the necessary information at the command line. And when a script incorporates pop-up dialog boxes that prompt you for missing information, you can even launch the script by simply double-clicking the script and providing parameters in the pop-up dialog boxes.


Guide to VBScripting

Listing 4
Dim oUser, oShareSvc, sServer, sUser, sUserDomain Dim oArg, sSwitch, sValue ‘ Check command-line switches. For Each oArg In WScript.Arguments sSwitch = LCase(Left(oArg,3)) sValue = Right(oArg,Len(oArg)-3) Select Case sSwitch Case “-s:” sServer = sValue Case “-u:” sUser = sValue Case “-d:” sUserDomain = sValue End Select Next ‘ Prompt for missing values. Do While sServer = “” sServer = InputBox(“Server name for user share?”) Loop Do While sUser = “” sUser = InputBox(“User name?”) Loop Do While sUserDomain = “” sUserDomain = InputBox(“User’s domain name?”) Loop ‘ Get a reference to the Server service and user account. Set oShareSvc = GetObject(“WinNT://” & sServer & _ “/LanManServer”) Set oUser = GetObject(“WinNT://” & sUserDomain _ & “/” & sUser & “,user”) ‘ Disable the home directory share (assumes ‘ the share name is the same as the user’s name). oShareSvc.Delete “fileshare”, sUser ‘ Disable the user account. oUser.Put “UserFlags”, oUser.UserFlags Or 2 oUser.SetInfo WScript.Echo “Complete.”



Chapter 5

Adding Users in Bulk
By Don Jones
Administrators encounter this scenario all the time: You show up on Monday morning, and the human resources (HR) department has a dozen new user accounts for you to set up. This setup doesn’t include just accounts, either, but the new users’ home directories, dial-in permissions, and more. Instead of spending the morning checking on your Microsoft Exchange Server or catching up on the latest news on the Web, you’re stuck entering new-user information from a spreadsheet. Scripting to the rescue! This type of task is not only well suited to scripting-based automation, it’s also a great way to demonstrate several different scripting capabilities. Let me show you a script that can automate the task of adding new users to your Windows NT 4.0 domain.

Getting Started
For this example, let’s assume you’re working with an NT domain called Corporate that has a PDC called NT4PDC. You must set up a basic infrastructure to let your script work. First, create a Microsoft Excel spreadsheet such as the one that Figure 1 shows. The spreadsheet should include information for each user that you’re adding to the domain. In this example, I’ve included columns for the user’s ID, full name, description, and home directory Universal Naming Convention (UNC) path, a column for a comma-separated list of user groups to which the user should belong, and a column for indicating whether the user needs to have dial-in permissions. You can distribute your spreadsheet to HR department personnel and ask them to use it when they submit new-user requests. (Notice that the list of user groups doesn’t include the Domain Users group because, by default, all users belong to Domain Users.) Next, create a System ODBC Data Source Name (DSN) that points to the spreadsheet, as Figure 2 shows. (I recommend that you create a System DSN rather than a User DSN. Otherwise, if another user logs on to the same machine to run the script, the script will fail.) The DSN points only to the spreadsheet’s location, so you can save new spreadsheets to the same location and the DSN will find them. In this example, I’ve created a DSN called Excel that uses the Excel ODBC driver to point to my spreadsheet. If your computer doesn’t have an Excel driver, you must install the latest version of Microsoft Data Access Components (MDAC), which you can download from http://www.microsoft.com/data. Finally, make sure that Windows Script Host (WSH) and Active Directory Service Interfaces (ADSI) are available on the computer on which you’ll run the script. (Both are present on Windows 2000 Professional, Win2K Server, Win2K Advanced Server, and Win2K Datacenter Server computers.) Note that you must install ADSI even if you’re going to be working with an NT domain. If you want to set up these components on an NT workstation, you must install WSH from the Microsoft Windows NT 4.0 Option Pack and ADSI from the Microsoft Platform software development kit (SDK), which is available from http://www.microsoft.com/msdownload/ platformsdk/sdkupdate.


Guide to VBScripting

Figure 1
Excel spreadsheet for adding users to a domain

Figure 2
Creating a System ODBC DSN that points to a spreadsheet

Chapter 5 Adding Users in Bulk


Writing the Script
When all the preliminary pieces are in place, you can get the script ready. Listing 1 shows the complete bulk-users.vbs script. (To download executable code for this chapter, go to http://www.winscriptingsolutions.com/Files/22537/22537.zip.) Because a lot is going on in this script, I’ll describe each section.

Listing 1
’ Part 1: Use ADO to open the Excel spreadsheet. Dim oCN Set oCN = CreateObject(“ADODB.Connection”) oCN.Open “Excel” Dim oRS Set oRS = oCN.Execute(“SELECT * FROM [Sheet1$]”) ‘ Part 2: Use ADSI to obtain a reference to the NT domain. Dim oDomain Dim sPDC sPDC = “NT4PDC” Set oDomain = GetObject(“WinNT://” & sPDC) ‘ Part 3: Open an output text file to store users’ initial passwords. Dim oFSO, oTS Set oFSO = CreateObject(“Scripting.FileSystemObject”) Set oTS = oFSO.CreateTextFile(“C:\passwords.txt”,True) ‘ Part 4: For each record in the record set, add the user, set the ‘ correct user properties, and add the user to the appropriate groups. ‘ Create the necessary variables. Dim sUserID, sFullName, sDescription Dim sHomeDir, sGroups, sDialIn Dim sPassword, oUserAcct, oFolder Dim sGroupList, iTemp, oGroup ‘ Define the base path in which to create the home directories. Dim sHomePath, sMsg sHomePath = “\\iridis1\c$\users\” ‘ Go through the record set one row at a time. Do Until oRS.EOF ‘ Get the user information from this row. sUserID = oRS(“UserID”) sFullName = oRS(“FullName”) sDescription = oRS(“Description”) sHomeDir = oRS(“HomeDirectory”) sGroups = oRS(“Groups”) sDialIn = oRS(“DialIn”) ‘ Make up a new password. sPassword = Left(sUserID,2) _ & DatePart(“n”,Time) & DatePart(“y”,Date) _ & DatePart(“s”,Time) ‘ Create the user account. On Error Resume Next Set oUserAcct = oDomain.Create(“user”,sUserID) If Err <> 0 Then sMsg = “An error occurred creating user “ _ & sUserID & vbCrLf & vbCrLf sMsg = sMsg & “The error is: “ _ & Err.Description MsgBox sMsg End If On Error Goto 0



Guide to VBScripting

Listing 1: continued
‘ Set account properties. oUserAcct.SetPassword sPassword oUserAcct.FullName = sFullName oUserAcct.Description = sDescription oUserAcct.HomeDirectory = sHomeDir ‘ Set RAS permission. If sDialIn = “Y” Then oUserAcct.RasPermissions = 9 Else oUserAcct.RasPermissions = 1 End If ‘ Save the account. oUserAcct.SetInfo ‘ Get a reference to the new account. This step provides a valid SID ‘ and other information. Set oUserAcct = GetObject(“WinNT://” _ & sPDC & “/” & sUserID & “,user”) ‘ Write the password to a file. oTS.Write sUserID & “,” & sPassword _ & vbCrLf ‘ Part 4A: Add the user account to groups. Use the Split function to ‘ turn the comma-separated list into an array. sGroupList = Split(sGroups, “,”) ‘ Go through the array and add the user to each group. For iTemp = 0 To uBound(sGroupList) ‘ Get the group. Set oGroup = GetObject(“WinNT://” & _ sPDC & “/” & sGroupList(iTemp) _ & “,group”) ‘ Add the user account. oGroup.Add oUserAcct.ADsPath ‘ Release the group. Set oGroup = Nothing Next ‘ Part 4B: Create the user’s home directory. (Append the UserID to ‘ the Home Path variable). Set oFolder = oFSO.CreateFolder(sHomePath _ & sUserID) ‘ Part 5: Release the user account. Set oUserAcct = Nothing ‘ Move to the next row in the record set. oRS.MoveNext Loop ‘ Part 6: Final clean up and close down. oRS.Close oTS.Close WScript.Echo “Passwords have been written “ _ & “to C:\passwords.txt.”

Chapter 5 Adding Users in Bulk


Part 1 of bulk-users.vbs uses ActiveX Data Objects (ADO) to create a connection to the ODBC DSN called Excel. Because that DSN points to my Excel spreadsheet, I have a database connection to the spreadsheet. Next, I use the Connection object’s Execute method to retrieve all the information on Sheet1 of the spreadsheet. This information resides in an ADO Recordset object. Part 2 of bulk-users.vbs uses ADSI to obtain a reference to the PDC. To use this script, you must replace NT4PDC with the name of the PDC in your environment. Changing the PDC name is easy because I’ve defined it in a variable rather than in each statement that requires the PDC name. Because the PDC’s SAM is the source for the domain’s account list, this reference lets me work with the domain accounts. Part 3 of the script uses the Scripting Runtime Library’s FileSystemObject object to open a new text file called passwords.txt. I use this .txt file to store the passwords I create for the new user accounts. The CreateTextFile method returns a reference to a TextStream object, which I store in the oTS variable. This step essentially makes the oTS variable represent the open text file, which in turn makes writing information to the file later easy.

The Work Begins
Now, all the preliminary scripting work has been done, and Part 4 of bulk-users.vbs can begin to create user accounts. The first few lines of Part 4 simply declare the variables that the script uses. The script then sets up the base path to the server on which the user’s home directory will be created. Note that this path connects to the C$ administrative share. The main work of this script is accomplished within the Do...Loop statement, which handles the record set one line at a time. The first six lines of code within the loop retrieve the new user’s information from the record set and store that information in variables. (This step isn’t strictly necessary, but it makes the information easier to work with.) Notice that the field names in the Recordset object correspond to the column names in the Excel spreadsheet. Next, I create a pseudo-random password by combining the first two characters of the new user’s ID, the minutes from the system clock, the Julian date (i.e., a number from 1 to 365 indicating the day of the year), and the seconds from the clock. You can also create a password based on the username, Social Security number, or other information. The next few lines actually create the user account in the domain. The Create method requires two parameters: the type of object you’re creating (e.g., a User object) and the identifier (e.g., the user’s ID). The method returns a reference to the newly created User object, which I assign to the oUserAcct variable. Capturing this reference in a variable lets the script immediately set the properties of the account, including the password, full name, description, home directory, and RAS permissions. Note that the script includes error checking to specify whether a problem occurred when the script tried to create the user account. For example, if the Excel spreadsheet contains a username that already exists, the script will encounter an error when it tries to create the duplicate username. The script will then display a message telling you about the error. After setting the account properties, the script sets RAS permissions. Note that the value 9 permits dialing in, while the value 1 denies it. You can look up these values in the ADSI documentation, which is available at http://msdn.microsoft.com/library/default.asp. (Navigate to Networking and Directory Services; Active Directory, ADSI and Directory Services; SDK Documentation; Directory Services; Active Directory Service Interfaces.) Next, the SetInfo method saves all the account properties to the domain, then the script obtains a fresh reference to the user account. When the


Guide to VBScripting

account is saved, the domain generates a SID and sets other internal information (e.g., the account’s creation date, initial security attributes). Obtaining a new reference to the account gives the script access to that internal information, which is required for the next major step—adding the user to the proper groups. Part 4A of the script accomplishes this step. Before moving on to Part 4A, I use the Write method of the TextStream object to save the user’s ID and password to the text file. Writing passwords to a file is a potential security breach, of course, which is why you might prefer to create nonrandom passwords that you don’t need to write to a file. If you’re certain that the password file won’t be compromised, though, this method is a convenient way to create passwords for new users. Because the Excel spreadsheet can contain a comma-separated list of groups to which the user should belong, I used the VBScript’s Split function to turn that list into a string array. The Split function looks for commas in the sGroups string and creates an array called sGroupList, in which each element in the array is one group name. I then use a For...Next statement to go through each element in the array. The uBound function tells the script how many elements are in the sGroupList array so that the script executes the loop the proper number of times. Within the For...Next loop, I use ADSI again to obtain a reference to the group to which I want to add the user. I use the oGroup variable to store the reference, then I use the Add method to add the user. The ADsPath property is an internal piece of information that the domain provides when the account is saved. After adding the user, I release the reference to the group by setting the variable to the Nothing keyword. This step isn’t strictly necessary, but it’s good scripting practice and helps improve performance. When the user is in all the correct groups, I use the FileSystemObject object again to create the user’s home directory. This process takes place in Part 4B of bulk-users.vbs. This step creates a new folder by using the sHomePath variable and appending the user’s ID for the final folder name. (Make sure that the C:\users folder already exists; otherwise, this operation will fail.) Part 5 of the script releases the reference to the User object in preparation for the next user in the record set. The last line in the Do...Loop construct moves the record-set pointer to the next row so that the loop can work on the next user. If the record-set pointer moves beyond the last user, the End of File (EOF) property is set to True and the loop terminates. Part 6 of the script runs after the Do...Loop construct has processed the last user. This part simply closes the Excel spreadsheet, closes the text file that contains the new passwords, and displays a dialog box that states the script has completed successfully. Figure 3 shows User Manager for Domains with the new user accounts in place.

Chapter 5 Adding Users in Bulk


Figure 3
New user accounts

Do It with a Script
Scripting is a great administrative tool because it lets you glue together various pieces of OS functionality to achieve terrific results. I used ADO, the Scripting Runtime Library, and ADSI—three relatively unrelated sets of technology—to perform a common, time-consuming administrative task. The examples in this chapter give you a good idea about how you can use a script to make complex tasks much easier and how to start exploring ADO and ADSI to come up with custom timesaving solutions.


Chapter 6

VBScript to Generate a List of User Last Logon Times for a Domain
By John Savill
I’ve written a small VBScript file, which Listing 1 contains, that you can use with the Windows scripting engine to generate a list of all user last logons (if the last logon time isn’t available, the user will be omitted). The file requires that you have Windows Script Host (WSH) installed. An advantage of using VBScript for this task is that you can change the code to output exactly what you want.

Listing 1
’ List last logon times ‘ 2001-03-27 John Savill, Jakob Hussfelt http://www.ntfaq.com On Error Resume Next sEnterDCs = “SAVILLTECH,SAVILLNT02” sObjects = Split(sEnterDCs, “,”) Set oDomain = GetObject(“WinNT://” & sObjects(0)) oDomain.Filter = Array(“User”) WScript.Echo “Showing last login times of accounts from: “ & oDomain.Name & vbNewLine For Each oDomainItem In oDomain sUsrLogin = oDomainItem.LastLogin If UBound(sObjects) >= 1 Then For ii = 1 To UBound(sObjects) Set oUsr = GetObject(“WinNT://” & sObjects(ii) & “/” & oDomainItem.Name & “,user”) If oUsr.LastLogin > sUsrLogin Then sUsrLogin = oUsr.LastLogin Next End If WScript.Echo “Username: “ & Left(oDomainItem.Name & Space(22),22) & “Last login: “ & FormatDateTime(sUsrLogin) Next

To use my VBScript file, save the text in Listing 1 as userlogin.vbs. Go to a command prompt and enter
cscript userlogin.vbs

Figure 1 shows a sample output. My script checks only the PDC. If you have BDCs, the output values might be incorrect because last logon times don’t update from BDCs to the PDC. You might want to modify the script to also query your BDCs and disply the latest logon time.


Guide to VBScripting

Figure 1
Sample userlogin.vbs output
C:\>cscript d:\temp\disuser.vbs Microsoft (R) Windows Scripting Host Version 5.0 for Windows Copyright (C) Microsoft Corporation 1996-1997. All rights reserved. Domain : SAVILLTECH Full Name=Maria Aala (DIS 120 inactive)Last login=27/05/1999 14:44:24 Full Name=Paul J AaronLast login=16/08/1999 13:01:56 Full Name=Hany A AbbasLast login=23/08/1999 13:25:46 Full Name=Tony S AbbittLast login=27/08/1999 15:07:20 Full Name=Adnan AbdallahLast login=16/07/1999 10:34:58 Full Name=Tony AbelaLast login=21/07/1999 10:43:20 Full Name=Juan Claudio AbelloLast login=25/06/1999 11:15:32 Full Name=Marie J B AbreyLast login=07/09/1999 08:00:34 Full Name=Philippa AbsilLast login=07/09/1999 06:33:18 Full Name=Ace Test account for NetID - Alistair PurvisLast login=28/01/1999 07:54:30 Full Name=Chris AckermannLast login=07/09/1999 08:21:20 Full Name=Curtis S AdamsLast login=10/08/1999 12:32:02 Full Name=Helen R Adams DIS user left 27.8.99Last login=02/08/1999 08:52:58 Full Name=Michael Adams Dis 4.6.99 NMFLast login=03/06/1999 08:50:10 Full Name=Philip R AdamsLast login=14/06/1999 12:49:00


Chapter 7

VBScript to Datastamp Log Files
By Larry Lai For administrators who manage applications that generate accumulative log files, I have a way to rename a file by date. For example, Oracle creates an Alert log file that can grow quite large. I wrote a VBScript routine, which Listing 1 shows, that timestamps the log file and renames it with a filename that reflects the date that Oracle created the log. The following wrapper batch file calls the script in Listing 1 so that you can use the AT command to schedule the script to run.
cscript datefile.vbs //nologo

Listing 1
’DateFile.vbs ‘VBScript that renames a log file with the current date Dim StrSource, StrNew, fso, sf, systime StrSource=”Source file fullpath” Set fso = CreateObject(“Scripting.FileSystemObject”) set sf=fso.GetFile(StrSource) systime=now() ‘You can skip either of the procedures below Call Comment(sf) ‘Add a timestamp at the end of the source file Call RenameFile(sf) ‘Rename the source file with the date format of yyyymmdd.log Sub Comment(f) Dim ts Const ForAppending = 8 set ts=f.OpenAsTextStream(ForAppending) ts.writeline cstr(systime) ts.close End Sub Sub RenameFile(f) StrNew=f.ParentFolder &”\” & cstr(year(systime)) & cstr(month(systime)) & cstr(day(systime)) & “.log” f.move StrNew End Sub


Chapter 8

Simplify Win2K Desktop Deployment
By Don Baker
How do you quickly deploy 50 Windows 2000 Professional desktops loaded with all the necessary application software? While working as a consultant to a large communications company, I faced this challenge and decided to automate the process. I’d done a lot of reading about Windows Script Host (WSH), and the time seemed right to see whether I could make it work for me. With disk-imaging software, Microsoft’s Sysprep utility, and some basic knowledge of WSH scripting, I was able to take the drudgery out of desktop setup and configuration. The process I came up with has four main steps: Create a standard hard disk image, write a script to perform some required setup tasks, prepare the disk image for cloning, and clone the image onto the target workstations. You too can deploy workstations quickly and easily using the procedure and code described here (of course, you’ll need to customize the code for your situation).

The Deployment Environment
In my situation, the company network is a mixture of Novell NetWare 4.x servers running in bindery emulation mode and Novell Directory Services (NDS) providing file-and-print services. The Windows NT 4.0 domain provides access to the application servers. The standard desktop OS is Windows 95, but inhouse application developers and administrators were using NT. Microsoft Systems Management Server (SMS) 2.0 and SMS 1.2 provide hardware and software inventory information, automated software distribution, and Help desk remote control. Then, our Web application developers decided they needed Win2K Pro, so we needed a plan for deploying the OS. We decided to use our standard method for building a workstation: cloning a disk image onto each workstation.

Create a Hard Disk Image
To clone a workstation, you configure a computer with the desired OS and application software, then create an image of the disk that you can copy to another computer’s hard disk. Disk cloning is a fast and efficient way to set up workstations, letting you build and configure a workstation with all its application software in less than 30 minutes. Several companies make disk-cloning software. I’ve most often used Symantec Ghost and PowerQuest’s Drive Image Pro, but all the products work similarly. You boot the computer to a DOS prompt, then start the disk-imaging software. You can create an image of an entire disk or a single partition and save it to another partition, drive, or network share. You can later restore the saved image to another disk. One feature to look for in disk-cloning software is media spanning, which lets you break up a disk image into smaller pieces. This feature is important if your image is larger than 640MB and you plan to store the image on a CD-ROM. Disk cloning works best in environments with a standard hardware platform. The video card, network adapter, sound cards, and so on should be the same in all the computers and should


Guide to VBScripting

occupy the same slots. The hard disks don’t all need to be the same size, but the disk image you’re loading onto a disk must be no larger than the disk. You might need to maintain several different disk images to keep up with your changing hardware, but the fewer images you have to maintain, the better. To prepare for disk imaging, load Win2K Pro on a representative workstation. Leave the local Administrator password blank and don’t join the domain at this time. Install all the application software that a standard workstation should have, and configure the OS and each application. Spend some time thinking about the OS and application settings that you would usually apply. Remember that the goal is to perform as little manual configuration as possible on each workstation. For example, if your word processing application has shared template directories, configure the directory location. My company was using an NT 4.0 domain as the back end, so I couldn’t take advantage of Win2K Group Policy. However, I was able to configure a local policy to run a logoff script to update antivirus definitions. Clean up the disk before you create the master image. Empty the Recycle Bin. Remove all the temporary files you and the setup process created. Clear the Start menu’s Documents list and browser history. Remove all persistent drive mappings you created while loading applications. Run Chkdsk to ensure the disk has no file-system errors, and clear the Application, Security, and System event logs. Create a 3.5” network boot disk. Copy your disk-imaging software to another 3.5” disk. Restart the computer by using the network boot disk, then map a drive to a network share that has enough space to store the disk image. When the computer has finished booting, insert the second 3.5” disk and start the disk-imaging software; save the hard disk image to the network drive you just mapped. After you’ve created the disk image, you can use a network boot disk to clone workstations directly from the image on the network share. Alternatively, you can burn the image on a CDROM and use the CD-ROM to clone workstations. If you follow the CD-ROM approach, you’ll need a boot disk with CD-ROM drivers or you’ll need to make the CD-ROM with the image on it bootable. Storing the image on a bootable CD-ROM makes workstation cloning fast and easy. You can find information about creating bootable CD-ROMs in the documentation that came with your CD-ROM burner or on the manufacturer’s Web site.

Write a FinalSetup.vbs Script
If you like batch files, you’ll love WSH scripting. WSH is a script interpreter (engine) built into Win2K and Win98. You can install it on NT 4.0 and Win95 as well. The WSH interpreter supports WSH commands, VBScript, and JScript (Microsoft’s version of JavaScript). You can use WSH scripting to manipulate the file system, automate desktop applications, manage a Microsoft SQL Server system, and much more. Learning WSH scripting is easy—all you need is a text editor and a computer with WSH installed. The Microsoft Windows Script Web site (http://msdn.microsoft.com/library/default.asp?url=/nhp/Default.asp?contentid=28001169) has a great deal of information available for download. I used WSH and VBScript to write the FinalSetup.vbs script in Listing 1. You’ll notice that I like a lot of white space and plenty of comments in a script. Comments make a script easier to read and can help you determine what the script does when you open it up months down the road. In many cases, I even retain, but comment out, any debugging code I added when I wrote and tested the script.

Chapter 8 Simplify Win2K Desktop Deployment


Listing 1
’**************************************************************************** ‘ TITLE: FinalSetup.VBS Ver 2.0 ‘ USE: Use this to add domain global groups to local groups after the ‘ computer has joined the domain (WIN 2000 Image). ‘**************************************************************************** OPTION EXPLICIT ‘Ensures variables are declared.

ON ERROR RESUME NEXT ‘Keeps script moving past errors. ‘**************************************************************************** ‘Declare variables. ‘**************************************************************************** Dim oGroup ‘ADSI object created by GetObject Dim oWshShell ‘Shell object to run executable (smsman.exe) Dim sInDomain ‘Used to test domain membership Dim sCompName ‘Local computer name Dim oWshNet ‘WshNetwork object Dim sUser ‘Username for error checking Dim sMember ‘Check for group membership 0 or 1 ‘**************************************************************************** ‘ Declare constants. ‘**************************************************************************** Const GLOBAL_GROUP = “WEBDEV” ‘Global group being added to local group Const LOCAL_GROUP = “Administrators” ‘Name of local group Const DOMAIN = “GONDOR” ‘Domain name ‘**************************************************************************** ‘ Create objects. ‘**************************************************************************** ‘Create network object. Set oWshNet = Wscript.CreateObject(“Wscript.Network”) ‘Create shell object. Set oWshShell = Wscript.CreateObject(“Wscript.Shell”) ‘**************************************************************************** ‘ Get local information. ‘**************************************************************************** sUser = oWshNet.Username ‘Get logon name of current user. SCompName = oWshNet.Computername ‘Get computer name. ‘**************************************************************************** ‘Confirm Administrator is logged on; quit if not. ‘**************************************************************************** IF UCASE(sUser) <> “ADMINISTRATOR” THEN Wscript.Echo “You must be logged in as “”ADMINISTRATOR”” to run this “ & _ “script! “ & vbCRLF & vbCRLF & “Log off and login as “ & _ “””ADMINISTRATOR”” to the local machine “ & “(“ & sCompName & “).” & _ vbCRLF & vbCRLF & “After logging in as ADMINISTRATOR, run the “ & _ “C:\WINNT\SYSTEM32\FinalSetup.vbs script again” & _ “ to finish the installation.” Wscript.Quit END IF ‘**************************************************************************** ‘Make sure the machine has been added to the domain; ‘if not, quit and display message. ‘**************************************************************************** ‘Get key value from registry. sInDomain = oWshShell.RegRead(“HKLM\SOFTWARE\” & _ “Microsoft\Windows NT\CurrentVersion\Winlogon\DomainCache\GONDOR”) IF sInDomain = “” THEN Wscript.Echo “The Computer does not belong to “ & DOMAIN & “ domain!” Wscript.quit END IF



Guide to VBScripting

Listing 1: continued
IF ERR.NUMBER = -2147024894 THEN Wscript.Echo “The Computer does not belong to “ & DOMAIN & “ domain!” Wscript.quit END IF ‘**************************************************************************** ‘ Modify group memberships. ‘**************************************************************************** Set oGroup = GetObject(“WinNT://” & sCompName & “/” & LOCAL_GROUP & “,group”) sMember = oGroup.IsMember(“WinNT://” & DOMAIN & “/” & GLOBAL_GROUP) ‘If a ‘member will return, 1. If not, 0. IF sMember = 0 THEN oGroup.add(“WinNT://” & DOMAIN & “/” & GLOBAL_GROUP) sMember = oGroup.IsMember(“WinNT://” & DOMAIN & “/” & GLOBAL_GROUP) ‘If a member will return, 1. If not, 0. IF sMember = -1 THEN oWshShell.Popup “The “ & GLOBAL_GROUP & “ global group has been “ & _ “successfully added to the “ & LOCAL_GROUP & “ local group.”, & _ 5, “Local Group Modified”, 064 End IF ELSE oWshShell.Popup “The “ & GLOBAL_GROUP & “ global group is already” & _ “ a member of the “ & LOCAL_GROUP & “ local group.”, 5, & _ “Global Group Exists” ,064 End IF ‘**************************************************************************** ‘Run smsman.exe to install SMS 2.0. ‘**************************************************************************** oWshShell.Popup “SMS 2.0 will now be installed. Please wait while” & _ “ program is loading...” & vbCRLF & vbCRLF & “The SMS Client” & _ “ Installation will take approximately 5 minutes to complete” & _ , 10, “SMS 2.0 Installation”, 064 oWshNet.MapNetworkDrive “N:”, “\\SMSCPS1\SMSLOGON”, , “guest”, “” oWshShell.Run “N:\x86.bin\00000409\SMSMAN.EXE”, ,TRUE oWshNet.RemoveNetworkDrive “N:” ‘**************************************************************************** ‘Display end-of-script message and exit. ‘************************************************************************** MsgBox “This portion of the setup has been completed. “ & _ “Logoff and login as the user and setup the printers.”

FinalSetup.vbs performs the final steps of the setup process before the installer turns a computer over to the end user. The code confirms that the person running the script is logged on as Administrator and that the computer has joined the domain. The script then adds the WEBDEV global group to the Local Administrators group on the computer to enable the Web developers to install software and configure their computers. The script’s first two statements are very important and should be in every script you write. The OPTION EXPLICIT statement requires you to declare variables before you can use them, thus helping you prevent errors in your scripts. Declaring variables is optional in VBScript. However, if you don’t use OPTION EXPLICIT, a typo in a variable name will create a new variable. Such an error might be easy to find in a simple script but not in one that contains hundreds of lines. The ON ERROR RESUME NEXT statement prevents your script from stopping if it encounters an error. The purpose of this statement isn’t to ignore errors but to let you deal with them gracefully so that they aren’t fatal to the script. FinalSetup.vbs’s next section declares variables. You can place all the variables on the same line, separating them with commas, or you can place them on separate lines, as I’ve done.

Chapter 8 Simplify Win2K Desktop Deployment


A variable name must begin with a letter and can have as many as 255 characters. The name can contain letters, numbers, and underscore (_) characters. Using a lowercase one-letter prefix to indicate the type of data (e.g., object, string, integer) stored in the variable is customary. The next section of the script declares constants, which are similar to variables except that you can’t change their values after you declare them. The script uses three constants: GLOBAL_GROUP, LOCAL_GROUP, and DOMAIN. To perform real work, a script needs to use objects, which have methods (functions that an object performs) and properties (characteristics). Before a script can use an object, it must instantiate (create) the object. Instantiating an object loads it into memory and registers it. In WSH scripting, you use the Wscript.CreateObject() function to instantiate an object. FinalSetup.vbs uses several objects that are built in to WSH. The next section of FinalSetup.vbs creates two objects: the Network object (Wscript.Network) and the Shell object (Wscript.Shell). The Network object lets the script connect to network resources. The Shell object lets the script run an executable, manipulate the registry, read environmental variables, create shortcuts, and perform several other functions. In each case, the script stores the created object in a variable (oWshNet and oWshShell, respectively) and uses the variable to access the object’s methods and properties. The local Administrator must be logged on to the computer for the script to be able to complete its tasks. To confirm that the user is the local Administrator, the script gets the value of the Username property of the Network object created earlier and stores the value in the sUser variable. The script retrieves the computer name from the Computername property and stores it in the sCompName variable. Then, the script uses the UCASE() function to convert the value of sUser to uppercase and compares the converted value with ADMINISTRATOR. If sUser’s value isn’t equal to ADMINISTRATOR, the script displays an error message and exits. When the user is the Administrator, the script continues to the next step, which ensures that the computer has joined the domain. First, the oWshShell.RegRead method reads the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Winlogon\DomainCache\GONDOR registry subkey and stores the value in the sInDomain variable. Then, the script performs two checks. First, it checks whether sInDomain is null (i.e., whether the registry subkey is empty). If it is, the script displays an error message and exits. Second, the script checks for the fatal error—2147024894, which occurs if the registry subkey doesn’t exist, and exits gracefully if the error has occurred. If the registry subkey weren’t present and the script didn’t include the ON ERROR RESUME NEXT statement that I mentioned earlier, the user would see the error message that Figure 1 shows. Next, the script adds the NT 4.0 domain global group to the Administrators local group on the computer. For this task, the script first uses Active Directory Service Interfaces (ADSI) to connect to the SAM database on the local computer. The script creates the oGroup object by using ADSI’s GetObject() function to bind to ADSI’s WinNT provider. After establishing the connection with the WinNT provider, the script uses the oGroup object’s IsMember method to verify that the domain global group (GLOBAL_GROUP) is a member of the Administrator local group. If the global group isn’t a member of the local group, the value of the sMember variable is FALSE (0) and the script uses the oGroup.Add method to add the global group to the local group. The script rechecks the value of sMember to ensure that it’s TRUE (-1), then uses the oWshShell.Popup method to display a status message and continues running after a 5-second pause.


Guide to VBScripting

Figure 1
Error message

We use SMS, so the last thing the script does is install the SMS client software on the computer. After displaying an informational message, the script uses the oWshNet.MapNetworkDrive method to map the computer’s N drive to the SMS server and the oWshShell.Run method to start smsman.exe. When smsman.exe has finished, the script disconnects the network drive and displays a final message.

Prepare the Disk Image
After you’ve created the master disk image, you use the Sysprep utility to prepare the image for duplication. The Sysprep utility comprises three files: sysprep.exe, setupcl.exe, and sysprep.inf. Sysprep.exe prepares the disk for cloning and runs setupcl.exe, which generates a unique SID on the cloned image. You can use the optional sysprep.inf file to automate Sysprep’s Mini-Setup Wizard process. During the first boot after cloning, the Mini-Setup Wizard will prompt you for any information sysprep.inf doesn’t provide, such as computer name, time zone, and domain membership. Sysprep also lets you copy a disk image to a computer that has a compatible hardware abstraction layer (HAL) but different hardware from the system that provided the image. The image must contain any drivers not in the C:\winnt\driver cache\i386\driver.cab file, and sysprep.inf’s [UNATTENDED] section must specify the location of the additional drivers. You can find a version of Sysprep on the Win2K Pro CD-ROM under \support\tools\deploy .cab, but a newer version is available at the Microsoft Web site at http://www.microsoft.com/windows2000/techinfo/planning/incremental/sysprep11.asp. Download the white paper “Automating Windows 2000 Deployments with Sysprep,” and read it before getting started. Appendix B of this white paper contains the commands you need to create sysprep.inf, and “Microsoft Windows 2000 Guide to Unattended Setup” (unattended.doc), in the \deploy.cab folder on the Win2K Pro CDROM, provides information about the syntax of the commands. Listing 2 shows the sysprep.inf file I created. Let’s look at each section and the actions that it tells the Mini-Setup Wizard to perform.

Chapter 8 Simplify Win2K Desktop Deployment


Listing 2
[Unattended] OemPreInstall = no OemSkipEula = yes KeepPageFile = 0 ExtendOemPartition = 1 [GuiUnattended] OemSkipWelcome = 1 AdminPassword = elyod[ TimeZone = 035 OemSkipRegional = 1 [GuiRunOnce] command01 = c:\winnt\system32\finalsetup.vbs [UserData] OrgName = “XYZ Communications” FullName = “XYZ User” [Identification] JoinDomain = GONDOR DomainAdmin = desktop DomainAdminPassword = eportsew [Networking] InstalldefaultComponents = no

In the [Unattended] section, the OemPreInstall = no line indicates that you’ll use a cloned disk rather than an unattended setup. OemSkipEula = yes suppresses the display of the End User License Agreement (EULA). KeepPageFile = 0 causes Win2K to regenerate the computer’s pagefile to accommodate any difference in RAM between the master computer and the target computer. ExtendOemPartition = 1 extends the system partition to fill the remaining space on the disk. Another option is to expand the partition by an amount you specify in megabytes (e.g., ExtendOemPartition = 75), leaving the unused disk space available for additional partitions. The Extend OemPartition feature works only on NTFS partitions. A parameter that I didn’t use but that’s worth mentioning is OemPnPDriversPath, which lets you add drivers not in the C:\winnt\driver cache\i386\driver.cab file. When the Mini-Setup Wizard detects new hardware on the target computer during installation, the wizard searches for the appropriate drivers in the driver.cab file, then, if necessary, in the location that the OemPnPDriversPath parameter specifies.

In the [GuiUnattended] section, OemSkipWelcome = 1 suppresses the display of the Welcome screen. AdminPassword = elyod[ sets the local Administrator password on the computer to the companywide standard password (elyod[) used for all NT systems. TimeZone = 035 sets the correct time zone for the computer (035 represents Eastern time). The unattended.doc file contains a table of the time zone codes. OemSkipRegional = 1 ensures that you’re prompted for any regional information.


Guide to VBScripting

The [GuiRunOnce] section causes the system to run commands after the Mini-Setup Wizard finishes by adding them to the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce registry subkey. In this case, command01 = c:\winnt\system32\finalsetup.vbs runs the script I created to finish the setup process.

In the [UserData] section, OrgName = “XYZ Communications” sets the company name and FullName = “XYZ User” sets the username. Enclose the names in quotes if they contain spaces, and use the same username for all computers. If you leave these parameters blank, the Mini-Setup Wizard prompts the user for the names. Likewise, if the Computername parameter is missing (as it is here) or empty, the Mini-Setup Wizard will prompt the user for the name. If you set the Computername value to *, the Mini-Setup Wizard will generate a random computer name.

In the [Identification] section, JoinDomain = GONDOR automatically adds the computer to the NT domain GONDOR. You must supply a username in DomainAdmin = and a password with sufficient rights in DomainAdminPassword = to add computers to the domain. I created the domain user account desktop and gave it the Add Workstations to Domain right. The sysprep.inf file contains network names and passwords in plain text, which might seem like a big security problem. However, the Mini-Setup Wizard automatically deletes the folder that sysprep.inf resides in on the target computer.

In the [Networking] section, setting the InstalldefaultComponents = parameter to no causes the Mini-Setup Wizard to leave the network stack alone. The master disk image has all the network settings, but if you find that you have machines with different NICs, you can set parameters in this section. Now that the FinalSetup.vbs script and the Sysprep utility are ready, you need to add them to the master disk image to prepare it for cloning. To add the files to the disk image, you must first copy them to the computer that you used to make the initial disk image, then create a second disk image. I created a batch file named prepsys.bat, which Listing 3 shows, to copy FinalSetup.vbs to the computer’s C:\winnt\system32 directory, copy the Sysprep utility and supporting files to the Sysprep directory on the system drive’s root, and run the Sysprep utility. When you run Sysprep with the /forceshutdown switch, the computer should shut down automatically. If it doesn’t, turn off the power manually when the hard disk light goes out. Don’t restart the computer yet. To make a second image of the computer’s disk, insert a network boot disk as before, boot the computer, and connect to the network share. Run the disk-imaging software and create the second disk image, giving it a new name. You’ll use the new image to clone workstations. When you load this image on a computer and boot the computer, the image will perform all the setup steps I’ve discussed. Save the first image in case you need to make changes.

Chapter 8 Simplify Win2K Desktop Deployment


If you do need to modify your master image, load the first master image onto a computer. Make any necessary changes to the configuration or applications, and clean the disk as I instructed earlier. Make any necessary changes to the FinalSetup.vbs script or sysprep.inf file, and use prepsys.bat to copy the script and the Sysprep files to the computer. Prepsys.bat will also run Sysprep to create a new disk image for cloning.

Listing 3
REM prepsys.bat @ECHO OFF CLS ECHO. ECHO. ECHO. ECHO. ECHO. ECHO. ECHO. ECHO. ECHO Preparing disk to run SYSPREP... ECHO. ECHO. ECHO. ECHO. copy a:\FINALSETUP.VBS c:\WINNT\SYSTEM32 MD c:\SYSPREP copy a:\sysprep.exe c:\sysprep copy a:\setupcl.exe c:\sysprep copy a:\sysprep.inf c:\sysprep c:\sysprep\sysprep.exe /forceshutdown

You can modify a disk that you’ve prepared with the Sysprep utility, but I don’t recommend doing so. When you booted the computer for the first time, the Mini-Setup Wizard would run and perform setup tasks. You would then have to “undo” the Mini-Setup Wizard’s work before making the modifications you needed. Making a mistake would be too easy.

Clone the Master Image to a Workstation
To clone the second master disk image to a target workstation, you can use one of three methods: Use a network boot disk to boot the workstation, then load the disk image from the network share; use a bootable CD-ROM that contains the image; or use a disk duplicator to copy the master disk to several hard disks at once. You might find that you need to use one method for some workstations and another method for other workstations. I think the most efficient approach is to use a bootable CD-ROM that contains the disk image. The CD-ROM lets you copy the image to a computer without network connectivity (which the network-share approach requires) and without removing the computer’s hard disk (which the disk duplicator requires). The CD-ROM (and disk-duplicator) approach also doesn’t consume network bandwidth. After you’ve loaded the disk image on the target workstation, restart the workstation. When the computer runs for the first time, Sysprep starts the Mini-Setup Wizard, which prompts the user for any information not in the sysprep.inf file. In my company’s case, the computer name was the


Guide to VBScripting

only information the user had to enter. The operator used the company’s standard naming convention to give the computer a unique name based on the end user’s name, department, and physical location. The Mini-Setup Wizard also adds an entry to the HKEY_LOCAL_MACHINE\SOFTWARE\ Microsoft\Windows\CurrentVersion\RunOnce registry subkey. Win2K will run the FinalSetup.vbs script when someone logs on to the computer the first time. Planning ahead can give network administrators and support staff a painless way to upgrade or reinstall a client’s computer in place in minutes. Disk-imaging software, WSH scripting, and Microsoft’s Sysprep utility are tools anyone deploying desktops should learn to use. In the time it would take you to install and configure a dozen desktops the old-fashioned way, you could master these new tools.

Sign up to vote on this title
UsefulNot useful