Archive for the 'Windows Installer' Category

Mixing 32 and 64-bit components in a single MSI

Definetely one my pet peeves about Windows Installer is how it deals with instruction set architectures (ISAs). Looking at Windows NT history, supported ISAs have come (amd64, IA-64) and gone (Alpha, PowerPC, MIPS) — yet most of the time, there was more than one ISA being officially supported. Having to ship binaries for multiple ISAs therefore always has been on the agenda for many ISVs.

Needless to say, supporting multiple ISAs requires special consideration when developing setup packages and providing separate packages — one for each ISA — has become common practice to approach this. This approach makes perfect sense: Given the incompatibility of most ISAs, nobody needs Alpha binaries on a MIPS system or amd64 binaries on a IA-64 machine, so there seems little reason to mix ISAs within a single package.

Unsurprisingly, Windows Installer, which was created somewhere around 2000, also goes this route and encourages developers to provide separate packages for each ISA.

However, with the advent of amd64/x64/IA-32e/Intel 64/whateveryoucallit, the situation has changed: Because i386 and amd64 are so closely related and compatible, there are now plenty of situations where combining binaries of differing ISAs (i.e. amd64 and i386) in a single installer package makes perfect sense. Examples for this include:

  • A package comprises a shell extension as well as a standalone App. For certain reasons (maybe the use of VB6), there only is a 32 bit version of the App. The shell extension, in contrast, is available for both, i386 and amd64. Whether you put everything into one package or provide separate packages for each ISA, one of them will comprise a mixture of ISAs.
  • SDKs for unmanaged code usually include .lib and .dll files for multiple architectures. Shipping separate packages for i386 and amd64 (containing different binaries but the same headers, docs, etc.) may please the Windows Installer gods, but seems redundant, a waste of disk space, and user-unfriendly.

Thanks to the msidbComponentAttributes64bit flag, mixing architectures in a single MSI package is technically possible: You mark the package as being 32 bit and set said flag for all 64-bit components. Rather than splitting your setup into multiple packages, you can conveniently combine everything into one.

When reading the documentation (and ICE requirements, more on this later) carefully though, it turns out that this is not quite what the Windows Installer team invented this flag for. Anyway, it works fine, problem solved.

Hmmm…

If only there was not ICE80.

ICE80, alas, is critical if you intend to conform to the Requirements for the Windows Vista Logo Program for Software:

Applications must use the Windows Installer (MSI) or ClickOnce for installation. Windows Installation packages must not receive any errors from the Internal Consistency Evaluators (ICEs) listed here:

1-24, 27-31, 33-36, 38, 40-57, 59, 61-63, 65, 67-72, 74-84, 86-87, 89-94, 96-99

ICE80 mainly states that (1) you should not install 64 bit components to 32 bit directories (e.g. Program Files vs. Program Files (x86)) and (2) you should not use 64 bit components in a 32 bit package.

(1) is fair enough, although it raises the question where you should install your software to without splitting it in two or violating other ICE rules. Worse yet, (2) effectively means that said way to create multi-ISA packages, creating 32 bit packages with some components marked with msidbComponentAttributes64bit, is illegal alltogether.

So to be logo’ed, there seems to be no other way than providing separate packages, maybe along with (urgh!) a meta-package that installs the other two.

If there are more important things on your schedule than getting a Vista logo, ICE80 seems like something that can safely be ignored. Indeed, this is what I have done several times, including in case of the cfix installer.

Anyway, let’s ignore ICE80 once more and hold on to the plan of building a 32-bit package containing both, 32-bit and 64-bit components.

Urgh…

For an SDK that is installed on 64-bit Windows, it will usually make sense to install both, 32 and 64 bit .lib and .dll files etc. On 32-bit Windows, installing 64-bit components may seem odd, but due to the existence of amd64 compilers for i386, it still makes sense to install them or at least offer them as optional feature.

So far, so good. Things get interesting, though, when COM registration comes into play. Naturally, a 32 bit installer package sees the system like any other 32 bit application does. Most importantly, this means that Registry Reflection and File System Redirection applies.

Now consider a package that contains both a 32-bit and a 64-bit version of some COM server, each installed to a separate directory. COM Registration either be performed through the Class or the Registry table. Provided that the msidbComponentAttributes64bit flag has been used properly, such a package will work great on 64 bit systems thanks to Registry Reflection: The regsitry entries will be written to the proper (reflected) locations and both COM servers will work properly.

