Subtle problem with major upgrades in WiX files in the Media Center SDK

One of the features included in the WiX v3.0 source files included in the C# project template in the latest version of the Windows Vista Media Center SDK is a group of settings to make it easy to implement a Windows Installer major upgrade when building new versions of the MSI-based installer.  A major upgrade allows users to run the install for the new version of the MSI and have it automatically uninstall any older version(s) of the MSI that are installed on the system.  Doing this will ensure that users will only have one version on their system at a given time, and that the installed version is the latest version.

Recently, Charlie was working on a Windows Vista Media Center application using the new Media Center SDK project template, and he built an MSI-based installer using the WiX v3.0 source files that are included in the project template.  He ran into an issue when trying to deploy version 2 of his MSI on a system that already had version 1 installed.  In this scenario, he found that the application was left in an unregistered state with Media Center (meaning that it did not appear in the Media Center start menu or the Program Library).  The application files were all installed in the expected locations though.

After looking at MSI log files and the WiX source code for a little bit, I found a subtle logic problem that caused this registration problem.  The WiX source files in the Media Center project template schedule the RemoveExistingProducts action after InstallFinalize in order to work around the bug in Windows Installer described in this knowledge base article.  However, the custom action to run RegisterMceApp.exe in uninstall mode is only conditioned on the REMOVE Windows Installer property, meaning it will run anytime the MSI is run in uninstall mode.  This scheduling sequence causes the uninstall of the previous version of the MSI to happen after the install of the new version of the MSI has been committed.  The combination of this scheduling sequence and the uninstall custom action condition causes the RegisterMceApp.exe custom action to run in uninstall mode after it has been run in install mode, and the application is left in an unregistered state after the major upgrade completes.

There are a few possible ways to work around this issue, each with its own pros and cons.  I will start with a recommendation in order to make it easier to find (since this blog post ended up being fairly long), but I encourage you to read the entire contents of the post as well in order to learn more details about what is happening behind the scenes in this scenario so you can make an informed decision for your application's deployment logic.

Recommendation:

In general, I recommend adding the condition described in Option 1 below to the RegisterMceApp.exe uninstall custom actions in order to resolve this issue.  The condition is specifically based on the state of the component that owns the XML registration file used by RegisterMceApp, and because of that, it is most closely tied to the custom action from a logical perspective.  It will help prevent this custom action from being run in all side-by-side install scenarios, and not only during Windows Installer major upgrades (unlike the condition described in Option 2).

In addition to adding this condition, I encourage you to review the alternatives for scheduling the RemoveExistingProducts action that are described in Option 3 below.  When creating the WiX files in the C# project template in the Media Center SDK, I attempted to choose a scheduling option that I thought would meet the widest range of Media Center application deployment scenarios.  However, there are definitely trade-offs that should be weighed here, and you may want to opt for a different solution depending on your application's scenarios.

Option 1: Add a condition to the RegisterMceApp uninstall custom action so it will only run when the registration XML file is being removed.

In this option, you can add the ($Registration.xml = 2) condition to the custom actions named CA_RegisterMceApp_AddIn_Unregister_Uninstall_Cmd and CA_RegisterMceApp_AddIn_Unregister_Uninstall.  To do this, you can change the conditions from the value <![CDATA[REMOVE]]> that is initially set for these custom actions to <![CDATA[REMOVE AND ($Registration.xml = 2)]]> in the setup.wxs file that is created when instantiating a new instance of the C# Media Center project template in version 5.2 of the Windows Vista Media Center SDK.

The syntax of the ($Registration.xml = 2) condition will evaluate to true if the action state of the Windows Installer component named App.xml evaluates to INSTALLSTATE_ABSENT (meaning that the component is in the process of being uninstalled).  You can see this MSDN topic for more details about Windows Installer conditional syntax if needed.

Option 2: Add a condition to the RegisterMceApp uninstall custom action so it will not run during the major upgrade.

In this option, you can add the UPGRADINGPRODUCTCODE condition to the custom actions named CA_RegisterMceApp_AddIn_Unregister_Uninstall_Cmd and CA_RegisterMceApp_AddIn_Unregister_Uninstall.  To do this, you can change the conditions from the value <![CDATA[REMOVE]]> that is initially set for these custom actions to <![CDATA[REMOVE AND NOT UPGRADINGPRODUCTCODE]]> in the setup.wxs file that is created when instantiating a new instance of the C# Media Center project template in version 5.2 of the Windows Vista Media Center SDK.

As described in this blog post, that property will only be set if an uninstall occurs as part of a Windows Installer major upgrade.

Option 3: Change the sequencing of RemoveExistingProducts so that it happens before the custom action that runs RegisterMceApp in install mode for the new version of the MSI.

In this option, you can change the relative sequence order of the RemoveExistingProducts action by changing the After value in the statement <RemoveExistingProducts After="InstallFinalize" /> to be a different action, or by removing the After value and using the Before value with a different action instead.

As described in the MSDN RemoveExistingProducts action topic, there are a few commonly used scheduling options for the RemoveExistingProducts action:

  1. Between the InstallValidate and InstallInitialize actions.  In this case, Windows Installer will completely remove the old version of the application before installing the new version.  This option is inefficient because all files that are the same between versions of the application will have to be deleted and re-copied.
  2. After InstallInitialize, but before any actions that generate entries in the Windows Installer execution script.
  3. Between the InstallExecute (or InstallExecuteAgain) and InstallFinalize actions.  Typically, these actions are scheduled in the following order: InstallExecute, RemoveExistingProducts then InstallFinalize.  When using this sequence, the new version of the application is installed, then old files that are a part of the old version of the application are removed.  If the uninstall fails for some reason, Windows Installer will roll back both the uninstall of the old version and the install of the new version.
  4. After the InstallFinalize action.  This option is the most efficient placement for the action from an installation performance perspective.  In this option, Windows Installer updates files before removing the old applications, but only the files being updated in the new version of the application get installed during the installation. If the uninstall of the old version fails, then Windows Installer rolls back the uninstallation of the old application, but leaves the new version installed.

The WiX files in version 5.2 of the Windows Vista Media Center SDK use the 4th scheduling option, which I chose initially when creating these files because it allows new versions of the application to use the same assembly version numbers as previous versions of the application.  This option also allows users to retain any customizations they have made to data files that are a part of your application's setup if the data files are used as key paths for the Windows Installer components that install them.  You can check out this blog post for more detailed information about how Windows Installer handles file replacement logic for unversioned key path files if you're interested.

Choosing scheduling options 1 or 2 will allow you to avoid the unregistration problem during Windows Installer major upgrades, but these options will require you to change assembly versions each time you release a new version of your application (in order to avoid this issue).  This means you must update the metadata in the assembly itself and in the RegisterMceApp XML file and any other places where your application's assembly is referred to by its full strong name identity.  In addition, options 1 and 2 will cause somewhat slower installation performance during major upgrades because any unchanged files will have to be uninstalled and re-installed.

Choosing scheduling option 3 will allow users to retain customizations that they make to data files like described in the explanation of scheduling option 4 above.  It will also allow for full transactionality during a major upgrade (meaning that if the uninstall of the old version fails, the old version will be rolled back to an installed state, and the new version will be rolled back so it is no longer installed).  However, this option also requires you to change the assembly version each time you release a new version of your application just like scheduling options 1 and 2.