You are on page 1of 52

s & .

NET
o w

M
nd

ag
Wi

a z i n e ’s
Guide to
Automating
Administrative
Tasks
with
VBScript
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 consul-
tancy 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.
iv

Table of Contents
Chapter 1: Scripting 101: Declaring and Initializing Variables . . . . . . . . . . . . . 1
VBScript Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Declaration and Initializing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
VBScript Directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
VBScript Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Variable Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Constant Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Demo.vbs End . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Writing the Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
The Work Begins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Do It with a Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Create a Hard Disk Image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Write a FinalSetup.vbs Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Prepare the Disk Image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Unattended . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
GuiUnattended . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
GuiRunOnce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
UserData . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Identification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Networking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Clone the Master Image to a Workstation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
1

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 ini-
tialization 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 Direc-
tory 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 specifica-
tion, 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 com-
ments 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 line-
continuation marker when I initialize dynamic arrays (e.g., at callout A in Listing 1) or to pass a
2 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 vis-
ible line terminator (;) that JScript and Perl use.
The seventh and final ground rule I want to establish governs VBScript identifiers (e.g., vari-
ables, 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
’ *
‘ demo.vbs
‘ *
‘ Declaration and Initialization Section
‘ *
Rem *** Script Directives ***
Option Explicit
On Error Resume Next

Rem *** Variable Declarations ***


Dim objFileSystem, objShell, objLogFile, objTempFile
Dim arrStrings, intReturnCode
Dim 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”, _ A
“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. B
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.
Continued
Chapter 1 Scripting 101: Declaring and Initializing Variables 3

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
4 Guide to VBScripting

Declaration and Initialization


The primary elements that make up declaration and initialization are script directives, variable dec-
larations, variable initialization, and constant definitions. All these elements (with the possible
exception of initialization) are optional. However, using these language features can help you pro-
duce 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 exe-
cution. 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 encoun-
ters 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 rudi-
mentary 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 con-
tinues 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 5

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 pro-
vides 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. How-
ever, 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
6 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 exer-
cise 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 better-
designed 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 vari-
ables 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 declara-
tions 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 informa-
tion as you do in VB. Instead, VBScript determines a variable’s type at runtime, based on the vari-
able’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 com-
plex 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 sup-
ports 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 7

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 con-
stants 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 Refer-
ence 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 dupli-
cate 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 OpenFileFor-
Reading 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.
9

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 per-
form in the script body: working with objects.

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. Unfortu-
nately, 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 prolif-
eration 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.
10 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
Scripts
C:\>utilities OK
User Manager

Win32 API

Service
Event System Performance
Registry SAM Control
Log Data Data
Manager

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

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

Win32 API

Event Active Service System and


Registry SAM Control Performance
Log Directory
Manager Data

For example, in addition to abstracting multiple and disparate directory services, Active Direc-
tory 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 11

(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 Web-
Based 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 under-
stand 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 program-
ming languages generally classify objects in terms of a user, a group, a computer, a printer, a net-
work, 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 per-
form 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—CreateOb-
ject and GetObject—that you can use to create objects. Using these methods is a concept that rep-
resents 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 func-
tions. 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.
12 Guide to VBScripting

CreateObject
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, strServer-
Name 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 refer-
ence to an object variable so that you can interact with the object within your script. In the pre-
vious 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 demon-
strates 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 Cre-
ateObject. 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 Cre-
ateObject 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 Computer-
Name, 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 func-
tion. 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 refer-
ence 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 13

LISTING 1:
Objects.vbs

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 & _ A


“________" & 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”) B
‘ ^^^^^^^^^^ ^^^^^^^.^^^^^^^
‘ The object reference variable is first followed by strApp.strObj.

‘ * Here I use the more efficient VBScript CreateObject function.


Set objShell = CreateObject(“WScript.Shell”) C
‘ * 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)” & _ D
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 E
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.
Set objNetwork = Nothing
Set ObjShell = Nothing
14 Guide to VBScripting

Table 1:
Common CreateObject Application and Object Names

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

GetObject
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 15

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 objExcel = WScript.GetObject(“c:\users.xls”)


Set objExcel = WScript.GetObject(“c:\users.xls”, “Excel.Application”)
Set objNetwork = WScript.GetObject(“”, “WScript.Network”)
Set 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 Description
CreateObject 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)
GetObject 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
17

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”
18 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 19

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”
21

Chapter 4

Scripting Command-Line Switches


By Don Jones

Most Windows administrators rely heavily on running commands at the command line to accom-
plish 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 com-
mand-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
DisableUser.vbs

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
22 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 param-
eters 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 com-
mand-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 ana-
lyze 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 indi-
vidually. 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 23

script treats -s and -S the same. The code sets the sValue variable to the remainder of the argu-
ment 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 argu-
ment’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 com-
mand-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.
24 Guide to VBScripting

Listing 4
NewDisableUser.vbs

Dim oUser, oShareSvc, sServer, sUser, sUserDomain

Dim oArg, sSwitch, sValue A


‘ 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.”
25

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 Win-
dows 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.
26 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 27

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
Bulk-users.vbs

’ 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
Continued
28 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 29

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 informa-
tion 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 cre-
ated. 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 nec-
essary, 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 proper-
ties of the account, including the password, full name, description, home directory, and RAS per-
missions. 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 user-
name 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 per-
mits dialing in, while the value 1 denies it. You can look up these values in the ADSI documenta-
tion, 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; Direc-
tory 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
30 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 pass-
words 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 sGrou-
pList 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 dis-
plays 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 31

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 func-
tionality to achieve terrific results. I used ADO, the Scripting Runtime Library, and ADSI—three rel-
atively 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 com-
plex tasks much easier and how to start exploring ADO and ADSI to come up with custom time-
saving solutions.
33

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
Userlogin.vbs

’ 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.
34 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
35

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

’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
37

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 Sys-
tems Management Server (SMS) 2.0 and SMS 1.2 provide hardware and software inventory informa-
tion, 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
38 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 soft-
ware 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 worksta-
tion. 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 CD-
ROM 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 39

Listing 1
FinalSetup.vbs

’****************************************************************************
‘ 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
Continued
40 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 com-
puter 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 41

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 indi-
cate 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 instan-
tiate (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 environ-
mental 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 com-
plete 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\Domain-
Cache\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.
42 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 com-
puter. 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 infor-
mation 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/win-
dows2000/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 CD-
ROM, 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 43

Listing 2
Sysprep.inf

[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

Unattended
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., Exten-
dOemPartition = 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 OemPn-
PDriversPath parameter specifies.

GuiUnattended
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 com-
panywide 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.
44 Guide to VBScripting

GuiRunOnce
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\CurrentVer-
sion\RunOnce registry subkey. In this case, command01 = c:\winnt\system32\finalsetup.vbs runs
the script I created to finish the setup process.

UserData
In the [UserData] section, OrgName = “XYZ Communications” sets the company name and Full-
Name = “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 Com-
putername value to *, the Mini-Setup Wizard will generate a random computer name.

Identification
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 suffi-
cient 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.

Networking
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 45

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
Prepsys.bat

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 net-
work-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
46 Guide to VBScripting

only information the user had to enter. The operator used the company’s standard naming conven-
tion 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.

You might also like