If It Moves, Script It

 

Andrew Clinick
Microsoft Corporation

June 14, 1999

Contents

What is WSH?
XML, Whatever: What About Some Cool Stuff?
Visual Basic User Bonus
Administering Windows with WSH
Built-in Objects
Registry Access
File System
ADSI
WMI
Summary

If you are using Visual Basic® Scripting Edition (VBScript) or JScript® today, chances are you're using them in either Microsoft® Internet Explorer or with Active Server Pages (ASP) technology. But these are not the only places to use these languages. Any application that supports Microsoft Windows Script can use these languages. Microsoft Outlook and Microsoft Exchange, for example, allow you to use VBScript in their applications. Another application that uses the VBScript and JScript engines is the Windows Script Host (WSH). WSH is a set of batch files for Windows that allows you to execute script either from the command line or as a full-blown Windows application. This means that you can now write logon scripts, back up routines, or administrative scripts in a "real" language (yes, we support Perl!), instead of having to cope with the existing batch script language. This article will focus on the new features in WSH 2.0 (currently in beta release only), and on how you can use those features to help you automate your life in Windows.

What is WSH?

Windows Script Host 2.0 is a language-independent host that allows you to run any script engine on the Windows 9x or Windows NT® operating system. In WSH 1.0, this was accomplished by looking at the file extension, deciphering the script engine associated with that file, loading the file, and running the code. For example, backup.vbs would be run with the VBScript script engine, and backup.js would be run with JScript. Without a decent object model, running script was a nearly useless exercise, since the script engines themselves had no way to connect to the operating system. This was meant as a security feature, allowing the script engines to be hosted in applications where safety and security were an absolute requirement. Internet Explorer is the best example of this: You don't want an errant Web page from drevil.com formatting your hard drive or stealing your mojo (a la Austin Powers). To address this, WSH provides an object model to every script, enabling you to get the basic information about running that script in the system. I'll go into the object model in more depth later, but a good example is that it provides an arguments collection containing all the arguments passed to the script.

WSH 1.0 was a good start—but in all honesty, determining that the file should be in WSH by relying on the file extensions wasn't a very effective technique. It meant that whenever a new script engine was released, the script engine vendor had to find a new extension that was applicable to the language name and that hadn't yet been used. We ran into this problem when we introduced Script Encoding to VBScript and JScript in Version 5.0. We settled, in the end, for .vbe (meaningVBScript encoded), and .jse (meaning JScript encoded). But it took considerable time to find an extension that wouldn't wreak havoc with a Microsoft product, to say nothing of third-party products. Relying on a file extension made it very difficult for tools vendors to support WSH: Was a .js file meant for WSH, or HTML, or ASP technology? Or all three? Such confusion led the tools group to opt for HTML, making WSH authoring more or less impossible.

The confusion felt by script-engine and tools vendors paled in comparison to that of WSH users. The very vibrant WSH community let their feelings be loudly known on the WSH newsgroup This forum really helped the Windows Script team get a good sense of what users were looking for in the product. One of the most common questions centered on how to include common functions into a script. Including common functions had been impossible in WSH 1.0, since it relied on a text file that contained only valid script code. Another common request was to give programmers the ability to use multiple languages in one file. This might sound like an esoteric request, but it sprang from a surprisingly common problem. Many programmers were creating functions using a particular script language - Perl, for example—yet most of their development was done using a different language—say, VBScript. Since WSH didn't support multiple languages, they were forced to rewrite all their Perl code just to be able to use WSH and VBScript. This problem was even worse when the original author of the code was no longer around to explain its structure.

In addition to the run-time object model, WSH provides a set of objects that make Windows scripts easier to write:

  • Network object: Provides access to common networking tasks, for example. connecting a network drive
  • Shell object: General purpose object that provides useful methods for manipulating Windows Operating System information —for example, registry access
  • Special folders: Access to all those special folders in Windows (My Documents, My Pictures, and so forth)

These objects are not designed to be the ultimate mechanism of accessing information on your machine, but rather a simple abstraction of commonly used tasks. Windows Management Instrumentation (WMI) and Active Directory Services Interfaces (ADSI) should be used for higher-level information about your network and local machine. These objects models provide a scriptable interface for nearly all the information on your machine. I'll cover these and how to use them in WSH below.

WSH 2.0