Now think what happens on 32-bit Windows: (1) There is no Registry Reflection and (2) Windows Installer silently ignores msidbComponentAttributes64bit flags. Result: The installation will run just as smooth as on the 64-bit system. However, while installing the files continues to works flawlessly, the registry will be left in a less-than-optimal state: Due to the nonexistence of Registry Reflection, the registration entries of both COM servers will have been written to the same location!

Needless to say, the server whose registration entries were written first will now be unusable.

In a way, Windows Installer has taken its revenge for breaking the rules.

Bottom line: Mixing 32 and 64-bit components in a single MSI works fine in many cases, but is against the MSI rules and can lead to further problems. And while I am still convinced that providing separate, ISA-specific packages is wrong or at least inconvenient in certain situations, it is definitely the safer and “right” way to go.

(Note: Windows Installer 4.5 introduced multi-package transactions, which allow reliable and transactional multi-package setups to be built so that splitting a setup into multiple packages can be implemented without much pain. However, very few users already have Windows Installer 4.5 installed and Windows 2000 is not even supported by this release. For many of us, relying on this feature therefore is not really an option.)

Advertisements

Writing Data-Driven Custom Actions

Whenever Windows Installer’s built-in actions do not suffice to perform a specific task, a Custom Action needs to be written. Needless to say, Custom Actions, can be a bit tricky — not only can they be laborious to write and cumbersome to debug, they also run the risk of interfering with Windows Installer’s declarative, transactional way of performing installs.

It is not really surprising that Windows Installer therefore more or less discourages the use of Custom Actions unless it is absolutely necessary. Moreover, as a result of its declarative nature, it is understadable that Windows Installer prefers Custom Actions to be data-driven.

What this means in practice is that a Custom Action should not perform a hard-coded task — rather, it should query one or more (custom) tables containing the necessary information (in a declarative manner) about what is to be performed and should act accordingly.

Using WiX, creating custom tables turns out to be pretty easy. Let’s assume we create a Custom Action that, based on some condition, does something with a specific file. An appropriate table could look like this:


<CustomTable Id="MyCustomTable">
  <Column Id="Id" Type="string" PrimaryKey="yes"/>
  <Column Id="Path" Type="string"/>
  <Column Id="Condition" Type="string"/>

  <Row>
    <Data Column="Id">Foo</Data>
    <Data Column="Path">[INSTALLLOCATION]foo.txt</Data>
    <Data Column="Condition"><![CDATA[ &FeatureFoo=3 ]]></Data>
  </Row>
  <Row>
    <Data Column="Id">Bar</Data>
    <Data Column="Path">[INSTALLLOCATION]bar.txt</Data>
    <Data Column="Condition"><![CDATA[ &FeatureBar=3 ]]></Data>
  </Row>
</CustomTable>

To query this table, we have to open a view and fetch the records one by one:

PMSIHANDLE Database = MsiGetActiveDatabase( InstallHandle );
ASSERT( Database != NULL );

PMSIHANDLE View;
UINT Result = MsiDatabaseOpenView(
  Database,
  L"SELECT `Condition`, `Path`, FROM `MyCustomTable`",
  &View );
if ( ERROR_SUCCESS != Result )
{
  ...
}

Result = MsiViewExecute( View, NULL );
if ( ERROR_SUCCESS != Result )
{
  ...
}

for ( ;; )
{
  PMSIHANDLE Record;
  Result = MsiViewFetch( View, &Record );
  if ( Result == ERROR_NO_MORE_ITEMS  )
  {
    break;
  }
  else if ( ERROR_SUCCESS != Result )
  {
    ...
  }

  //
  // Read condition. 
  //
  // N.B. Do not format -- this is done by 
  // MsiEvaluateCondition itself.
  //

  WCHAR Condition[ 256 ];
  DWORD Length = _countof( Condition );
  Result = MsiRecordGetString(
    Record,
    1,
    Condition,
    &Length );
  if ( ERROR_SUCCESS != Result )
  {
    ...
  }

  if ( MSICONDITION_TRUE != MsiEvaluateCondition(
    InstallHandle,
    Condition ) )
  {
    //
    // This record can be skipped.
    //
    continue;
  }

  //
  // Read remaing fields.
  //

  WCHAR Path[ MAX_PATH ];
  Length = _countof( VszPath );
  Result = GetFormattedRecord(
    InstallHandle,
    Record,
    2,
    Path,
    &Length );
  if ( ERROR_SUCCESS != Result )
  {
    ...
  }

  
  ...
}

