When I create an MSI-based installer, one of the things I typically include in the setup authoring is some logic to allow me to implement Windows Installer major upgrades if/when I need to release a new version of the MSI in the future. I typically include this authoring even if I don't currently plan to release a future version that I would need to use it for. Doing so gives me flexibility if/when I decide to update the MSI in the future.
There are some specific things you need to include in your MSI to enable major upgrades, and as I've learned about how to do this, I've found that the information is scattered around in several different places (MSDN, WiX documentation, blog posts, etc). As a result, I decided to create a couple of simple samples that I copy and paste from each time I create new MSI-based installers using WiX v3.0.
This blog post will use the samples that I created and posted at http://cid-27e6a35d1a492af7.skydrive.live.com/self.aspx/Blog%7C_Tools/WiX%20Samples/wix%7C_sample%7C_major%7C_upgrade.zip to demonstrate how to implement Windows Installer major upgrade functionality in an MSI created with WiX v3.0.
Step 1: Add upgrade information needed to cause new versions to upgrade older versions
In order to allow major upgrades, an MSI must include the following information:
- An UpgradeCode property in the Property table. In WiX v3.0, this means you must include an UpgradeCode attribute in your Product element. From the example, this looks like the following:
<Product Id="$(var.Property_ProductCode)" UpgradeCode="!(loc.Property_UpgradeCode)" Name="!(loc.Property_ProductName)" Language="!(loc.Property_ProductLanguage)" Version="$(var.ProductVersion)" Manufacturer="!(loc.Property_CompanyName)">
- An entry in the Upgrade table that defines the range of previous versions that should be upgraded by the new MSI. Typically, I use a range from version 1.0.0 up to the current MSI's version, not including the current version. From the example, this looks like the following:
<UpgradeVersion Minimum="1.0.0" IncludeMinimum="yes" Maximum="$(var.ProductVersion)" IncludeMaximum="no" Property="OLDERVERSIONBEINGUPGRADED" />
The exact name of the property specified in the UpgradeVersion element does not matter, but it must be in all capital letters to indicate to Windows Installer that it is a public property.
- An entry in the InstallExecuteSequence table that schedules the RemoveExistingProducts action. There are several options for where you can schedule RemoveExistingProducts in your product’s sequence table, and you will need to review the options and choose the one that makes the most sense for your scenarios. You can find a summary of the options at http://msdn.microsoft.com/library/aa371197.aspx. In my example, I chose to schedule it after InstallInitialize, and that looks like the following:
<RemoveExistingProducts After="InstallInitialize" />
Note - Windows Installer looks for other installed MSIs with the same UpgradeCode value during the FindRelatedProducts action. If you don't specifically schedule the FindRelatedProducts action in your setup authoring, WiX will automatically schedule it for you when it creates your MSI. I did not schedule FindRelatedProducts in the sample I created for this reason.
Step 2: Add logic to handle out-of-order installations (installing version 2 then trying to install version 1)
The information provided in step 1 will allow your MSI to uninstall older versions of your MSI during the install process for newer versions. In order to be complete, you should also include information in your MSI to handle scenarios where a user attempts to install a newer version of your MSI and then install an older version afterwards (an out-of-order installation). This step is not strictly necessary, but including this information in your MSI allows you to provide a more user-friendly experience in the case of an out-of-order installation scenario.
Detecting an out-of-order installation requires authoring another entry in the Upgrade table that defines a property that will be set if a newer version of the MSI is found on the user's system. From the example, this looks like the following:
<UpgradeVersion Minimum="$(var.ProductVersion)" OnlyDetect="yes" Property="NEWERVERSIONDETECTED" />
Once you have defined the detection property, you need to decide how you want your MSI to behave in an out-of-order installation scenario and author an appropriate custom action. There are a couple of options:
- Block installation. You can define and schedule a type 19 custom action to block installation if this property is set. The custom action must be run after the FindRelatedProducts action, because that action is the one that will search for other versions of the MSI and set the property if any are found. From the example, this looks like the following:
<CustomAction Id="CA_BlockOlderVersionInstall" Error="!(loc.LaunchCondition_LaterVersion)" />
<Custom Action="CA_BlockOlderVersionInstall" After="FindRelatedProducts">NEWERVERSIONDETECTED</Custom>
<Custom Action="CA_BlockOlderVersionInstall" After="FindRelatedProducts">NEWERVERSIONDETECTED</Custom>
In the example, this custom action is scheduled in both the InstallExecuteSequence table and the InstallUISequence table so that an out-of-order installation will be blocked if the user runs the MSI in silent mode or in full UI mode.
- Immediately exit and return success. This requires creating an immediate custom action that returns exit code 5 (ERROR_NO_MORE_ITEMS). WiX v3.0 has a built-in custom action named WixExitEarlyWithSuccess that can be used to enable this functionality. To use the built-in custom action, you must make sure that the property created above is named NEWERVERSIONDETECTED exactly like it is in the example I posted. Then, you must add a reference to the custom action by using the following syntax:
You must also reference the WixUtilExtension, either by adding it to the references list for your project if you are using Votive and Visual Studio, or by passing it into light.exe with the -ext command line switch.
A question that I have been asked in the past is why would an MSI want to immediately exit and return success instead of blocking and returning an error in an out-of-order installation scenario? The primary reason for this type of behavior is backwards compatibility for calling applications, particularly if the MSI is a redistributable component that can be shipped and installed as a part of other products. Here are a couple of examples of this type of scenario:
- The .NET Framework 2.0 ships as a part of Visual Studio 2005 setup, and the .NET Framework 2.0 SP1 ships as a part of Visual Studio 2008 setup (inside of the .NET Framework 3.5). These versions of Visual Studio can be installed side-by-side on the same system, but the .NET Framework 2.0 SP1 performs a major upgrade of the .NET Framework 2.0 so it cannot be installed side-by-side. If you have the .NET Framework 2.0 SP1 installed and attempt to repair Visual Studio 2005, it will fail with an error because it tries to re-run the .NET Framework 2.0 installer that it shipped with, and .NET Framework 2.0 setup fails because there is already a newer version of that MSI on the system. It would have been preferable for the .NET Framework 2.0 installer to exit and return success so that Visual Studio 2005 could continue its repair process and return success at the end instead of returning an error.
- The Games for Windows - LIVE redistributable ships updates as major upgrades. Games that ship with earlier versions of the Games for Windows - LIVE redistributable need to be able to install and repair successfully even if a newer version of this redistributable is on the system. This means that the game installer should be able to invoke the older version of the Games for Windows - LIVE redistributable installer that it shipped with and have it return an exit code indicating success in this scenario.
Step 3: Build version 1 and version 2 of your MSI
Creating version 1 of your MSI is as simple as running your standard build process - in WiX v3.0, this means you compile and link it with the WiX toolset. In order to create version 2 of your MSI, you must make the following changes to your setup authoring, then re-run your build process to create a new MSI:
- Increment the ProductVersion value in the Property table to be higher than any previous versions that you have shipped. Windows Installer only uses the first 3 parts of the version in upgrade scenarios, so make sure to increment your version such that one of the first 3 parts is higher than any previously shipped version. For example, if your version 1 uses ProductVersion value 22.214.171.124, then version 2 should be set to 126.96.36.199 or higher (188.8.131.52 will not work here). In WiX v3.0, you use the Version attribute in the Product element to define the product version.
- Generate a new ProductCode value in the Property table of the new version of the MSI.
In the example I created, I included scripts you can run on a system that has a build of WiX v3.0 installed in order to build 2 sets of MSIs:
- The scripts named build_wix_sample_major_upgrade_block_older_v1.0.1.bat and build_wix_sample_major_upgrade_block_older_v1.0.2.bat will build version 1 and version 2 of an MSI that will block out-of-order installations.
- The scripts named build_wix_sample_major_upgrade_ignore_older_v1.0.1.bat and build_wix_sample_major_upgrade_ignore_older_v1.0.2.bat will build version 1 and version 2 of an MSI that will immediately exit and return success for out-of-order installations.
Step 4: Test upgrade scenarios BEFORE YOU SHIP VERSION 1
This step is very important and in my past experience it has been too often ignored. In order to make sure that upgrade scenarios will behave the way you expect, you should test upgrades before you ship the first version of your MSI. There are some upgrade-related bugs that can be fixed purely by making fixes in version 2 or higher of your MSI, but there are some bugs that affect the uninstall of version 1 that must be fixed before you ship version 1. Once version 1 ships, you are essentially locked into the uninstall behavior that you ship with version 1, and that impacts major upgrade scenarios because Windows Installer performs an uninstall of version 1 behind the scenes during version 2 installation.
Here are some interesting scenarios to test:
- Install version 1, then install version 2. Make sure that version 1 is correctly removed and version 2 functions correctly. Make sure version 2 cleanly uninstalls afterwards.
- Install version 2, then try to install version 1. Make sure that version 1 correctly detects that version 2 is already installed and either blocks or silently exits, depending on what behavior you choose to implement for your out-of-order installation scenarios.
When testing major upgrade scenarios, make sure to pay particular attention to custom actions in your MSI and assemblies that need to be installed to the GAC or the Win32 WinSxS store. Here are a few blog posts I've written in the past that contain examples of issues found during major upgrade testing:
- Assemblies may be missing from the GAC or the WinSxS store after major upgrades - http://blogs.msdn.com/astebner/archive/2007/02/08/assemblies-may-be-missing-from-the-gac-or-winsxs-cache-after-an-msi-major-upgrade.aspx
- Configuring custom action conditions to behave properly during standard uninstalls and major upgrade uninstalls - http://blogs.msdn.com/astebner/archive/2007/09/06/4798334.aspx
- Using the UPGRADINGPRODUCTCODE property to condition custom actions to not run during a major upgrade uninstall - http://blogs.msdn.com/astebner/archive/2007/05/31/3012427.aspx
- Enabling in-place assembly upgrades - http://blogs.msdn.com/astebner/archive/2008/08/10/8847259.aspx
Another more detailed example
While working on the sample for this blog post, I found the blog post at http://blogs.technet.com/alexshev/archive/2008/02/15/from-msi-to-wix-part-8-major-upgrade.aspx. This blog post contains more specific details about how Windows Installer behaves behind the scenes during a major upgrade, what information is required in an MSI for major upgrades, and how to map the information in the MSI rows/tables to WiX syntax. The example in that blog post was created with WiX v2.0 whereas the sample I posted uses WiX v3.0. The underlying concepts are the same, and the WiX syntax is only slightly different between v2.0 and v3.0. If you are interested in more detailed behind-the-scenes information about how major upgrades work, and/or if you are trying to implement a major upgrade that uses different settings than the ones presented in my example, I encourage you to check out this blog post as well.
<update date="3/12/2009"> Fixed broken link to the sample WiX source code for this post. </update>