WSH 2.0 was designed to meet our top ten user requests:

  1. Support for include files
  2. Support of multiple engines
  3. Enhanced tools support
  4. Enhanced debugging
  5. Access to typelibrary constants
  6. Mechanism to pause a script
  7. Stdin/stdout and stderr support
  8. Enhanced logon script capabilities
  9. Drag and Drop support
  10. Quality is a feature you know! (aka "no more GPFs")

To address these issues, we took a look at what we could do within the existing file format and the software. Some of the run-time features (enhanced debugging, drag and drop support, and so forth) could be addressed simply by updating the WSH programs. But providing support for the top three requests required more information than could be provided by plain script alone. In essence, we needed some meta information in the file—for example, the script language name. We could have invented our own file format, but we chose XML because of its quality and popularity. Since we already had an XML parser that would host multiple script engines in the form of the Windows Script Component (WSC) at run time, it was an easy decision.

We looked at the XML schema for WSC, removed the bits that didn't make sense in WSH (registration information, for example), and came up with a subset that enables you to share a common XML schema between WSH and WSC. I hope this will make everyone's life a little easier, since it's now necessary to learn just one format. The XML file format provides the ability to do includes, multiple engines, type library constants, multiple jobs within one file, and tools support. It does this through some simple XML elements:

<script> Element

Very similar to the <SCRIPT> element you are familiar with in HTML and ASP, <script> enables you to specify the language and a src for the script code. For example:

<script language="VBScript">
msgbox "Hello World"
</script>

<script language="VBScript" src="utils.vbs"/>

<object> Element

With the <object> element, you can create an object for use within the script code. You have the option of responding to events from the object, and also of getting those constants provided by the object. For those unfamiliar with constants, they help with calling methods in the object. Suppose, for example, that you have an e-mail object with a send method. The method has an argument that allows you to set the priority of the e-mail. This argument expects a number that is used to determine the priority, but the developer of the object assumed that the constant for the high priority should be 0, normal 1, and low 2. When a programmer unfamiliar with the original code looks at the code, the original priority values won't be clear, and he will have to create a variable with a readable name, such as intHighPriority. This process is okay for one or two constants, but when you're using complex objects with thousands of constants, the process quickly becomes unmanageable. Luckily, most objects provide these constants in their type libraries; in WSH 2.0, all you have to do is add the reference attribute to the object element and you'll get the reference as well as an instance of the object.

<reference> Element

Sometimes you want to get type library information without having to create an instance of the object—for instance, if you have a CreateObject in your script code. The <reference> element allows you to do exactly this.

<job> Element

The <job> element is the parent element for the <script>, <object>, and <reference> elements. If you have a simple WSH script, you need just one job element. The <job> element also allows you to have multiple jobs in one file. Why would you want this? If you're a network administrator, you probably have a number of logon scripts for each type of user (admin, finance, user, and so on). Being able to have multiple jobs in a single file means that you can have just one logon file that contains a job for each type of user. You can access the job by setting the job run-time switch on the WSH run-time program. Logon.ws //job:admin, for example, will run the admin job.

Example:

<job>
<script language="VBScript">
msgbox "Hello World"
</script>
</job>.

XML, Whatever: What About Some Cool Stuff?

The question I get asked most about the new file format is "Do I have to do all that XML stuff?" The answer? Only if you want to use the new features it provides. If you're happy with a single language (no includes, no type libraries) then a plain old .vbs or .js file is probably the best solution for you.

The new file format helps, but it's only part of the new functionality provided by WSH. The rest is provided by enhancing the existing object model. The enhancements primarily help with areas that are not covered by other object models in Windows. We are often asked how to determine the user group to which a user belongs, primarily by logon script users for whom the information is vital. This information is made available with ADSI—so rather than reinvent the wheel for WSH, we let ADSI do this work. There are still some major areas that need to be addressed, though, such as having the ability to communicate with other programs. In WSH 1.0, you were pretty much restricted to applications that supported COM. This might not seem like a big restriction (since most Windows applications support COM), but what about all those command-line utilities and older applications that don't support COM? There still exist a considerable number of such applications out there, and they are used extensively by administrators.

To address these needs, WSH 2.0 adds support for stdin, stdout, and stderr streams. This enables users to pipe between scripts and other applications, as well as allowing fuller control over the command-line environment. We spent some time choosing the best way to provide for all of these streams, and the process led us to the FileSystemObject. The FileSystemObject provides the ability to manage files on your system and also open up text files. The text file support is based on a streaming model, enabling you to open a text file as a stream and then either read the entire file or get at the stream line by line. These mechanisms worked well with text files; given that the stdin/out/err are largely text-based, it made sense to apply the same object model for those streams. In WSH 2.0, you can get the stid object by referencing the stdin property on the WScript object. Once you have the reference, you can then treat it as a text stream in FileSystemObject.

