The COM technology behind objects such as the Microsoft Scripting Runtime Library's FileSystemObject object (aka the FSO object) and Windows Script Host's (WSH's) WshShell object is what makes WSH scripts so powerful. However, the way this technology works causes scripters grief in one area: Scripts don't get access to the crucial constants embedded in objects.
If you use the FSO object's Open-TextFile method to open a file for writing or appending, you might have run into this problem. In its simplest form, this method takes one mandatory parameter—the name of a file—and opens that file so you can read it. For example, the code
Dim fso Set fso = CreateObject( _ "Scripting.FileSystemObject") Set file = fso.OpenTextFile( _ "C:\temp\computers.txt")
opens C:\temp\computers.txt for reading. The OpenTextFile method has three optional parameters, one of which is the I/O mode. When you look at the documentation for using this parameter, you'll notice that it can take one of three constants: ForReading, ForWriting, or ForAppending. However, if you try to use ForAppending to open and add data to the file with code such as
Set file = fso.OpenTextFile( _ "C:\temp\computers.txt", _ ForAppending, True)
VBScript will immediately give you the runtime error Invalid procedure call or argument. If you're a cautious scripter who uses Option Explicit to make sure all variables are defined, you get an even more puzzling but more accurate error message: Variable is undefined: 'ForAppending'. The problem is that you're using ForAppending as a constant, but the VBScript engine sees it as a variable. You'll receive similar error messages when you use ForReading and ForWriting.
Why do scripters have this problem when programmers who use compilers don't? To answer that question, you need to understand a little about type libraries. Microsoft defines a type library as, "A file or component within another file that contains type information about exposed objects." The FSO object is a type. Every object you create in a script is an instance of a type, which you load from a type library. Constants are special types called enumerations, which are also in type libraries.
When you use VBScript's CreateObject function with the FSO object, you're dealing directly with a type within a specific type library. You have no direct knowledge of the type library or any of its contents other than that used to create the instance of the FSO object. Languages that use compilers, such as Microsoft Visual Basic 5.0 (VB 5.0), VB 6.0, and Visual Basic for Applications (VBA), provide a way for programmers to bind early and connect to the entire type library. Microsoft Office applications can provide this information if the programmers add a reference to the library. However, if programmers choose to use the CreateObject function in VB 5.0, VB 6.0, or VBA, they have the same limitations that scripters do: Values for constants such as ForAppending aren't automatically available in the code. Fortunately, there are several ways to work around these limitations in scripts.
There are two standard solutions to the constants problem. You can do research to find the integer values associated with the constants, or you can use a .wsf file.
Researching the values. The values associated with constants' names are usually documented somewhere. For example, if you look in the Windows Script (WS) 5.6 Help file (script56.chm), you'll find that the values for the I/O constants ForReading, ForWriting, and ForAppending are 1, 2, and 8, respectively. After you find the values, you can then simply define the constants as those values in your script, with code such as
Const ForAppending = 8
This solution works fairly well for objects that have few constants and that are well documented. But it doesn't work quite as well for other objects. Sometimes finding clear, accurate documentation for constants can be difficult.
If you use a type library browser, you already know a better documentation source: the component itself. Most type library browsers show you the values for enumerated constants. Unlike the information you might get from static external documentation, these values are always correct because the type library browser obtains them from the component on your system. Microsoft Office applications include an object browser as part of their VBA macro-editing support. In the sidebar "Formatting the Reports" (May 2003, InstantDoc ID 38402) from the article "AD and WMI Reporting" (InstantDoc ID 38401), you'll find an example of how to use Microsoft Excel's object browser to look for constants' values.
Using a .wsf file. Microsoft added .wsf files to WSH 5.6. In these files, you can use the <reference> element. This element automatically connects to the specified type library and loads the constants' values for you. For example, to have your script automatically access the constants defined in the Scripting Runtime Library, you simply insert the following code inside the job containing the scripts that need the constants:
<reference object = "Scripting.FileSystemObject"/>
Although the <reference> element is easy to use, this solution has a few limitations. First, if you add this element but don't have the referenced object installed on the system, the script will error out before execution even starts. Another limitation is that you don't get a chance to see the values associated with the constant names. Thus, you don't get a chance to learn or document them.
A Better Solution
Because constants' values are in a type library, you can use the TypeLib Information Objects (aka TlbInf32) library to get the information. The nickname TlbInf32 comes from the original name of the file containing this component: tlbinf32.dll. TlbInf32 is itself a component, which means you can easily call it from script. You can then use it against files or even objects currently being used in a script.
Office 2000 and VB 6.0 include TlbInf32. (TlbInf32 isn't shipped as a part of Windows and, to my knowledge, has never been distributed separately by Microsoft.) Visual Studio .NET 2002 and later uses the file as well, but Microsoft renamed it to vstlbinf.dll.
In most cases, you don't need to install TlbInf32 everywhere, just on one machine that has the components you need to inspect. Although you can browse for tlbinf32.dll or vstlbinf.dll, the simplest way to check for TlbInf32's existence is to try to call it with the code
Dim tla Set tla = CreateObject( _ "Tli.TliApplication")
If this code works, TlbInf32 is correctly installed and functioning. If not, make sure TlbInf32 is present on your system-and register it when you find it. If you have more than one copy of TlbInf32, make sure you register the most recent version.
Lots of object browsers use TlbInf32. Some browsers are specifically designed for scripters. Object browsers typically work by opening a type library and looking at the interfaces it describes. Simplifying a bit, interfaces act as doorways to the objects you can create in scripts, so when you look at a particular interface, you can see the methods and properties of any objects you would create from it.
This process is the reverse of how a script works. Scripts usually start by creating a reference to a particular object rather than a particular interface. Although it's more work, you can still use TlbInf32 in a script. Listing 1 demonstrates how with the FSO object. In this code, the tla variable contains a TLIApplication object. Because TLIApplication can access other objects currently being used in a script, it can provide you with the interface through which it was called, as callout A in Listing 1 shows.
As I mentioned earlier, type libraries contain interfaces. Because the FSO object was created directly through an interface in the type library and because an interface always knows its parent, you can get the parent of the interface, which is the type library, or TypeLibInfo object. In Listing 1, the tlb variable contains a TypeLibInfo object. This object has a Constants property, but the name is poorly chosen. The Constants property refers to a collection of enumerations of constants because constants exist in groups within the type library. For example, ForAppending, ForReading, and ForWriting are all members of an enumeration that's a single element of Constants.
An ill-chosen name isn't all you have to worry about with Constants. This property has a lot of redundancy. For example, the Constants property contains enumerations of hidden constants, which are constants that typically just mirror the exposed constants. So, you need to filter out the hidden constants. Fortunately, the hidden constants all exist in enumerations whose names begin with an underscore (_), so you can search on that character to find and remove them.
After you filter out the hidden constants, you'll have the true constants, which are referred to as members of an enumeration. Each member is itself an object, with its own properties. Two useful properties are the Name and Value properties. By calling these properties, you can obtain the constants' values. Although the Constants property is complex, the code to filter out the hidden constants and show only the Name and Value properties' contents is compact, as callout B in Listing 1 shows.
The code in Listing 1 returns a lot of information—more than 30 constants in seven different enumerations. That output includes the values for the desired constants:
ForReading = 1 ForWriting = 2 ForAppending = 8
After you generate the constants' values, you can save them in a file and use them in your scripts.
TlbInf32 definitely makes documentation easier. When I ran the code in Listing 1 against Microsoft Word, 2771 constants from 270 enumerations were passed to me in seconds. I certainly don't need all that information, but if I'm going to write a script that accesses Word and need a constant's value, I can quickly look it up.
The Best Solution
Although TlbInf32 is a great tool for exploring COM objects, you shouldn't have to write custom code to get constants' values every time you want to use them in a script. For that reason, I wrote Export-Enum.wsf, which lets you find out an object's constants and associated values. All you need to do is provide the object's programmatic identifier (ProgID) or the name of the library in which the object resides.
The code in Export-Enum.wsf is similar to the code in Listing 1, except that it has additional code that helps with error handling, output handling, and shutting down applications. I added output-handling routines so that the constants' values are outputted as VBScript constant declarations (e.g., Const ForAppending = 8). You can immediately use the constant declarations by copying and pasting them into a script. The outputhandling routines also add a header so that you know what object the constant declarations are for.
I added code that shuts down the object's application because there are no standards for how applications that are also COM servers should act when you stop using them. Some applications, such as Excel, will shut down if they're invisible and have nothing to display. Other applications, such as Word, will continue to run invisibly. As the code excerpt in Listing 2 shows, Export-Enum.wsf tries to make an application exit by first invoking a generic Quit method, then setting the application's object reference to Nothing. Be aware that these techniques might not work in all cases. COM objects that live in .dll and .ocx files will always quit when the running process exits, but out-of-process executables such as Word might not. For true out-of-process executables, you might want to watch your process list in Task Manager while starting and exiting the script to determine whether the new application is left running hidden.
Export-Enum.wsf is a console script, which means you launch it from a command-shell window using CScript. Alternatively, you can invoke the wrapper script Export-Enum.cmd if you want to run Export-Enum.wsf under WScript. You can find both Export-Enum.wsf and Export-Enum.cmd on the Windows Scripting Solutions Web site. Go to http://www.windowsitpro.com/windowsscripting, enter 48494 in the InstantDoc ID text box, then click the 48494.zip hotlink. Using Export-Enum.wsf is simple. Suppose you want to see the constants available for the WshShell object. You just run the command
and Export-Enum.vbs will generate a long list of constant declarations, which looks like
' Constants from 'WScript.Shell' Const WshRunning = 0 Const WshFinished = 1 . . .
Because VBScript parses its files completely and catalogs all constants before running a script, you can append the constant declarations to the end of an existing .vbs file just by using the command shell's redirection (>>) operator in a command such as
Export-Enum WScript.Shell >> MyScript.vbs
If you want to use a filename instead of a ProgID to specify the target object, you use the /F switch. For example, to export Word's constants by using the ProgID, you'd run the command
If you want to use the filename, you'd include the /F switch followed by Word's TypeLib file (msword.olb) in the command
Export-Enum /F msword.olb
When you use the /F switch, you don't need to worry about hidden applications left running because Export-Enum.wsf binds directly to the library to get the constants and their values.
The Choice Is Yours
In scripting, there are usually many ways to achieve the same end. That's certainly the case when you need to find out the values for constants. Besides researching documentation, using a .wsf file, and using TlbInf32, you now have another option from which to choose: Export-Enum.wsf.
Alex K. Angelopoulos ([email protected] hotmail.com) is a senior network engineer who does IT consulting work in Indiana. He is an MCSE, an MCP+I, and an MVP.