Building an MSI File: Visual Studio and Orca

In this chapter, you’ll build an installer package using a VS.NET Setup and Deployment Project and look at it with a tool called Orca, part of the Windows Installer SDK. (The Definitive Guide to Windows Installer, by Phil Wilson, Apress, ISBN: 1590592972.)

WilsonBackground

First, some history and an overview of VS’s capabilities in the installation area.

Microsoft has often added capabilities for building installations in VS—perhaps you’ve used the Visual Basic Package and Deployment Wizard. If you’ve used VS 6.0, you might have used the first version of Visual Studio Installer, which was available as a free download for VS licensees. VS.NET is the first release of VS that integrates the ability to build Windows Installer packages with the IDE. However, VS.NET’s installation tool comes with some limitations and restrictions that become apparent as you use it. This doesn’t mean that Microsoft did a bad job, but it does mean that if you want to use a substantial set of Windows Installer’s features, you should look beyond VS.NET’s installer tool. Look in Chapter 16 for a list of some of the vendors that supply fully featured tools to create installer packages.

One of the tools that Microsoft supplies to view and modify installer packages is called Orca. You can find it in the Windows Installer section of the Platform SDK; the installation package is (what else?) an installer package called ORCA.MSI. After you’ve installed it, you’ll find that the right-click context menu on Windows Installer files (notably packages, MSI files and merge modules, MSM files) allows you to open and edit them. You’ll be using Orca later to view and modify installation packages.

I’ll describe everything I cover here regarding Windows Installer concepts in more detail in later chapters. In this chapter, you’ll build an installer package so that you can look inside the actual MSI file; that’s when you’ll use Orca.

This is from The Definitive Guide to Windows Installer, by Phil Wilson, (Apress, ISBN: 1590592972). Check it out at your favorite bookstore today. Buy this book now.

{mospagebreak title=Building a Package}

The first package you build is a simple one—this is the “Hello World” program’s equivalent in the installation context. You’ll install Notepad (NOTEPAD.EXE) and a text file, together with a shortcut to run the installed Notepad against the text file. Note that this project is supplied with the book, so you can build it yourself or use the one provided.

You start by running VS and choosing the New Project from the File menu, selecting Setup and Deployment Project, and then Setup Project. Once the wizard has completed, select the Project icon in the Solution Explorer. The Edit menu has drop-down choices for View -> Editor -> File System. When you’re in this File System view, you can then select Application Folder. Then you can right-click in the file pane and select Add, then File. Add NOTEPAD.EXE and SomeTextFile.txt in that pane. Because you’ll need a shortcut to NOTEPAD.EXE for the Programs menu, right-click NOTEPAD.EXE in the file pane that you just added it to, and choose Create Shortcut. At this point you should see something like Figure 2-1. Notice that this view also shows the Properties window for the Application Folder, where ProgramFilesFolder, Manufacturer, and ProductName are enclosed in square brackets. These are three standard Windows Installer properties. You’ll see more of these later because properties are the variables that drive the behavior of an installation. For now the important thing to know is that the value of [ProgramFilesFolder] is resolved when the installation runs and is replaced with the Program Files path on the system. Those square brackets mean that the identifier they contain is a Windows Installer property.

 
Figure 2-1.  VS’s Application folder with shortcut

You need the shortcut to Notepad for the User’s Programs Menu, so right-click that shortcut and select Cut. Then right-click the User’s Programs Menu item, select Add -> Folder to add a folder and a subfolder (I called it Phil and TryNotepad), then use Paste to get the shortcut into the pane. You’ll see something like Figure 2-1.

Before you do a build, right-click the Project in the Solution Explorer and set the build properties to package files “In setup file.” Options are here for bootstrappers (in other words, making sure that the Windows Installer engine is installed on the target system), but you’ll just be building the installer package, an MSI file. See Figure 2-2 for an example of the property pages for the build.

 
Figure 2-2.  VS’s build options

