A key technology that Microsoft has delivered in the past couple of years is Windows Installer. This technology is significantly more versatile than the setup.exe programs that come with applications. You can use Windows Installer to not only install but also configure, repair, and delete applications. In time, setup.exe installations will become obsolete, so learning how to use Windows Installer and write scripts that configure and modify it makes sense.
The technology comes in two parts: the Windows Installer service and a Microsoft Installer (.msi) file—typically one per application—that contains the components and features of the product being installed. In the same way that Windows Script Host (WSH) interprets and services .wsf files, the Windows Installer service interprets and services .msi files. In other words, the Windows Installer service is a registered server for files with the .msi extension.
The Windows Installer service is part of Windows 2000, but you can install it on Windows NT 4.0, Windows Millennium Edition (Windows Me), and Windows 9x systems. You can download the Windows Installer service, various software development kit (SDK) elements, and sample VBScript files from http://msdn.microsoft.com/downloads/sdks/platform/wininst.asp. Most commercially available .msi files (such as those with Microsoft Office 2000—the first application to ship with this technology) will autoinstall the Windows Installer service if it isn't already present.
In the next few columns, I'll show you how to write scripts that configure and modify .msi files so that you can change the behavior of the installation and autorepair options for your applications. From this point on, I assume that you have the Windows Installer service installed and the necessary .msi file on hand. If you're unfamiliar with the Windows Installer technology, I encourage you to check out these resources:
- "Windows Installer Service Overview," http://www.microsoft.com/windows2000/library/howitworks/management/installer.asp
- Mike Kelly, "Gain Control of Application Setup and Maintenance with the New Windows Installer," Microsoft Systems Journal, September 1998, http://www.microsoft.com/msj/defaulttop.asp?page=/msj/0998/windowsinstallertop.htm
- "The Windows Installer Service," http://www.microsoft.com/technet/win2000/win2kpro/prodfact/wispro.asp
- Application Specification for Microsoft Windows 2000 for Desktop Applications: Design Guide for Building Business Applications, "Chapter 2: Windows Installer Service," http://msdn.microsoft.com/library/default.asp?url=/library/specs/w2kcli_chapter2.htm
Automating the Database
The key part about the Windows Installer technology is that, unlike setup.exe files, you can configure .msi files through a COM automation interface. The .msi files are configurable because they're OLE-structured files that contain a database of relational tables. The purpose of such tables as Feature (i.e., a table that shows the features the product installs) and Component (i.e., a table that shows the components the product installs) is fairly obvious. The purpose of other tables, such as Class (i.e., a table that shows the information about installed COM automation servers) and InstallUISequence (i.e., a table that collects information from the user before installation), is less obvious.
Here's how you can create a script to configure an .msi file to list all the applications currently registered on your system. I say registered rather than installed because you can use Win2K Server's Group Policy Objects (GPOs) to advertise applications to Win2K users and computers. Consequently, the Windows Installer service displays not only installed applications but also advertised applications. Listing 1 contains the VBScript file RegisteredApps.vbs you're creating.
Listing the Registered Applications
To work with any .msi file, you first need to create an instance of the WindowsInstaller::Installer object, the COM object that represents Windows Installer. As callout A in Listing 1 shows, you use VBScript's CreateObject function to create an instance of this object and assign that instance to msiObject. You then use msiObject to access the various methods and properties of the WindowsInstaller::Installer object's automation interfaces. (You can find links to information about the various automation interfaces at http://msdn.microsoft.com/library/default.asp?url=/library/psdk/msi/auto_8uqt.htm.)
Using msiObject, you can access and query the Windows Installer service about the registered applications on your system, but first you need to decide on the data you want to display about each application. To keep the script simple, let's display each application's globally unique ID (GUID), name, and version.
To retrieve this information, you use the Installer::ProductInfo property, which returns various values based on parameters you pass to it. Because a system can have any number of registered applications, using this property in a For Each...Next loop is best. (To use a For...Next loop, you would need to know how many applications are registered.) To refer to the collection of applications you'll obtain, you use the Installer::Products property. This property returns a StringList object that holds the GUIDs for all the products installed or advertised for the current user and current machine. The StringList object is a collection of strings that behaves like a typical collection, so it has the StringList::Count and StringList::Item properties as part of its interface.
Callout B in Listing 1 shows the For Each...Next loop. In each cycle of the loop, you use the msiProduct variable to select an application from the collection of applications (i.e., msiObject.Products). You then access the GUID, name, and version from the application that msiProduct holds.
GUID. You access the GUID by simply referencing the msiProduct variable. In other words, the code
retrieves the application's GUID. This simplicity is possible because msiObject.Products represents a list of all the applications' GUIDs and msiProduct represents one of those applications in each cycle of the For Each...Next loop. Thus, referencing msiProduct is equivalent to calling StringList::Item to retrieve an application's GUID.
Name. To access the application's name, you call the Installer::ProductInfo property and pass in two parameters. The first parameter needs to be the application's GUID, so you can pass in msiProduct, which represents that GUID. You use "ProductName" as the second parameter to retrieve the name. In other words, the code
retrieves the application's name.
Version. Retrieving the application's version number is easy, but you need to mathematically manipulate that number to obtain the desired format. To retrieve the version, you use the same code you used to retrieve the name, except you specify "Version" instead of "ProductName" as the second parameter:
The Installer::ProductInfo property returns a raw version number, such as 167774362. In this format, the version is of little use, so you need to perform a bit of mathematical wizardry on the numbers to obtain the values in the desired format of major_version_number.minor_version_number.build_number.
This wizardry involves using VBScript's integer division (\) and modulus (Mod) operators. The \ and Mod operators are complementary. The \ operator divides two numbers and returns the integer result (i.e., it truncates any remainder), whereas the Mod operator divides two numbers and returns only the remainder. For example, in normal division, the equation
results in 5 with a remainder of 2. If you use the \ operator, the equation
results in 5. If you use the Mod operator, the equation
results in 2.
Both the major version number and minor version number have a maximum of 255, whereas the build number has a maximum of 65,535. Thus, to get the major version number, you can use the VBScript equation
where version is the raw version number that the Installer::ProductInfo property returns. The VBScript equation for the minor version number is
and the VBScript equation for the build number is
You concatenate the three resulting numbers into a string with periods between the numbers to obtain the version in the desired format. For example, let's transform the raw version number of 167774362 into the desired format:
167774362\65535 Mod 256 = 0
167774362 Mod 65536 = 2202
When you concatenate these numbers with periods, version 10.0.2202 results.
Most of the code in the For Each...Next loop retrieves each application's GUID, name, and version. The rest of the code in the loop performs minor tasks, such as adding headings and line breaks.
The last main section in Listing 1 is an If...Then...Else statement. As callout C in Listing 1 shows, if the system doesn't have any registered applications, you receive the message No products installed or advertised. If the system has at least one registered application, you receive a message specifying how many applications were found. To obtain this number, you use the StringList::Count property. You access this property through Installer::Products::Count, with Installer::Products returning a StringList object.
Finally, you use WSH's WScript::Echo method to display the results of the script. Figure 1, page 7, shows sample results. You can run RegisteredApps.vbs to obtain similar results. You can use this script with Win2K, NT 4.0, Windows Me, or Win9x systems. You must have Windows Installer, VBScript 5.5, and WSH 2.0 installed.
Next time, I'll continue my discussion about how to use Windows Installer and configure and modify its files. I'll show you how to take advantage of some of the other WindowsInstaller::Installer object properties so that you can write scripts that customize existing .msi files.