With GetFormattedRecord being the following utility routine:


static UINT GetFormattedRecord(
  __in MSIHANDLE InstallHandle,
  __in MSIHANDLE Record,
  __in UINT Field,
  __out PWSTR Value,
  __inout PDWORD Length
  )
{
  DWORD RecLength = *Length;
  UINT Result = MsiRecordGetString(
    Record,
    Field,
    Value,
    &RecLength );
  if ( ERROR_SUCCESS != Result )
  {
    *Length = RecLength;
    return Result;
  }

  PMSIHANDLE FormattingRecord = MsiCreateRecord( 1 );
  
  Result = MsiRecordSetString( FormattingRecord, 0, Value );
  if ( ERROR_SUCCESS != Result )
  {
    return Result;
  }

  return MsiFormatRecord(
    InstallHandle,
    FormattingRecord,
    Value,
    Length );
}

Some things are worth noting:

  • I use PMSIHANDLE, which, as you probably already know, is not a typedef for MSIHANDLE* but rather a smart-pointer like class that automatically closes the handle when it goes out of scope.
  • The use of backticks in the query.
  • It must have been a Visual Basic programmer implementing MsiRecordGetString: Field Indexes start with 1, not 0. To make matters worse, reading from index 0 does not fail but returns arbitrary data. Finally, to confuse people further, indexes are 0-based for MsiRecordSetString.
  • If the field contains formatted data, you have to MsiFormatRecord it yourself. For conditions, however, MsiEvaluateCondition handles that for you.

So far, so good. There is, however, one thing to notice: To access the installer database, the custom action must be a nondeferred action:

You cannot access the current installer session or all property data from a deferred execution custom action

The problem with nondeferred actions, however, is that they execute in user context — in contrast to deferred actions, which execute in system context. On pre-Vista platforms, a per-machine installer package can be expected to always be launched by an administrator (otherwise it will fail anyway) — in this case, the differences between user and system context may not be important — both, for example, have r/w access to files in %ProgramFiles%. On Vista and later OS, however, it is common to have a regular user launch an installation which causes an elevation prompt once it reaches the main install phase. In this case, the user context is significantly less privileged than system context.

For a hypothetical custom action that is intended to edit a file installed to %ProgramFiles%, this means that (disregarding rollback considerations and assuming proper scheduling) performing this action from within the nondeferred custom action will work fine on pre-Vista OS. When run on Vista, though, it is likely to fail due to lack of write access to %ProgramFiles%. In practice, this means that all system-changing tasks usually have to be performed by a deferred action.

To sum up: To be data-driven, you have to use nondeferred actions. To be able to perform any serious, system state-changing tasks, however, you have to use deferred actions.

Great.

As it turns out, however, there is a way to escape this catch-22, and it is carefully buried in the Windows Installer documentation:

[…] Actions that update the system, such as the InstallFiles and WriteRegistryValues actions, cannot be run by calling MsiDoAction. The exception to this rule is if MsiDoAction is called from a custom action that is scheduled in the InstallExecuteSequence table between the InstallInitialize and InstallFinalize actions. […]

[From the Remarks section of MsiDoAction]

In fact, the way I came across this solution was by looking at the source code of the WiX XmlFile action, which I knew manages to both be data-driven (uses a custom table) and alter system state (edits XML files). The way it does this, and the point where the above remark comes into play, is as follows: In the nondeferred action, you do not perform any actions changing system state. Rather, you collect the information from the installer tables and stuff it (yuck) into the CustomActionData property. Then, leveraging MsiDoAction and passing said CustomActionData, you schedule another custom action — this time a deferred one — which parses the CustomActioData (yuck) and, based on this data, finally performs the actual modifications — in system context.

It really could not be easier and more intuitive, right?

On Setup Bootstrap Loaders

Almost two years ago, I wrote about how to create multi-language MSI packages. Although using transforms to internationalize an MSI package is a viable solution, one drawback of this approach is that it may require a bootstrap loader.

While it is easy to say that a bootstrap loader is required and many high-profile setups do indeed use bootstrap loaders, bootstrap loaders do have their issues. They not only add complexity to the setup package, there actually are several reasons why a bootstrap loader-free setup may be preferrable.

