WiX v5 is here! Let us help.

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='PUT-GUID-1-HERE' UpgradeCode='PUT-GUID-2-HERE'
        Language='1033' Codepage='1252' Version='1.0.0' Manufacturer='Acme Ltd.'>
        <Package Description="Acme's Foobar 1.0 Installer" Compressed='yes' />

        ...
    </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='PUT-GUID-1-HERE' UpgradeCode='PUT-GUID-2-HERE'
    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 less than or equal to the version specified in Maximum while IncludeMaximum=no only finds those less than 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='PUT-GUID-2-HERE'>
    <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 before AppSearch, 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 Property attribute (SELFFOUND and 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:

<Upgrade Id='PUT-GUID-2-HERE'>
    <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>