WiX tutorial

Lesson 4 Upgrades and Modularization

At the end of the previous lesson, we have already learned how to do everything that can be thought of when it comes to installation packages, right? After all, even if Windows Installer doesn't provide an immediate solution, we just fire up our trusty old compiler and write it for ourselves, right?

In a way, yes. And in a way, no. We know how to assemble our original installation package but what shall we do when it comes to shipping an upgrade? Or a patch? Shall we ask the user to uninstall the previous version and reinstall the new one? And what happens to the settings our user has made during the use of the software? Shall they start everything from scratch again?

Of course not. So, let's see how Windows Installer can help solve such problems. It divides product changes into three categories:

There are cases when the Windows Installer forces you to use a major upgrade (in other words, to change the Product GUID). You have to when you expect the old and new versions to coexist on the user's computer. You have to when you change the name of the .msi file for any reason. You have to when you need to change any Component GUID in the package. You have to when a component has been removed. You have to when there are any changes in the feature hierarchy (child feature moving out of a parent, or a parent feature getting a new child).

So, major upgrades are a clear cut but the line between small updates and minor upgrades can be blurred. Think about it this way: if you'll ever need to differentiate between the current and the new versions in your future products, vote for the minor upgrade. As always, it is better to err on the safe side.

When you change GUIDs, keep track of the old ones. You will need them to use the sophisticated upgrade features of Windows Installer.

4.1 Checking for Oldies

The first task when doing any kind of update or upgrade is to make sure we have the previous version we want to change. Windows Installer relies on the UpgradeCode attribute of the Product tag to do that. Therefore, it is very important to always include an UpgradeCode, even if you don't plan your current program to be upgraded later. You never know and once it's out there, you can't provide it any more. Keep the same UpgradeCode GUID as long as you want the products to be upgraded by the same upgrade version. In the usual case, this would mean one code for all 1.x versions, another one for the 2.x versions, etc:

<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>

  <Product Name='Foobar 1.0' Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE' UpgradeCode='YOURGUID-7349-453F-94F6-BCB5110BA4FD'
    Language='1033' Codepage='1252' Version='1.0.0' Manufacturer='Acme Ltd.'>

    <Package Id='*' Keywords='Installer' Description="Acme's Foobar 1.0 Installer"
      Comments='Foobar is a registered trademark of Acme Ltd.' Manufacturer='Acme Ltd.'
      InstallerVersion='100' Languages='1033' Compressed='yes' SummaryCodepage='1252' />

      ...

    </Package>
  </Product>
</Wix>

Our current SampleUpgrade now consists of two installation packages. Both are based on the simple UI installer, SampleWixUI.

The second version of SampleUpgrade is meant to replace one of the deployed files with a newer version. As we will now consider this a minor upgrade, we change the Version. Cosmetic changes to the human readable Name and Description are obvious:

<Product Name='Foobar 1.0.1' Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE' UpgradeCode='YOURGUID-7349-453F-94F6-BCB5110BA4FD'
  Version='1.0.1' Manufacturer='Acme Ltd.' Language='1033' Codepage='1252' >

  <Package Id='*' Keywords='Installer' Description="Acme's Foobar 1.0.1 Updater" ... >