Bootstrap loaders

The idea of a bootstrap loader is to have an EXE file that does some prerequisite work and then launches the actual installer, which is usually implemented as an MSI package. While the MSI package may be a separate file, a download-friendly setup usually requires the bootstrap loader to embed the MSI package inside the EXE file and extract it when started.

While the average end user probably will not care much whether he double-clicks on an .exe or an .msi file, a standalone MSI-package is a better Windows citizen for at least the following two reasons:

  • Active Directory has native support for deploying MSI packages. It is, of course, possible to deploy EXE-based setups through AD, but these tend to require more work or more elaborate software solutions like SMS.
  • msiexec supports a plethora of switches that allow customizing and automating setups. These switches usually cannot be used when a bootstrap loader is present. To be equally admin-friendly, a bootstrap loader should therefore support appropriate command line switches for things like performing quiet installations, pre-selecting features, and suppressing reboots. Needless to say, impementing these features increases complexity and implementation effort.

That said, there are good reasons to avoid using a bootstrap loader. If, for example, all the bootstrap loader has to do is determine the user’s locale to choose an appropriate MSI transform, it may well be worth considering whether it is easier to just offer 5 language-specific MSI packages for download. And in case of CD-ROM based distribution, a simple HTML Application (HTA) could serve as a bootstrap loader-surrogate.

Still, there are situations when a a true boostrap loader is required. A classic example for this is MDAC: With Windows XP, MDAC has become part of Windows, but if you still support older Windows releases, you do not have much choice but to redistribute MDAC. In all those years, however, Microsoft has not managed to provide a proper merge module for MDAC. There were several half-hearted attempts both by Microsoft and others to wrap MDAC by a MSM, and in fact, I tested at least five of them, but not a single one was robust enough to be useable in practice. Given this situation, the only sane choice was (and still is) to put the duty of installing MDAC on the bootstrap loader.

Another common reason for using bootstrap loaders used to be that the machine might not have the correct Windows Installer version installed — but unless you are crazy enough to require Windows Installer 4.0, this should not be of much concern today.

Creating a bootstrap loader

Notwithstanding these pros and cons, the question remains how to create a bootstrap loader. Several of the commercial setup authoring tools have built-in support for that and their use might be the most straighforward approach. However, after having had a horrid experience with Wise for Windows Installer, I have not used any of these Windows Installer-based authoring tools and therefore cannot say much about them.

With the upcoming 3.0 release, WiX also includes a tool for creating bootstrap loaders — although it does not quite seem ready for prime time yet.

Creating a custom bootstrap loader from scratch is certainly the most flexible approach and doing so should not actually be too hard. However, implementing a full fledged bootstrap loader is likely to require more effort than most teams are willing to put into a setup.

There is, however, another approach that may seem somewhat unorthodox at first but has served me well for a past project: Using one of the script-based setup solutions to roll your own bootstrap loader. The idea of tools such as Wise Installation System (which, btw., has absolutely nothing to do with Wise for Windows Installer) is that the entire setup is authored as a script. Now, contrary to the original intent of these authoring tools, you can leave all the wizards and UIs aside, start off with an empty script and leverage the rather powerful building blocks and constructs these scripting languages tend to offer to create a highly customized (UI-less) bootstrap loader. Taking WIS as an example, extracting an embedded file is a matter of one line of code. Similarly, invoking GetLocaleInfo to query the system’s locale is a snap. And for even more powerful bootstrap loaders, it is possible to extract a helper DLL and call its routines from within Wise Script.

As I said, this approach has served me well for a particular project that required a pretty complex bootstrap loader. And while I cannot recommend this approach unconditionally (especially because of the quirkiness of WIS and the fact that WIS is actually an obsolete technology), it is an approach that, when a bootstrap loader is absolutely necessary, may be worth considering.

Install MSI with log from shell context menu

When authoring MSI packages, you frequently need the installation to be logged. Instead of repeatedly opening a console to type in msiexec /l* install.log /i foobar.msi, these shell context menu items may speed this process up a little. They do the same as ‘Install’ and ‘Uninstall’ but log everything (/l*) to PackageName.msi-install.log or PackageName.msi-uninstall.log, respectively.
msi.png

Here are the registry entries:

[HKEY_CLASSES_ROOT\\Msi.Package\\shell\\LoggedInstall]
@="&Logged Install"
[HKEY_CLASSES_ROOT\\Msi.Packageshell\\LoggedInstall\\command]
@="msiexec.exe /l* \"%1-install.log\" /i \"%1\" %*"
[HKEY_CLASSES_ROOT\\Msi.Package\\shell\\LoggedUninstall]
@="L&ogged Uninstall"
[HKEY_CLASSES_ROOT\\Msi.Package\\shell\\LoggedUninstall\\command]
@="msiexec.exe /l* \"%1-uninstall.log\" /x \"%1\" %*"

And in case you have not yet configured ‘Run as’ for MSI files, you may want to add these as well:

[HKEY_CLASSES_ROOT\\Msi.Package\\shell\\runas\\command]
@="msiexec.exe /l* \"%1.log\" /i \"%1\" %*"

MSI shell context menu (Save as and rename to .reg)

Type 1 CustomAction Load Failures

When testing a custom action, it is usally practical to have the CA write some information to the installer log, so that you can verify that the CA has been called properly. But what if the installer log does not show up your information but instead the installation aborts and the log only states that the custom action returned 3 (ERROR_INSTALL_FAILURE)?

There may be various reasons for this to happen, but the most common reason is probably that your DLL was unable to load because of unsatisfied dependencies. Note that for the CA to work on all machines, it may only have dependencies to ‘standard DLLs’ — those DLLs that are available on a fresh Windows install.

Odds are that if you created the DLL with Visual C++ 2005 using standard settings (i.e. compiling with /MD[d]), VC has introduced a dependency to msvcr80.dll. As msvcr80.dll is not a ‘standard Windows DLL’, this will result in a DLL loading failure. The solution in this case is mostly as easy as switching to /MT[d].

Where to place RemoveExistingProducts in a major MSI upgrade

When Windows Installer performs a major upgrade, the position of the RemoveExistingProducts action determines when the uninstall of the old product is performed. Despite efficiency, two factors dominate the decision

  • What should happen if either the install or the uninstall fails?
  • Should the new product ‘see’ registry entries/files/etc from the existing product or should the new product install ‘start from scratch’?

The following table summarizes the four possible approaches.

Position Audit scripted? Which product remains if … Unchanged files are reinstalled?
uninstall fails? install fails?
Between InstallValidate and InstallInitializeRemove old, install new No Old Neither Yes
After InstallFinalizeInstall new, remove old No Both Old No
After InstallInitialize, before any script-generating actionsRemove old, install new Yes Old Old Yes
Between InstallExecute (or InstallExecuteAgain) and InstallFinalizeInstall new, remove old Yes Old Old No

Authoring multi-language MSI packages

By default, Windows Installer packages are single-language only — no direct support for multi-language packages is currently provided. Creating a multi-lanuage installation package is thus a little more compex — the following provides a summary of the steps required:

  • Create an english package
    • Compile

      candle -dLANG=1033 foo.wxs

    • Link

      light -out foo.msi -loc strings-en.wxl foo.wixobj

  • Create the german (or whatever language) package
    • Compile

      candle -dLANG=1031 foo.wxs

    • Link

      light -out foo-de.msi -loc strings-de.wxl foo.wixobj

    • Create a transform describing the differences between german and englich package:

      msitran -g foo.msi foo-de.msi de.mst

    • Embed the transform in the english package:msidb -d foo.msi -r de.mst

The boostrapper has to decide which language the installer package should use. To use english, call msiexec without any special parameters. To use german, pass the parameter TRANSFORMS=:de.mst to msiexec, which directs it to apply the embedded transform named de.mst before running the package.

To avoid creating a bootstrapper, you might want to try this. See also: WiX tutorial, Lesson 8.


Categories




About me

Johannes Passing, M.Sc., living in Berlin, Germany.

Besides his consulting work, Johannes mainly focusses on Win32, COM, and NT kernel mode development, along with Java and .Net. He also is the author of cfix, a C/C++ unit testing framework for Win32 and NT kernel mode, Visual Assert, a Visual Studio Unit Testing-AddIn, and NTrace, a dynamic function boundary tracing toolkit for Windows NT/x86 kernel/user mode code.

Contact Johannes: jpassing (at) acm org

Johannes' GPG fingerprint is BBB1 1769 B82D CD07 D90A 57E8 9FE1 D441 F7A0 1BB1.

LinkedIn Profile
Xing Profile
Github Profile