I had some interesting challenges when integrating WiX based MSI installer generation into my build scripts. My previous post (NAnt and WiX Versioning) details how I ended up injecting version numbers into the MSI build. In this post, I detail how to ensure that each newly generated installation file will cleanly replace the previous.

When your build script is producing an installer, you want each installation to install cleanly, even if a previous version was already present. The most straightforward way to achieve this with an MSI installer is to build each as a major upgrade (in MSI terms) which removes any existing installation automatically. Achieving this requires a number of elements to interact in a precise manner.

To begin, use your favourite tool to define an UpgradeCode – a unique GUID that must remain constant through all versions of the product.

For ease of reference (since the upgrade code needs to be repeated in a couple of locations), define the upgrade code as a variable at the top of your WXS file.

<?define UpgradeCode="{YOURGUID-GOES-HERE-9586-B92717D03E90}"?>

This ensures that every reference is identical, avoiding some subtle potential bugs.

The UpgradeCode should remain unchanged throughout the lifetime of your product – it is the one thing in the installer that remains constant. Also, the Upgrade code should be unique to the product you’re installing – never reuse the same upgrade code for multiple different product lines.

**Note: **You should always include an upgrade code in every installer, even if you do nothing else. Without an upgrade code, it is impossible for any future installer to be an upgrade.

The upgrade code variable should then be referenced from the Product element.

<Product Id="YOURGUID-GOES-HERE-0123-012345678901" 
         Name="My Example Product" 
         Language="1033" 
         Version="$(var.version)" 
         Manufacturer="Niche Software" 
         UpgradeCode="$(var.UpgradeCode)">
    ...

</Product>

With the upgrade code in place, the next step is to ensure that new product and package GUIDs are used for each installer built.

Fortunately, the WiX toolset provides a simple way to achieve this – instead of supplying a hard coded Id for each, specify a wildcard to request autogeneration of an appropriate GUID.

For WiX 2.x, use a sequence of “?” characters like this:

<Product Id="????????-????-????-????-????????????" 
         Name="My Example Product" 
         Language="1033" 
         Version="$(var.version)" 
         Manufacturer="Niche Software" 
         UpgradeCode="$(var.UpgradeCode)">
    ...

</Product>

For WiX 3.x, a simpler wildcard “*” can be used instead.

<Product Id="*" 
         Name="My Example Product" 
         Language="1033" 
         Version="$(var.version)" 
         Manufacturer="Niche Software" 
         UpgradeCode="$(var.UpgradeCode)">
    ...

</Product>

The same autogeneration needs to be applied to the Package element.

<Package Id="????????-????-????-????-????????????" 
         Description="Installer for Example Product" 
         InstallerVersion="200" 
         Compressed="yes" />
    ...

</Package>

Again, if you are working with WiX 3.x, use “*” instead.

As a part of the installer, include an Upgrade section to define upgrade policy.

<Upgrade Id="$(var.UpgradeCode)">

    <!-- Detect any newer version of this product -->
    <UpgradeVersion Minimum="$(var.version)"
                    IncludeMinimum="no"
                    OnlyDetect="yes"
                    Language="1033"
                    Property="NEWPRODUCTFOUND"/>

    <!-- Detect and remove any older version of this product -->
    <UpgradeVersion Maximum="$(var.version)"
                    IncludeMaximum="yes"
                    OnlyDetect="no"
                    Language="1033"
                    Property="OLDPRODUCTFOUND"/>

</Upgrade>

Using the UpgradeCode and Version variables, this policy may define one of two properties. If an installation of this product with a greater version number is found, the property NEWPRODUCTFOUND will be defined. If an installation of this product with a lower or equal version number is found, the property OLDPRODUCTFOUND will be defined.

The policy shown here is sufficient to automatically uninstall any old version when installing a newer one.

To prevent downgrading, add the following.

<!-- Define a custom action --> 
<CustomAction Id="PreventDowngrading" 
              Error="Newer version already installed."/>

<InstallExecuteSequence>

    <!-- Prevent downgrading -->
    <Custom Action="PreventDowngrading"
            After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
    <RemoveExistingProducts After="InstallFinalize" />

</InstallExecuteSequence>

<InstallUISequence>

    <!-- Prevent downgrading -->
    <Custom Action="PreventDowngrading"
            After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>

</InstallUISequence>

With all of these elements in place, each new build of your installer should cleanly replace the previous.

I hope you’ve found this helpful.

Comments

blog comments powered by Disqus