Now if you do a build it should create an installer package: an MSI file containing everything needed to install your Notepad application. You can now double-click the package and the installation starts. After the Welcome screen, you see a Select Installation folder that’s got some notable features (see Figure 2-3):

 
Figure 2-3 Selecting the installation folder

  • The installer has resolved the ProgramFilesFolder property and the Folder box shows the actual path on the target system. This is a key feature of Windows Installer properties—not so much that they are resolved but that you can use them in square brackets in places such as folder names and Registry entries. I should point out that this folder isn’t always C:Program Files, which is why Windows Installer supplies a variable property.

  • The dialog wants to know whether you want to install this application for Just me or for Everyone. In other words, should this be installed for the current user or for all users of the system? This choice affects an important Windows Installer property that you can control—the ALLUSERS property you’ll look at later.

Select Everyone and continue. The installation should complete normally, and the Start Menu should now have the shortcut to initiate Notepad. You can uninstall the product in the usual ways via Add/Remove Programs, or by right-clicking the package and selecting Uninstall. However, you’re going to look at the content of the package, so after you’ve installed Orca, right-click the package and select Edit with Orca. What you see is a screen like Figure 2-4, where Orca shows each installer table in the left pane and the rows and columns of each table on the right. (Figure 2-9 shows a view with more tables shown.) In the case of Figure 2-4, Orca is showing the File table because it’s a useful place to start dissecting a package.

 
Figure 2-4The File table

Before you go any further, the tables and their contents, including the details of each table and column, are documented in the Windows Installer section of the Platform SDK. I’m generally not going to repeat all that detail; I’ll just point out how the tables work and fit together so that you can make more sense of the documentation.

Notice that there is a row for each file you’re installing, and that each row has the name of a file in the FileName column. If you hadn’t guessed from the format, the file name is shown in short file name and long file name format separated by a vertical bar. Perhaps more interestingly, the File table shows the version for code files in the Version column (which appears only if the file has a version resource). If you were thinking that Windows Installer uses this version value to decide whether to replace older versions on the target system, you’re right, and you’ll be using this value when you get to Chapter 6, which describes updating products.

An important item in the File table is the Component_ column. A Windows Installer Component is the key unit of an installation. There is a component reference at almost every place in the package where something is going to be installed onto the client system. VS has generated two components in the File table, one for NOTEPAD.EXE and one for the text file. If you’ve installed something and selected a “Custom” install, you’re probably used to the idea of a list of features that can be selected or deselected for installation. The way this works in Windows Installer is that a feature consists of one or more components. In fact, if you look at Figure 2-5 you see the FeatureComponents table, which is where components get mapped to their owning feature. Notice that VS generates only one feature, called DefaultFeature. This is one of the limitations of VS: It has no capability to organize an installation into more than one feature. You also see more than just the two components in the File table here. It turns out that VS wants to be sure that the Program Menu folders get removed at uninstall time. It adds a component and some entries in the RemoveFile table to make sure that the Program Menu subfolders get deleted, and it creates a component because most activities done during an installation require an associated component. VS also creates a component to deal with some Registry entries it might create (more on this later in this chapter).

 
Figure 2-5.  The Feature Components table 

This is from The Definitive Guide to Windows Installer, by Phil Wilson, (Apress, ISBN: 1590592972). Check it out at your favorite bookstore today. Buy this book now.

{mospagebreak title=A First Look at Shortcuts}

You installed a shortcut to Notepad in this installation. As you might expect, there is a Shortcut table in the installation package. Figure 2-6 shows the first few columns of the Orca view of the shortcut table and the entry for the shortcut to NOTEPAD.EXE.

 
Figure 2-6.  The Shortcut table

Note that the Component_ column contains the Windows Installer Component that the shortcut belongs to—exactly what you would expect knowing that all installed items belong to a component. There is also a Directory_ column, which effectively names the Program Menu folder and the subfolders that contain the shortcut. There is also an entry in the Target column that contains the name of the only feature in this installation. The fact that this is the name of a feature in the Feature table means that this is an “advertised” shortcut. Although advertised features are associated with the “install on demand” scenario, they have another interesting characteristic, which is that they verify the correctness of their associated installer components. In other words, using the shortcut triggers an automatic repair if the components are “broken.” If you remove the file NOTEPAD.EXE from its installed location on disk and then use the shortcut, you see a Windows Installer dialog that restores the missing file.