Example:

'==========================================================================
'
'
' NAME: VbsFilter.vbs
'
' COMMENT: Sorts through a directory listing and filters out VBS files
'
' USAGE: dir | cscript vbsfilter.vbs
'
'==========================================================================

Dim StdOut, StdIn

Set StdOut = WScript.StdOut
Set StdIn  = WScript.StdIn

Dim InputString
Do While Not StdIn.AtEndOfStream

   InputString = StdIn.ReadLine
   If 0 = StrComp(".vbs", Right(InputString, 4), vbTextCompare) Then
      StdOut.WriteLine InputString
   End If
Loop

Visual Basic User Bonus

The stdin/stdout/stderr support is actually implemented in the FileSystemObject, so you can now access stdin/stdout/stderr from any Visual Basic program. As a Visual Basic programmer, I've been waiting for ages for this functionality. As an added bonus, you also get a regular expression object with VBScript 5.0 that can be used in Visual Basic. Just install Beta 2 of WSH 2.0 and you're set.

WSH 2.0 also supports the ability to talk to older Windows applications via the introduction of the sendkeys method. This works in the same way as Visual Basic does, and allows you to send keystrokes to a named application running on your machine. This is useful if you want to automate an older setup program; for example, you could write a logon script that launched the setup program, and then send a bunch of keystrokes to it to automate the setup process. This has been added to the Wshell object.

Example:

'==========================================================================
'
'
' NAME: Calculater.vbs
'
' COMMENT: Sends keystrokes to calc.exe
'
'==========================================================================


Dim WshSHell
set WshShell = CreateObject("WScript.Shell")
WshShell.Run("calc")
WScript.Sleep(100)
WshShell.AppActivate("Calculator")
WScript.Sleep(100)
WshShell.
SendKeys("1{+}")
WScript.Sleep(500)
WshShell.
SendKeys("2")
WScript.Sleep(500)
WshShell.
SendKeys("~")
WScript.Sleep(500)
WshShell.
SendKeys("*3")
WScript.Sleep(500)
WshShell.
SendKeys("~")
WScript.Sleep(2500)

For more information on these features and the other new features in WSH 2.0, check out the documentation on the Microsoft Scripting Web site.

Administering Windows with WSH

Now that you've had a quick run-through of the new features in WSH, it's about time we got to some more detail about how you can use it for administering your Windows workstation or server. To illustrate what you can do with WSH, I'll cover the built-in WSH administration object, ADSI, and WMI. ADSI and WMI provide an incredibly rich object model, so I won't even attempt to explain them fully. If you want more complete information, go to the Windows 2000 Server Site and theWindows NT Server Site.

Built-in Objects

Network

WSH provides a simple network object with a set of methods and properties that cover the basics of the networking features in Windows. It's not designed to provide all networking features—just those that are commonly used in logon scripts. If WSH doesn't provide the features you need, using ADSI would be your best bet. If you are writing a logon script, it's likely that you will want to attach to network drives and printers. In a command logon script, this would be something like the following:

Net use u:
Net use lpt1: \\servername\sharename
Echo "you are now connected to the network printer and file share"

This would translate to this in WSH:

' Create reference to the Wscript network object
set WshNetwork=CreateObject ("Wscript.Network")
' Call the addprinterconnection method
WshNetwork.AddPrinterConnection "lpt1",
' Attach u: to \\servername\sharename
WshNetwork.MapNetworkDrive "u:", "\\servername\sharename"
' Tell the user we're all done
WScript.Echo "you are now connected to the network printer and file share"

Those of you familiar with Windows Printers and WSH 1.0 will realize the shortcomings of the addprinterconnection method: It requires the user to provide a printer port. This is not required in Windows, and in WSH 1.0 this printer wouldn't show up properly in the Printer folder. In WSH 2.0, we have added the addWindowsPrinterConnection method which adds a real printer to windows.

Registry Access

When you've tried administering Windows with tools and objects and you've reached a point where they don't do exactly what you want, the last resort is to edit the registry. To provide access to the registry, WSH provides a set of Registry methods on the WScript shell object.

Example

set WshShell=WScript.CreateObject ("WScript.Shell")
WshShell.RegWrite "HKLM\ Hardware\Description\System\ CentralProcessor\0\VendorIdentifier", "Genuine Intel"
WshShell.Popup WshShell.RegRead("HKLM\ Hardware\Description\System\ CentralProcessor\0\VendorIdentifier")

