Thoughts about setup and deployment issues, WiX, XNA, the .NET Framework and Visual Studio
All postings are provided AS IS
with no warranties, and confer no rights. Additionally, views expressed
herein are my own and not those of my employer, Microsoft.
I recently read about an issue that affects Windows Installer products that use major upgrades and also install assemblies to the GAC or the WinSxS component store using the MsiAssembly and MsiAssemblyName tables. This issue is documented in the Microsoft Knowledge Base article at http://support.microsoft.com/kb/905238.
To summarize the issue, if you build an MSI that installs assemblies and includes major upgrade functionality, and then build a new version of the MSI that invokes a major upgrade, you may see the assemblies removed from the GAC or the WinSxS store after the major upgrade completes. This happens if you do not change the assembly version for your assemblies and have not scheduled the RemoveExistingProducts action to occur after the InstallFinalize action.
If you are running into this issue with your MSI, you can workaround this issue in one of the following 2 ways:
If you change the scheduling, you do not necessarily need to increment the assembly version number for each of your assemblies.
One important note here - the sample WiX files for the Q and Z sample applications that ship in the Windows Media Center SDK for Windows Vista are susceptible to this problem. I have updated the downloadable WiX v3.0 projects for the Q application (that can be downloaded from this location and that is described here) and the Z application (that can be downloaded from this location and that is described here) to address this. The specific change I made in q.wxs and z.wxs implements the 2nd option listed above by changing this line in each WXS file:
<RemoveExistingProducts After="InstallInitialize" />
<RemoveExistingProducts After="InstallInitialize" />
<RemoveExistingProducts After="InstallFinalize" />
<RemoveExistingProducts After="InstallFinalize" />
If you have the Media Center SDK installed and want to make this fix for the WiX v2.0 files that are installed as part of the SDK, you can make equivalent changes to the versions of q.wxs and z.wxs included there.
Having RemoveExistingProducts after InstallFinalize comes with its own set of issues. It's not in the scripted part of the install, so if that REP uninstall fails and rolls back then you're left with both old and new products on the system. When REP is in the scripted part a roll back in the upgraded install and in the remove of the older one restore the system to its original state. The after InstallFinalize behavior is documented as "If the removal of the old application fails, then the installer only rolls back the uninstallation of the old application." but perhaps the consequences of that (leaving both old and new on the system) aren't quite so obvious. Since a repair of the product will install the missing assemblies anyway, my vote for the least-dangerous course would be to keep REP in the safest place - just after InstallInitialize.
Hi PhilDWilson - I'm not sure I agree that the least dangerous course is to leave RemoveExistingProducts before InstallInitialize. This can cause your product assemblies to be not installed correctly in major upgrade scenarios, and then your users have to figure out that they will need to run a repair to fix it. I understand the possible risk of the RemoveExistingProducts uninstall failing and leaving an extra copy of the product on the system, but that seems less invasive than leaving an install with missing assemblies.
Overall, the cleanest solution is to update assembly versions for each major upgrade, but I know that is not always feasible for every software project for various reasons.
It's a judgement call, yes, but I often ask the question "what's the worst that that can happen". In this case, imagine a help desk trying to solve the problem of having both old and new products on the system, conflicting with each other, services all mixed up, COM registration all mixed up etc because one product was supposed to replace the other, not co-exist with it. Now imagine a help desk call where the answer is "right-click the MSI file and choose repair", and that call may not have even been necessary in the first place if a shortcut invoked the repair. So we'll agree to disagree on this one ;=)
Hi PhilDWilson - Your help desk call analogy is a useful question to ask in scenarios such as this, thank you for providing this example.
I do want to point out, however, that leaving RemoveExistingProducts before InstallFinalize and doing a major upgrade of an MSI that includes assemblies with the same assembly version is guaranteed to result in missing assemblies. The other case (putting RemoveExistingProducts after InstallFinalize and having the old product uninstall fail) might fail, but is not guaranteed to cause problems like the other scenario. The help desk people may have to dig a bit deeper in this latter case, but I think the frequency of calls may be less with the recommendation in this blog post.
That is a trade-off that each setup developer needs to weigh for their own product though, and my blog post should be taken only as a recommendation, not as the only option to approach this issue.
I ran into an interesting scenario related to Windows Installer major upgrades recently and I wanted
It's unclear from reading the KB article and your post if this issue would still apply if the File Version of the assembly was incremented and the FileVersion was set in the MsiAssemblyName table, or if it only applies when the assemblies are exactly the same and thus would not otherwise need be copied by the new package. Do you have any idea?
Hi Jlevine - The FileVersion value is used when doing in-place upgrades of assemblies (for example, when applying a patch to an installed product) as described at http://msdn2.microsoft.com/en-us/library/Aa372360.aspx.
However, my understanding is that the issue described in this blog and the KB article at http://support.microsoft.com/kb/905238 happens if the assembly identity is the same, which happens if the AssemblyVersion attribute is the same in both the old and the new version of your MSI.
My understanding could be wrong though, so it may be worth trying a test major upgrade scenario where you only update the assembly's FileVersion to see how it behaves.
When I create an MSI-based installer, one of the things I typically include in the setup authoring is
We have a msi package which installs assemblies into the GAC. We use major upgrades to update our products. In the updated package, we sequence the RemoveExistingProducts standard action , right after InstallInitialize.
However, this is resulting in a problem where the files are missing from the GAC after an upgrade. This problem has been described here:
To overcome this issue, there are multiple approaches:
i)Increment the assembly version of the files which go into the GAC with every upgrade.
This is not an option for us , as these files are sourced from Merge Modules, which are owned by completely different teams. Additionally, the merge modules need a rebuild for the assembly version to be incremented. Since these files do not change across releases, rebuilding Merge Modules just for the sake of incrementing Assembly versions is not an option.
ii)Sequence RemoveExistingProducts after InstallFinalize
However, this comes associated with a risk that if the uninstall of the older product fails, we will be left with 2 products registered on the system. hence, we would like to avoid this option.Additionally, doing this is also resulting in numerous uninstallaiton issues with shared components as described in :blogs.msdn.com/.../major-upgrades-with-shared-components.aspx
A lot of resources are not cleaned up after an uninstall.
iii)Change the component GUID of the components which install into the GAC with every upgrade.
This though seems to work, i am not sure if this is a good solution. As per the MSDN recommendation, you should change the component GUID only when the location or the key path changes. Since this is not the case here, i am a bit hesitant to adopt this approach.
iv)Have the REINSTALLMODE=emus property set in the property table.
I wanted to know as to what is the recommended solution here?
I am hesitant to use solutions ii) and iii).
Are there any other safe solutions which we could make use of?
Any help would be very much appreciated.
Hi Problems with major upgrades with assemblies in the GAC - recommended solution - What I'd recommend in this scenario is using a chainer. This would allow you to build one MSI that includes the assemblies that you're picking up from the merge modules and one MSI that includes the payload that you build that needs to change during each major upgrade. If you're using WiX v3.6, you can use the Burn chainer technology to accomplish this. There is more information about Burn at wix.sourceforge.net/.../authoring_bundle_intro.htm.
If a chainer isn't an option for you, then the solutions for this scenario all have drawbacks. The option I'd probably lean towards is sequencing RemoveExistingProducts after InstallFinalize, but as you noted, that solution has potential issues if you run into failures during installation.
As long as you're following Windows Installer component authoring best practices, I don't think you should have to worry about the uninstall issues that Heath describes in his blog post. The example uninstall issue that Heath describes should be contained to scenarios where you change the composition of a shared component between versions (his example has foo.dll and bar.dll in the component in 1st version of the product but only has foo.dll in the component in the 2nd version of the product).
What about REINSTALLMODE= emus?
Other than being inefficient, do you think there are other drawbacks here?
Hi Kiran Hegde - It is fine to set REINSTALLMODE = emus in your MSIs. The REINSTALLMODE flag that I recommend avoiding is "a" because it will cause an MSI to revert a file to an older version if a newer one exists on the computer, which is almost always a bad idea.
I'm not sure if using REINSTALLMODE = emus will fix the issue you're seeing where assemblies are not in the GAC after a major upgrade though. You'll have to give it a try and see what happens.
I am reigniting this thread again w.r.t to the GAC issue which i had mentioned earlier, as i had 2 additional questions:
i)Would sequencing RemoveExistingProducts before CostInitialize be a good solution?
I have tried it and am observing that i am not seeing MigrateFeatureStates being invoked. RemoveExistingProducts gets invoked and runs fine.
I see the following message: Skipping Migratefeaturestates action: not run in maintenance mode.
Our installer has a single feature at the moment. So it is not an issue.
This seems to have been recommended earlier on the web.
Do you see any issues with this?
ii)I have also figured out that if you can populate the FileVersion attribute for assemblies which go into the GAC in the MsiAssemblyName table, this problem would be resolved.
Is there something i need to do to get the FileVersion attribute populated?
Thanks in advance,
Hi Kiran Hegde - According to the documentation at msdn.microsoft.com/.../aa371197(v=vs.85).aspx, it isn't permitted to schedule RemoveExistingProducts before InstallValidate, and CostInitialize is scheduled before InstallValidate, so your sequencing suggestion won't work.
When RemoveExistingProducts runs, it will do an uninstall of the old product, and that will cause the entire uninstall log to be included within the verbose MSI log for your installation process. The item you saw about MigrateFeatureStates not running in maintenance mode might be from the uninstall process invoked by RemoveExistingProducts as opposed to from the install of the new version of your product. I'd suggest looking for other instances of the string MigrateFeatureStates in your log file to see if it appears elsewhere.
If you're using WiX to build your MSI, you can use the suggestions listed at blogs.msdn.com/.../8847259.aspx to make the WiX build process populate the FileVersion attribute in your MsiAssemblyName table.