Although the name of the shortcut is in the Shortcut table, there is no obvious reference to the actual target file—the installed NOTEPAD.EXE. That’s because the linkage is indirect, through the component name in the Shortcut table. If you look at the Component table in Figure 2-7, the shortcut component is that top one, and that component has a KeyPath entry that is a key into the File table. Referring back to Figure 2-4, you can see that this File table entry is NOTEPAD.EXE. This is a specific case of a general principle in the installer—that a target file is indirectly referenced by naming a component that has a KeyPath entry referring to a File table entry for a file. In other words, the Shortcut table names the component (not the file), and the Component table’s KeyPath entry points to the file. There’s another subtle point to notice here about the Shortcut, which is that a shortcut file is not literally being installed. Although it might have looked as if you were installing an actual file in the IDE—a shortcut—the package contains no such file, just a table entry with the information required to create one.

 
Figure 2-7.  The Component table

If you look at this actual shortcut with Open All Users in Explorer, it looks like Figure 2-8. Compared to noninstaller shortcuts, the Target is grayed out and can’t be altered. This is because this shortcut is encoded with the special behavior that causes Windows to verify the presence of the installer component.

 
Figure 2-8.  A Windows Installer shortcut

This is from The Definitive Guide to Windows Installer, by Phil Wilson, (Apress, ISBN: 1590592972). Check it out at your favorite bookstore today. Buy this book now.

{mospagebreak title=More About Properties}

Windows Installer properties are the variables that you use to interact with the installation, such as the ProgramFilesFolder property I mentioned. They’re somewhat similar to Environment variables in the sense that they are text-based variables that define other text, standard ones are provided by Windows, and you can define your own and sometimes set the Windows ones. You’ll be looking at properties in detail later, but be aware that they have some important behaviors:

  1. Properties are case-sensitive. If you create your own property name and refer to it later, be sure that you get the case correct.

  2. A property can be public or private. A public property must be in uppercase. If it’s public, you can specify it with a value in a command-line install of a package. Also, because of the way that installations work internally, a public property that you create is also visible throughout most of the installation sequence. You cannot specify private properties in a command-line installation and you can’t propagate them throughout the installation sequences (covered in Chapter 5).

  3. You can treat properties as if they were logical values or data values. I’ll use the built-in VersionNT property as an example. This property tells you whether the system you’re installing on is in the Windows NT family (NT, Windows 2000, Windows 2003, Windows XP). For example, anything you do that depends on Windows Services can be conditioned on VersionNT as if it were a Boolean-valued attribute. It’s not unusual to see VersionNT as a condition in these situations. However, if you look at the actual value of this property you’ll find it returns the version of the NT family that is running. On Windows 2000 this property has the value 500, and on XP the value 501. In other words, it can be used as a Boolean True/False value even though it contains a version value. If you’re a C programmer, you might be familiar with the idea that a value is True if it is non-NULL in spite of its actual value, somewhat like the behavior of some C language variables.

  4. The data type of a property is vague. As you’ve seen, you can treat VersionNT as Boolean-valued even if the actual value of it is 500. If you wanted to check if you were running on Windows 2000 or later, you might check for VersionNT>=500. Does this mean it’s a string or a Boolean or a number? In practice it usually doesn’t matter because the context usually defines how the data type works, but this vagueness might well offend you if you are a programmer with a type-safe sensibility.

An installer package—a database—contains a Property table, which is where you can define your own properties if you need to give them default values, and also set values for standard installer properties. You aren’t required to declare your own properties in the Property table. Declare them only if you need to give them a default value, because the act of programmatically setting a property name to a value creates the property on the fly.

Looking at Figure 2-9, you see the Property table of your Notepad package. The table in general is just a list of property names and property values, some uppercase and public, some lowercase and private. Because this list contains both user-defined (added by VS when it built the package) and Windows Installer–defined properties, there’s no way to distinguish between what is a predefined installer property and what isn’t, other than by looking in the SDK to see if it’s defined there. ProductName and Manufacturer are among the standard installer properties that are defined here. Like many other properties, these two end up on the system, where they are available to be retrieved using API calls, or shown to the user in places such as the Add/Remove Programs Control Panel applet. ProductVersion is another installer property that you set, but unlike ProductName and Manufacturer, its value is more than just informational. When you look at upgrades and updating your product, you’ll see that incrementing ProductVersion is a key step in the process.

 
Figure 2-9.  The Property table

One of the key properties in an installation is the TARGETDIR property. This is the property that names the product’s primary installation folder. It usually consists of a concatenation of other standard properties, such as [ProgramFilesFolder][Manufacturer][ProductName], which is what VS creates as the default location for the application folder.

