Why Windows Installer removes files during a major upgrade if they go backwards in version numbers


Recently, I investigated an issue where some files were missing from a user’s PC after installing version 1 of a product and then upgrading to version 2 of the product.  I wanted to write about how we diagnosed and resolved the issue in more detail in case others run into similar issues in the future.

I started the investigation by looking in the verbose MSI log file for the version 2 upgrade process, and I found entries for the RemoveExistingProducts action that showed the missing files being uninstalled from the PC.  Later in the log, I found entries for the CopyFiles action that showed most of the files in version 2 being installed onto the PC, but I didn’t see any CopyFiles entries for a few of the files that should’ve been installed.

I dug a bit deeper, and early on in the log file, I found an entry like the following for each of the components that contained files that were missing after the upgrade finished:

MSI (s) (60:00) [12:34:56:789]: Disallowing installation of component: {GUID} since the same component with higher versioned keyfile exists

It turned out that each of the files that were missing after the version 2 upgrade had file versions that were lower in version 2 than they were in version 1.  The MSI in question has the RemoveExistingProducts action scheduled after the InstallInitialize action (based on this Windows Installer documentation), and Windows Installer evaluates whether or not it needs to install each of the components in version 2 of the MSI before it uninstalls the components in version 1.  Because a higher version of the component’s key path file is found on the PC, it decides that it needs to skip attempting to install the component.  However, after that decision has been made to skip attempting to install the component, the component is then removed during the RemoveExistingProducts action.

There are a few options to work around this issue, and we ended up choosing option 1:

  1. Re-build the affected files to ensure that they have higher file versions than any previously shipped versions, then re-package version 2 of the MSI with the updated files.
  2. Update the build process for the MSI to ensure that the files in newer builds of the MSI have higher file versions than in any older build of the MSI.
  3. Re-name the affected files and put them into new components in version 2 of the MSI.
  4. Schedule the RemoveExistingProducts action before the costing actions in version 2 of the MSI (as described on Stack Overflow). 

In my scenario, option 2 wasn’t technically feasible because the files in question were built by one team and delivered to another team to include in an MSI, option 3 wasn’t technically feasible because other components depended on the affected files having a standard, well-known name, and option 4 was discarded because it contradicted Windows Installer documentation and led to ICE validation errors for the resultant MSI.