File System

Just about every administration script needs to access the file system at some point. WSH uses the Scripting Run-times FileSystemObject (FSO) to do this. FSO is a pretty comprehensive object that allows you access the drives, directories, and files on your machine and get information about each of them. This allows you to verify that there is enough disk space on a user's machine before trying to install a program by checking the AvailableSpace property on the Drive object. If you're needing more specific information on the FileSystemObject, you'll find an in-depth tutorial on the Microsoft Windows Scripting Technologies Web site One new feature that has been added for WSH 2.0 is the getFileVersion method, which returns the version information about a file. This information is very useful for checking to see whether your users have the right version of a .dll or .exe file.

Examples

Next available drive

set objFS=CreateObject ("Scripting.FileSystemObject")
set colDrives=objFS.Drives
letter=Asc("c")
while objFS.DriveExists(Chr(letter)+":")
   letter=letter+1
wend
Wscript.Echo "use "+Chr(letter)+":"

Reading a text file

set objFS=CreateObject ("Scripting.FileSystemObject")
set listFile = objFS.OpenTextFile ("c:\windows\system\oeminfo.ini")
do while listFile.AtEndOfStream <> True
   Wscript.Echo listFile.ReadLine
Loop

ADSI

Active Directory Service Interfaces (ADSI) abstract the capabilities of various directory services from different network vendors to present a single set of directory service interfaces for managing network resources. Administrators and developers can use ADSI to manage the resources in a directory service, no matter which network environment contains the resource. ADSI enables administrators to automate common tasks such as adding users and groups, managing printers, and setting permissions on network resources.

What this means for WSH users is that you can now pretty much query any network resource for information straight from script, without having to rely on custom C++ or Visual Basic objects. A good example of when to use ADSI is a simple add-user script. Utilizing ADSI, your script would look something like this:

'  Substiture your companies organization structure here
set objDS=GetObject("@Namespace!Company \TopOU\NextLowerOU")
' Call the create method
set objUser=objDS.Create ("User","JaneDoe")
' Set the users password
objUser.AccountRestrictions.SetPassword ("password")
' Commit the changes
objUser.SetInfo

I've included a more complete example that will allow you to create multiple users at a time, so you can look at a more real-world example.

WMI

On the WMI marketing Web site, I found the following product description:

  • "The Windows Management Instrumentation (WMI) technology is an implementation of the Desktop Management Task Force's (DMTF) Web-Based Enterprise Management (WBEM) initiative for Microsoft® Windows platforms that extends the Common Information Model (CIM) to represent management objects in Windows management environments."

In English, that means that you now have a scriptable object model that allows you to access just about all the information about your machine from the BIOS to the display adapter information.

Example:

' Display the RAM size in the machine
strQuery = "Select TotalPhysicalMemory From Win32_LogicalMemoryConfiguration"
'Now execute the query.
Call ExecuteQuery(objService, strQuery)
Private Sub ExecuteQuery(objService, strQuery)

    ON ERROR RESUME NEXT

    Dim objEnumerator, objInstance, strMessage

    Set objEnumerator = objService.ExecQuery(strQuery)

    For Each objInstance in objEnumerator
        If Err.Number Then
            Err.Clear
        Else
            If objInstance is nothing Then
                Exit Sub
            Else
                strMessage = Space(6) & objInstance.TotalPhysicalMemory & " KB"
                Wscript.Echo strMessage
            End If
        End If
    Next

End Sub

WMI is a powerful service, but it can be fairly complex when you're just getting started. Once you understand the basic model, though, it's relatively simple to develop very powerful scripts that can do things like remotely reboot machines or kill processes.

Summary

Windows Script Host 2.0 is our first attempt to develop a great scripting platform for administering Windows. We think we've found a good balance between power and complexity with this release, and most importantly, we've fixed many of the problems that plagued version 1.0. Beta 2 is being released with this article. Check out the new features and give us your feedback on how we're doing and where we should be going in the future. The final version will be released with the official release of Windows 2000, but will also be available for Windows NT 4.0, Windows 95, and Windows 98.

 

Scripting Clinic

Andrew Clinick is a Program Manager in the Microsoft Script Technology group, so chances are, if there's script involved, he's probably had something to do with it. He spends most of his spare time trying to get decent rugby coverage on U.S. television and explaining cricket to his new American colleagues.