This is from The Definitive Guide to Windows Installer, by Phil Wilson, (Apress, ISBN: 1590592972). Check it out at your favorite bookstore today. Buy this book now.

{mospagebreak title=GUIDs: Product, Upgrade, and Package Codes}

You can see in Figure 2-9 that the ProductCode property is in fact a GUID, a unique identifier made famous by its use in identifying COM classes and interfaces. It’s used here simply because it’s a convenient mechanism to identify the product uniquely. It’s in what is called the Registry format—text with curly braces around it. Windows uses the ProductCode property to identify this product uniquely, and the Windows Installer Service uses it to determine whether your product is already present on a system.

The UpgradeCode is also a GUID that you should not change within the lifetime of a product. The idea behind UpgradeCode is that you will have major revisions to a product over time, and each of these revisions is intended as a replacement for the previous version. The UpgradeCode is the constant that links these revisions together. You can think of an UpgradeCode as defining a product family. Windows can detect whether a product containing a particular UpgradeCode has already been installed on a client system. It provides a mechanism to uninstall the prior version as you install the new version, so that the replacement is a seamless process instead of a visible uninstall followed by an install of the newer product. Each version replaces the prior version until a product arrives that is completely independent of the previous versions. In practice, this might be a marketing decision as much as a technical one. For example, each new version of Microsoft Office that comes out is a replacement for previous versions. However, if you look at the VS product line, VS.NET was the start of a different product line—it did not replace VS 6.0 but could be installed alongside it. If you were designing these product lines, the versions of Microsoft Office would all use the same UpgradeCode, but VS.NET would have a different UpgradeCode than VS 6.0. The way you build an installation to perform a major upgrade of a previous version is covered in Chapter 6.

Each individual package—the MSI file—is also identified by a GUID—the PackageCode. This is used to distinguish between different builds of the same product. If you run Orca on an install package and choose Summary Information from the View menu, you see something like Figure 2-10. This shows the contents of the Summary Information stream for this package, and it includes the PackageCode. (You can also see a similar display when you choose Properties and Summary from the context menu when right-clicking a package, except that the PackageCode is reported there as Revision Number.)

The combination of ProductCode and PackageCode is the way Windows knows what to do if you try to reinstall the product. If you install your Notepad package that you built in this chapter, then try to install the exact same package again, you see that it shows a maintenance mode dialog. In this Notepad installation, this means that the setup shows a dialog with choices for Repair or Remove. Windows knows that this product from this package is already installed on the system, and the package itself is designed to go into this maintenance mode if the product is already installed on the system. By definition, the fact that you are attempting to install the same product (same ProductCode) from the same package (PackageCode) means that you either want to repair or remove it. No other choices make sense in this context—the product is already installed, after all!

You can change the PackageCode by altering the Revision Number from Explorer (right-clicking, choosing Properties and the Summary tab) or in the Orca view of the Summary Information, then closing and saving the file. When you attempt to install this package now, you see different behavior. This time there is a message box from Windows saying that “Another version of this product is already installed” and suggesting that you reconfigure from Add/Remove Programs. In other words, you have to configure or remove the existing installed product before you can install the new one. The PackageCode determines the initial behavior here. If you change only the ProductCode in the Property table using Orca and then try to install the package, it would go into maintenance mode and ask for a Repair or Remove. If you change the ProductCode and the PackageCode, Windows thinks it’s a totally new product and lets you go ahead with the installation.


 
Figure 2-10.  MSI file summary information

If you have built a genuinely new package containing new files, you’ve probably done so to have it update or replace the existing product on the system. This is the subject of Chapter 6. For now, the point of this exercise is to demonstrate the relationship between PackageCode and ProductCode. 

This is from The Definitive Guide to Windows Installer, by Phil Wilson, (Apress, ISBN: 1590592972). Check it out at your favorite bookstore today. Buy this book now.

{mospagebreak title=Into the Package with Programming}