We also need a description of what product versions we plan to replace with this upgrade. The Id attribute of the Upgrade tag refers back to the UpgradeCode GUID of the original installation package (the older SampleUpgrade in our case). The internal UpgradeVersion tag describes the details of the versions to be updated. OnlyDetect tells the installer not to remove the previous product (remember, we're doing a minor upgrade, we keep the old version of the product in place but replace one file. If we were doing a major upgrade, we would probably remove 1.0.0 and install 1.1.0 instead).

Minimum and Maximum specify the range of versions we are supposed to update with this upgrade. IncludeMaximum and IncludeMinimum specify whether the bound value is actually included in the range or not (IncludeMaximum = yes meaning to find versions below or equal to the version specified in Maximum while IncludeMaximum = no only finds those below the Maximum). Although these attributes have their default values, we don't rely on those here and always include them–it's better to spell them out for the sake of clarity and self-documentation.

  <Upgrade Id='YOURGUID-7349-453F-94F6-BCB5110BA4FD'>
    <UpgradeVersion OnlyDetect='yes' Property='SELFFOUND'
      Minimum='1.0.1' IncludeMinimum='yes'
      Maximum='1.0.1' IncludeMaximum='yes' />
    <UpgradeVersion OnlyDetect='yes' Property='NEWERFOUND'
      Minimum='1.0.1' IncludeMinimum='no' />
  </Upgrade>

Using an Upgrade tag in our source file triggers the inclusion of a new standard action, FindRelatedProducts. It is scheduled to run right after LaunchConditions, if any. If necessary, we can re-schedule it inside the InstallExecuteSequence tag.

FindRelatedProducts works its way through our Upgrade tag and checks for each version listed there. If one is found, its Product GUID will be appended to the property specified in the UpgradeVersion tag (SELFFOUND or NEWERFOUND in our example). Needless to say, the Installer can only find products that have been installed using an .msi package with UpgradeCode specified—so now you know why it's so important to always specify one.

If you develop localized software packages, you can also specify a Language attribute in both UpgradeVersion and Product tags. Use the usual Windows decimal language identifier to specify the language. If specified, FindRelatedProducts will only locate products with matching languages.

After the check has run, we can take the appropriate actions based on the existence and value of the properties involved:

  <CustomAction Id='AlreadyUpdated' Error='Foobar 1.0 has already been updated to 1.0.1 or newer.' />
  <CustomAction Id='NoDowngrade' Error='A later version of [ProductName] is already installed.' />

  <InstallExecuteSequence>
    <Custom Action='AlreadyUpdated' After='FindRelatedProducts'>SELFFOUND</Custom>
    <Custom Action='NoDowngrade' After='FindRelatedProducts'>NEWERFOUND</Custom>
  </InstallExecuteSequence>

For some strange reason, small updates and minor upgrades cannot be run simply by clicking on the .msi file—they give the error: "Another version of this product is already installed." We know, stupid... Anyway, you have to start it with the command:

msiexec /i SampleUpgrade.msi REINSTALL=ALL REINSTALLMODE=vomus

Don't ask me how this would fare with the average user... You'd better start it from an Autorun.inf file or devise an outer Setup.exe shell to launch it.

As you can see, this upgrade version will behave properly in both directions. It will replace the earlier package but it will also handle the case where the program might be upgraded even further in the future (eg, 1.0.2 and later). It will not downgrade a possible later version back to 1.0.1. To be absolutely correct and foolproof, we have to plan that early. Even the very first installer we ship should already have this safety lock built in (see it in the Old version of our current sample):

  <Upgrade Id='YOURGUID-7349-453F-94F6-BCB5110BA4FD'>
    <UpgradeVersion OnlyDetect='yes' Property='NEWERFOUND' Minimum='1.0.0' IncludeMinimum='no' />
  </Upgrade>

  <CustomAction Id='NoDowngrade' Error='A later version of [ProductName] is already installed.' />

  <InstallExecuteSequence>
    <Custom Action='NoDowngrade' After='FindRelatedProducts'>NEWERFOUND</Custom>
  </InstallExecuteSequence>

4.2 Replacing Ourselves

If our intention is a major upgrade, that is, complete and automatic removal of the previous version when a new one arrives, we just need to set OnlyDetect to no and to set the version numbers accordingly. Minimum will then specify the first version we allow to replace with the current one (and we include this minimum version in the range) while Maximum can be set to the current version number (but not included in the range). Then, any version between the first and the previous one before the current version will be removed automatically during the installation of the current version. Also note that this same installer works as a first time installer as well: if it founds a previous version, it will remove the previous version and install the current one. If it is run on a clean system, it will simply install the current application; there is no need to create separate upgrade and full installers.

<Upgrade Id='YOURGUID-7349-453F-94F6-BCB5110BA4FD'>
  <UpgradeVersion OnlyDetect='no' Property='PREVIOUSFOUND'
    Minimum='1.0.0' IncludeMinimum='yes'
    Maximum='3.0.0' IncludeMaximum='no' />
</Upgrade>

The removal of the previous version is completely automatic. If, for any reason, we need to perform any action when this previous version is removed, we can author a custom action whose condition is the UPGRADINGPRODUCTCODE property. The Windows Installer only sets this property when the automatic removal is in progress, not when the application is manually uninstalled using Add and Remove Programs.

<InstallExecuteSequence>
  <Custom Action=' ... ' After=' ... '>UPGRADINGPRODUCTCODE</Custom>
</InstallExecuteSequence>

4.3 Patchwork

It wouldn't be terribly effective to create an upgrade installation package with megabytes of files in it just because one or two small files inside have to be renewed. In cases like this, patching has always been a better solution. Patches basically contain the difference between the old and the new versions and can automagically turn the old file on the user's computer into the new one. Patch packages can also contain new files to be deployed.

The WiX toolset can also create patch installation packages (.msp files). These are created from two normal installation packages: the original one with the old, erroneous files and a new one with the fixed files. In the downloadable SamplePatch we have two very simple installation packages, without user interface. Both install a single file that will change between the original and patched versions. The details of the source files must be completely familiar by now. Our Error.wxs and Fixed.wxs source files will only differ in their Source file references:

  <File Id='FoobarEXE' Name='FoobarAppl10.exe' DiskId='1' Source='Error\FoobarAppl10.exe' KeyPath='yes' />

versus

  <File Id='FoobarEXE' Name='FoobarAppl10.exe' DiskId='1' Source='Fixed\FoobarAppl10.exe' KeyPath='yes' />

The patch will be created from a third source file. It is, just like the earlier ones, an XML file but the contents are different from the files we've created so far:

<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
  <Patch AllowRemoval='yes' Manufacturer='Acme Ltd.' MoreInfoURL='www.acmefoobar.com'
    DisplayName='Foobar 1.0.1 Patch' Description='Small Update Patch' Classification='Update'>
 
    <Media Id='5000' Cabinet='Sample.cab'>
      <PatchBaseline Id='Sample' />
    </Media>
 
    <PatchFamily Id='SamplePatchFamily' Version='1.0.0.0' Supersede='yes'>
      <ComponentRef Id='MainExecutable' />
    </PatchFamily>

  </Patch>
</Wix>

The Classification attribute can be Hotfix, Security Rollup, Critical Update, Update, Service Pack or Update Rollup. AllowRemoval decides whether the user can later uninstall the patch or not.

A PatchFamily tag encompasses the items to be patched. Supersede decides whether this actual patch supersedes all previous patches in this same family.

Building will be a little bit more complicated than our previous projects. First, we build the two base packages the usual way. Both will go into its own folder:

candle.exe Error.wxs
light.exe -out Error\Product.msi Error.wixobj

candle.exe Fixed.wxs
light.exe -out Fixed\Product.msi Fixed.wixobj

Then we use another WiX tool, torch to create a transform between the two installation packages. The command line arguments instruct the program to use WiX's own formats, .wixpdb and .wixmst rather than those of Windows Installer (.msi and .mst).

torch.exe -p -xi Error\Product.wixpdb Fixed\Product.wixpdb -out Patch.wixmst

We also have to build our patch package using the usual WiX compiler and linker but this time, the output format will be different from the usual one, .wixmsp. There is no need to tell the linker to produce this kind of file, it will determine it automatically based on the contents of the source file.

candle.exe Patch.wxs
light.exe Patch.wixobj

And finally, we build the actual Windows Installer patch package from the result of the previous step and the transform we created a while ago. Pyro, the WiX tool responsible for patch creation needs not only the name of the transform file but the corresponding PatchBaseline/@Id attribute as well on the command line:

pyro.exe Patch.wixmsp -out Patch.msp -t Sample Patch.wixmst

The Patch.msp will be the patch installer actually distributed. To test it, first install the original package (Error/Product.msi), then add the patch:

msiexec /p Patch.msp

... and check that the file has indeed been changed to the new version. Then, go into Add and Remove Programs, allow it to Show Updates, and remove first the patch (the changed file will revert back to the original one) and then the program itself.

An administrator can update the administrative source image located at //server/Patch.msi to a new source image that is the same as would be produced by an administrative installation from a fully updated distribution media.

msiexec /a //server/Patch.msi /p Patch.msp

Members of the workgroup using the program then must reinstall the application from the new administrative source image to receive the update. To completely reinstall the applications and cache the updated .msi file on the user's computer, users enter:

msiexec /fvomus //server/Patch.msi

4.4 Fragments

Fragments offer a way to break up large installation projects. Basically, a fragment is a wrapper put around a WiX source unit, be it as simple or as complicated as we like. Similarly to object files or libraries we use in program development, a fragment can be linked into other product installation packages. If you consider the two WiX tools we have, Candle and Light like compiler and linker, fragments, just like source code modules, can be compiled independently into object code (.wixobj files). Rebuilding of a large installation package can be sped up dramatically by using the usual makefile approach, only recompiling .wxs source files that have been changed since the last compilation and linking the .wixobj files, fresh and old, together to form the final package.

As we can't have such large and complex projects as samples in a tutorial, our SampleFragment will be necessarily somewhat constructed. We reuse our first installer but we outsource some parts to separate fragments in order to illustrate our point. We removed the component that installs the user manual file, thus, the following reference will point to nowhere:

  <Feature Id='Complete' Title='Foobar 1.0' Description='The complete package.'
    ...

    <Feature Id='Documentation' Title='Description' Description='The instruction manual.' Level='1'>
      <ComponentRef Id='Manual' />
    </Feature>
  </Feature>

We place the removed component into a file of its own, inside a Fragment tag. Here we only refer to the Directory we have already declared in our main source file because we can't declare the same thing in two places. Anything that can be delegated into a fragment has its variant tag: to refer to a feature defined elsewhere, we use FeatureRef, to refer to a property, we use PropertyRef.

<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
  <Fragment Id='FragmentManual'>
    <DirectoryRef Id='INSTALLDIR'>
      <Component Id='Manual' Guid='YOURGUID-574D-4A9A-A266-5B5EC2C022A4'>
        <File Id='Manual' Name='Manual.pdf' DiskId='1' Source='Manual.pdf' KeyPath='yes'>
          <Shortcut Id="startmenuManual" Directory="ProgramMenuDir" Name="Instruction Manual" Advertise="yes" />
        </File>
      </Component>
    </DirectoryRef>

  </Fragment>
</Wix>

We could already compile and link these two files into an installation package. Note that we didn't need to add a single line to the source to make the two compilation units link together. The fact that we referenced a component from one file that was defined in the other one is enough for the linking to happen as we expect it. Referencing one element from a fragment opens up the whole fragment and makes all its elements immediately available. This behavior is different from what we are used to with the usual programming languages: fragment linking not only makes it possible to use elements defined elsewhere, using at least one of them will always switch on everything from the fragment referenced.

To build the sample, use the commands:

candle.exe SampleFragment.wxs Manual.wxs
light.exe -out SampleFragment.msi SampleFragment.wixobj Manual.wixobj

Fragments have many uses not only inside a single setup project but to share common items between different projects as well. For instance, if you have several related applications that share one or more common files (eg. device drivers or other functionality in DLLs), you might need to ensure that removing one application will not remove the common file that is still needed by the other one.

In this case, put your common file into a fragment of its own and refer to this fragment from all of your separate application setups. As the component (and, therefore, the component GUID) is the same, Windows Installer will be able to keep track of all the application requiring the common files, including properly enforcing versioning rules so that a newer version will never be overwritten by an older one.

4.5 Mergers

Fragments help divide a large package into manageable chunks of source code and make developer co-operation and code reuse possible. Therefore, it is best suited to in-house development where WiX source code can and will be shared. But there is another mechanism more capable of providing complete installation packages for other parties to use: merge modules. If, say, your product A depends on product B of another vendor, you can use their merge module meant to install their product and make it a part of your own so that both will be installed at the same time.

Setting up a merge module is very similar to the standalone source files we've used so far. However, instead of a Product, we specify a Module tag. Unlike with earlier packages, here we have to provide a unique GUID ourselves:

<?xml version="1.0" encoding="windows-1252"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Module Id="File1" Language="0" Version="1.2.3">

    <Package Id="YOURGUID-8DEE-4410-990A-1802896C4209" InstallerVersion="100"
      Languages="1033" Manufacturer="dev" SummaryCodepage="1252" AdminImage="no" />

      <Directory Id='TARGETDIR' Name='SourceDir'>
        <Directory Id='ProgramFilesFolder' Name='PFiles'>
          <Directory Id='Acme' Name='Acme'>
            <Directory Id='INSTALLDIR' Name='Foobar 1.0'>

              <Component Id="File1" Guid="YOURGUID-CF0E-40AB-ACC5-0E9A5F112628">
                <File Id="File1.txt" Name="File1.txt" Source="File1.txt" KeyPath='yes' />
              </Component>

            </Directory>
          </Directory>
        </Directory>
      </Directory>

  </Module>
</Wix>

In our sample, we use a second merge module as well. Its source file almost identical to the previous one, with the following differences: we refer to a different file to deploy and we include a Dependency tag to indicate that the second module is dependent on the first one. For the dependency, we use the Module Id identifier with the module's Package GUID appended—but we have to replace the original dashes by underscore characters:

  <Module Id="File2" Language="0" Version="1.2.3">
    ...
    <File Id="File2.txt" Name="File2.txt" Source="File2.txt" KeyPath='yes' />
    ...
    <Dependency RequiredId="File1.YOURGUID_8DEE_4410_990A_1802896C4209"
      RequiredLanguage="0" RequiredVersion="1.2.3" />
  </Module>

To create a unified installation package, we write a usual standalone WiX source file:

<?xml version="1.0" encoding="windows-1252"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Name='Foobar 1.0' Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE' UpgradeCode='YOURGUID-7349-453F-94F6-BCB5110BA4FD'
    Language='1033' Codepage='1252' Version='1.0.0' Manufacturer='Acme Ltd.'>

    <Package Id='*' Keywords='Installer' Description="Acme's Foobar 1.0 Installer"
      Comments='Foobar is a registered trademark of Acme Ltd.' Manufacturer='Acme Ltd.'
      InstallerVersion='100' Languages='1033' Compressed='yes' SummaryCodepage='1252' />

    <Media Id="1" Cabinet="product.cab" EmbedCab="yes" />

    <Directory Id='TARGETDIR' Name='SourceDir'>
      <Directory Id='ProgramFilesFolder' Name='PFiles'>
        <Directory Id='Acme' Name='Acme'>
          <Directory Id='INSTALLDIR' Name='Foobar 1.0'>

Instead of the usual components, we refer to the merge modules. The SourceFile mentions the .msm files:

            <Merge Id="file1" Language="1033" SourceFile="Module1.msm" DiskId="1" />
            <Merge Id="file2" Language="1033" SourceFile="Module2.msm" DiskId="1" />

          </Directory>
        </Directory>
      </Directory>
    </Directory>

    <Feature Id="Msm" Title="Msm" Level="1">
      <MergeRef Id="file1" />
      <MergeRef Id="file2" />
    </Feature>

  </Product>
</Wix>

You can download the complete SampleMergeModule. You need to build the merge modules separately:

candle.exe Module1.wxs
light.exe Module1.wixobj

candle.exe Module2.wxs
light.exe Module2.wixobj

candle.exe SampleMerge.wxs
light.exe SampleMerge.wixobj
Copyright © 2004-2010, Gábor DEÁK JAHN, Tramontána
Comments and contributions are most welcome                   Comments and contributions are most welcome
A note: some people use a very braindead spam filtering idea actually generating extra amounts of spam: asking the sender to reply to an acknowledgment message in order to be placed on a whitelist and allowed through. All those who do so, unfortunately, will never receive a reply from me: I refuse this idea both in theory and practice.