Comments (11)

  1. Laughing John says:

    Nice article Aaron. I came across this very issue a couple of weeks back. In my case I'd forgotten to update the version and the files in question were the same version at those already installed.

    I never did get completely to the bottom of it, but I ended up just updating the version numbers too. I did come across REINSTALLMODE which sounded like it would have an affect but I didn't want to go against the norm.

    Links:

    msdn.microsoft.com/…/aa371182%28v=vs.85%29.aspx

    msdn.microsoft.com/…/aa367835%28v=vs.85%29.aspx

  2. Rob D. says:

    I have also run across this issue as well.  For your workarounds 1-3, they impose a requirement on development teams that they may feel as unwarranted. Playing in the Microsoft sandbox is not enough for some people to just accept the rules as cannon.  I work with some people who don't agree that just because a version number of a file is lower than a previous release an installer should not install the "new" file.  They instead believe the installer version is the only thing that matters.  I happen to disagree with this view but this is the world I live in.

    There is an added complication between .Net assemblies and their version numbers.  Typically, a component's version is changed only when a code change is made.  However for .Net assemblies, the binding information can change without any code or version changes.  In this case, the same file version is being installed but marked as "disallowing".  The result of which is that there is no .Net assembly in the GAC (if installed there) or in a folder.

    These issues all stem from an attempt to be very efficient during file transfers.  I don't care about efficiency down to the last few KB that would otherwise get skipped.  My installers are many orders of magnitude smaller than Office or MSSQL.

    For these reasons, I always put the RemoveExistingProducts action before File Costing steps.  This does the following:

    1. An installer will completely uninstall a previous version before installing the new version.  This breaks rollback if there are errors later but this is acceptable outcome given that missing files almost always happen where as installation errors rarely do.

    2. There is no dependency on version numbers since there are no files present when costing runs.

    3. There is no confusion around .Net assemblies and all assemblies get installed into the GAC with the correct compilation date. (and current binding info)

    4. The entire stripe of files of a product get installed together which helps with knowing what will be on an end user's system regardless of the previous version installed.

    Regards,

    Rob

  3. Hi Laughing John – You're right, another option to work around the issue described in this post would be to include the "a" value in the REINSTALLMODE property.  I didn't include it in the list of options in the blog post because that value can be pretty dangerous depending on your scenario.  I've always worked on products in the past that support side-by-side installs and that include some shared components.  Using the "a" value will cause an out of order install (installing version 2 and then installing version 1 afterwards) to downgrade any shared components.  For example, if we had done that for the .NET Framework back when I worked on that installer, the "a" value would cause c:windowssystem32mscoree.dll to be downgraded if you installed the .NET Framework 4 and then installed the .NET Framework 2.0 afterwards, and that would've in turn caused the .NET Framework to not work on the PC.

    Using the "a" value can be safe in controlled conditions though – specifically if the product in question doesn't support side-by-side installs, if out of order installs are blocked at a setup level, or if the product doesn't have any shared components.

  4. Hi Rob D. – Thanks for posting about your experiences with this scenario.  It does technically work to schedule the RemoveExistingProducts action before the FileCost action, but the product I worked on decided not to do that because it triggers an ICE validation failure.  We also had the benefit of being able to re-build the impacted binaries with updated version numbers to eliminate the issue, and I recognize that this isn't an option for every product/scenario too.

  5. Christopher Painter says:

    As someone who's worked at a dozen companies while using MSI for the last dozen years I'd say this is a good article to understand the rock and the hard place we setup developer are often put.  As Rob D said, these things are canon to the Windows Installer.  To developers?  YMMV but usually not.  It's good to understand what works and what doesn't, anticipate developers doing bad things and know when/where you can influence and when you cannot.  For example, as a consultant, I make sure I ask the customer for several builds of their binaries so I can judge their behaviors.  Nothing is worse then creating a perfect MSI based on one snapshot of their binaries just for them to come back 6 months later and say your installers upgrades are broken.

  6. matt@configurationnation.com says:

    Could you not just version lie in the MSI? Of course this file will always be repaired in a repair scenario and may therefore require source, but maybe you need that at repair time anyway?

  7. Hi  matt@configurationnation.com – Yes, I think that would be technically possible.  However, it can lead to Windows Installer resiliency repairs and other possible issues, so I wouldn't recommend it as a workaround for the issue described in this blog post.

  8. Laughing John says:

    Aaron,

    Thanks for replying. The REINSTALLMODE option that interested me was "e" which means "Reinstall if the file is missing, or is an equal or older version.". In my particular scenario I had forgotten to update the file version so the new file version was the same as the existing. Not replacing seemed a bit odd and the side effect of it being completely deleted even more so. I just wasn't sure if there would be any unintended side effects with the "e" option, can you think of any?

    I'm surprised windows installer doesn't spot the RemoveExistingProducts action and take it into account before deciding the course of action. If doing an upgrade with RemoveExistingProducts it seems to me that the file should only ever be deleted if it doesn't appear in the new version.. I expect there are reasons for this behaviour, but I'm not sure what they are other than technical? 🙂

    LJ

  9. Hi Laughing John – The "e" flag is typically included in the REINSTALLMODE property by default when doing a repair (in case an existing file got corrupted on the user's PC for example), and there shouldn't be any unintended side effects of doing so.

    I was surprised that Windows Installer didn't take the presence of the RemoveExistingProducts action into account in this scenario too.  I'm sure there must have been a good reason for it at the time, but it led to a counter-intuitive outcome in this type of file downgrade scenario.

  10. Kiran Hegde says:

    Hello,

    Thanks for the excellent article and the informative comments.

    Of all the options listed here, if i have to choose between sequencing REP before Costing and REINSTALLMODE=emus, what would be better?

    Also please state the reason as to why you think one is better than the other.

    Regards,

    Kiran Hegde

  11. Hi Kiran Hegde – I assume you mean "amus" as opposed to "emus", correct?  If those are your only 2 choices, then I would probably choose using REINSTALLMODE=amus but only if you are positive that you won't be shipping side-by-side versions of your product at some point in the future.  The "amus" switch presents problems with side-by-side installs when installing v2 before installing v1 – any files shared by the 2 versions will be downgraded in that scenario.

    If you need to support that type of side-by-side scenario, then you'll probably need to go with the sequencing change.

    I personally don't like to introduce ICE validation issues into an MSI, so I would avoid that type of sequencing change just in case, but I've heard from several other folks who have used this option successfully, so it is up to you.

Skip to main content