Windows has a collection of Win32 APIs, all starting with Msi, that you can use on the contents of installation packages. Other APIs interface with Windows to interact with the products installed on the system. There’s also a COM interface that is exposed mainly for administrators. I say this because the COM interface is scriptable, and VBScript with Windows Script Host (WSH) can take full advantage of it. You’ll create two VBScripts here: The first does an inventory of the package, and the second updates the package and modifies it. The complete scripts are in the sample code associated with this chapter, and I’ll go through the functionality a snippet at a time. In the interests of readability, not everything is declared explicitly.

The scripting interface into Windows Installer uses a ProgID of “WindowsInstaller.Installer”, so first a VBScript creates the interface with code such as this:

Dim installer
Set installer = CreateObject(“WindowsInstaller.Installer”)

Note that I’m skipping the error-checking code in these code fragments.

With an Installer object, you can now open a database package:

Dim database
Const msiOpenDatabaseModeReadOnly = 0
Set database = installer.OpenDatabase
(“trynotepad.msi”, msiOpenDatabaseModeReadOnly )

The first parameter to OpenDatabase is simply the path to the package: the actual MSI file. The second parameter says how you plan to use it. In this case, the value zero means that you aren’t planning to update the package.

You’re going to do an inventory of the files in the package, so you’ll be setting up a SQL-like query into the File table that you previously looked at with Orca. This works using the concept of a “view” based on a query. A View object is returned by calling the OpenView method of the Database object, passing the query that returns the data. The returned View object is a collection of records— a dataset if you like.

Dim view
Set view = database.OpenView
(“SELECT `FileName`,`Version`, `FileSize` FROM `File`”)

This query should look familiar if you’ve worked with databases. You’re getting items from the File table here; those items correspond to the column names in the Orca view of the File table. Incidentally, look carefully at what the column names are surrounded by. Those are grave characters, sometimes known as back quotes or peck marks: `. They are not single quotation marks. It is usually safer to quote the database content items with grave characters to avoid conflicts with reserved words.

The way you iterate through each of the records is to use the Fetch method of the View object:

Dim record
Set record = view.Fetch

At this point you have a record: a row from the File table. You can retrieve each of the columns with the StringData property of the Record object. You retrieve a single item by indexing StringData with an index that corresponds to its order in the original query, the SELECT statement. The query order was FileName, Version, FileSize, so StringData(1) returns FileName, StringData(2) returns Version, and StringData(3) the FileSize:

Dim afile
afile = record.StringData(1)

This gives you a string containing the FileName value of the particular row. When all the Record objects in the View have been returned with View.Fetch, the final returned Record object is empty, so the script can check to see if the Record object has the value Nothing to find out whether all the records have been returned. Putting this all together into a code fragment that loops through the File table, you have this code:

Set view = database.OpenView
(“SELECT `FileName`,`Version`, `FileSize` FROM `File`”)
view.Execute
Do
   Set record = view.Fetch
   If record Is Nothing Then Exit Do
   afile = record.StringData(1)
   aversion = record.stringdata (2)
   fsize = record.Stringdata (3)
   fullmsg = fullmsg & afile & ” ” & aversion & ” ” & fsize & vbcrlf
Loop
msgbox fullmsg

The principles of updating the package are similar. To make this updating script a bit more interesting, it reads a text file consisting of a series of commands that modify the database. You use FileSystemObject to read the modify.txt file containing the commands:

Set fso = CreateObject(“Scripting.FileSystemObject”)
Set afile = fso.GetFile(“modify.txt”)
set ts = afile.OpenAsTextStream (1, 0)

You create an Installer object and open the database package. However, this time you’re updating the package, so you open in transacted mode. This is important when you close the database after your changes:

Const msiOpenDatabaseModeTransact = 1
Set database = installer.OpenDatabase
(“trynotepad.msi”, msiOpenDatabaseModeTransact)

Given a command that updates the database package, as in the previous script you use the OpenView method of the Database object, passing the command you expect to run. With this command in a string called “thecommand” that you get by reading the text file, you do the OpenView:

Set view = database.OpenView(thecommand)

Because you’re updating, these commands use verbs such as Update, Insert, and Delete, so you use the Execute method of the View object to cause the change to occur. Put this loop together to read the text file and execute the SQL updates:

Dim thecommand, view
Do
   thecommand = ts.readline
   Set view = database.OpenView(thecommand) : CheckError
   view.Execute : CheckError
   if ts.AtEndofStream then exit do
Loop

The modify.txt file supplied with the book samples has two updates. The first inserts a new Property into the Notepad installation that you built. The text of this command follows:

INSERT INTO Property (Property.Property, Property.Value) VALUES (‘ARPHELPLINK’, ‘http://www.microsoft.com’)

Note that this is in two general pieces. The first names the table and the columns into which data is being inserted in the form <TABLE>. <COLUMN>. The second part lists the values corresponding to those columns. In this example, ARPHELPLINK is a standard Windows Installer property that is displayed in the Add/Remove Programs applet as a “Click here for support information” link. VS’s installer doesn’t let you specify one, so this example shows the way you add one to point to Microsoft’s Web site.

If you refer to Figure 2-9, you see an existing value for the property FolderForm_AllUsers with a value of ME. Your second update changes the value of that existing property in the property table from its previous ME to now say ALL.

UPDATE Property SET Property.Value = ‘ALL’ WHERE Property.Property = ‘FolderForm_AllUsers’

When you opened the database, you opened it in transaction mode. This means that none of the changes you’ve made are in the database package yet. To save the updates, you must commit the changes with the Commit method of the Database object:

database.Commit

Now that you’ve been introduced to an example of how to alter the content of an installation package outside the VS environment, you may wonder about the effect on the installation of changing this particular FolderForm_AllUsers property. VS.NET generates this property—it’s not a standard Windows Installer one. What happens here is that the value of the FolderForm_AllUsers property drives the state of the Everyone or Just me choice in the installation dialog shown in Figure 2-3. After you change the property value from ME to ALL with that Update statement, when you install the new package you’ll note that the radio button now defaults to Everyone. How did I know that ALL is a legal value for the property value? You might have noticed that Orca has a Find choice under the Edit menu. If you do this Find and put FolderForm_AllUsers in the “Find what” text box, the search eventually shows you a ControlEvent table that has an entry with a condition FolderForm_AllUsers=”ALL” (see Figure 2-11). I won’t go into the deep details of dialog behavior here, but the FolderForm user dialog (the one with those Everyone/Just me radio buttons) has a ControlEvent that is triggered off the Next button of the dialog. This event occurs when the user chooses the Next button, and the event sets the value of the ALLUSERS property to 2 if FolderForm_AllUsers=”ALL”. It’s this value of the ALLUSERS property that determines whether the installation is for the current user or for the system as a whole (meaning that the product is installed for all users of the system).

 
Figure 2-11.  ALLUSERS in the ControlEvent table

The ALLUSERS property is a Windows Installer property that you can set to three states:

  1. Unspecified (NULL is how the SDK documentation describes this). The installation defaults to a per-user installation.

  2. A value of 1. This means a per-system installation, with the caveat that the installation will fail if the user does not have administrative privileges.

  3. A value of 2. This causes a per-system installation if the user has administrative privileges, and a per-user installation if the user does not have administrative privileges.

For now, I’ll point out that the nondeterministic value of 2 is likely to be unwelcome in some environments. In particular, it’s unusual for applications to be installed on servers for the installing user—it’s much more likely that the product is being installed for all users of the server. Having it accidentally installed for the current user could be a disaster, so in the cases where you know how the product is intended to be installed and used, force the issue by setting the ALLUSERS property accordingly. You can do that in the ControlEvent table in Figure 2-11 by changing the Argument value to 1 using Orca.

Summary

You’ve looked at building a basic installation package and seen some of the contents of the underlying database tables that are used during the installation. You’ve had a brief look at Windows Installer properties and the APIs that you can use to access database packages. One of the key points to take away from this chapter is that a Windows Installer Component is the basic unit of an installation, and you’ll come back to this idea many more times. 

This is from The Definitive Guide to Windows Installer, by Phil Wilson, (Apress, ISBN: 1590592972). Check it out at your favorite bookstore today. Buy this book now.

One thought on “Building an MSI File: Visual Studio and Orca

  1. Hi,

    I’ve read this article :
    building msi file with visual studio and orca and it s a good article !

    very excellent to read and to use 😉 !

    now i would like to know if is it possible to have the list of table and display them on a datagrid with all contents as for example ! i would like to display all content of table property!?

    I try to do that but i can ‘t !!!!!

    Have you got any idea with code vb.net 1.1 !?

    Thanks for all
    Christophe

[gp-comments width="770" linklove="off" ]