How Windows Installer handles file replacement logic for versioned and unversioned files

I ran into an interesting problem yesterday where a team had built an MSI-based setup package, and they were seeing sporadic problems where some of the files failed to install.  They enabled Windows Installer verbose logging and this is an example of an entry for one of the files that was failing to install:

MSI (s) (B4:4C) [11:30:07:906]: Executing op: FileCopy(SourceName=eulatxt|eula.txt,SourceCabKey=FILE1,DestName=eula.txt,Attributes=512, FileSize=29239,PerTick=32768,,VerifyMedia=1,,,,, CheckCRC=0,,,InstallMode=58982400,HashOptions=0, HashPart1=-1713153497,HashPart2=58557210, HashPart3=1046945815,HashPart4=871163290,,)
MSI (s) (B4:4C) [11:30:07:906]: File: C:\WINDOWS\system32\eula.txt; Won't Overwrite; Won't patch; Existing file is unversioned but modified

After I saw this, I took a look at the MSI package and I found that this MSI was divided into one component per file, and each component used the one file that it contained as the key path.  This led me to understand the root cause of this issue.  Windows Installer has a specific set of rules for file versioning and how it decides when to replace files when they already exist in the destination location.  Here are the possible permutations for file versioning:

  • In the case when the source and destination files are both versioned files, these rules are straightforward - if the key path file does not exist in the destination location or if it exists in the destination location and it is of a lower version, Windows Installer will install the component (and all of the files in it, including the key path).  If the key path file exists in the destination location and it is of an equal or higher version, Windows Installer will not install the component but instead only reference counts the component.  It becomes trickier when only one of the files is versioned or neither of them are.
  • In the case when only one file is versioned, Windows Installer will install the component (and all of the files in it, including the key path file) if the source file is versioned and the destination file is not (meaning it will replace an unversioned file with a versioned one).  It will not install the component but instead only reference counts the component if the source file is unversioned and the destination file is versioned (meaning it will not replace a versioned file with an unversioned one).
  • In the case when both files are unversioned and have file hashes (as in the scenario I was debugging yesterday), Windows Installer will compare the created time and the last modified time of the destination file.  If the times are the same, Windows Installer will compare file hashes of the source and destination files.  If the file hashes are different, Windows Installer will install the component (and all of the files in it, including the key path).  If the file hashes are the same, Windows Installer will not install the component but instead only reference counts the component.  This means that if the key path file already exists in the destination location but has been changed sometime after it has been created, Windows Installer will not update this file, even if it is carrying a different copy of the file as part of the setup package.  The intent of this logic is to prevent Windows Installer from overwriting data files that customers may have modified on their machine before running setup
  • In the case when both files are unversioned and do not have file hashes, Windows Installer will compare the created time and the last modified time of the destination file.  If the times are the same, Windows Installer will install the component (and all of the files in it, including the key path).  If the times are different, Windows Installer will not install the component but instead only reference counts the component.

This particular MSI I was investigating was trying to update a text file named eula.txt in %windir%\system32.  In some test cases, the file did not exist, in some cases it existed and was identical and in some cases it existed and was different.  The 3rd and 4th versioning rules above explains why this problem would only occur on some test machines but not others.  This illustrates one of the problems with using an unversioned file as the keypath for a component, especially if the file could be shared by other products on the system.  The solution I recommended to the team hitting this problem was to move this eula.txt file to be installed as part of another component that installed to %windir%\system32 and had a binary file as a key path.  In general you have to be careful about installing multiple files in one component (because of the Windows Installer component rules), but in this case it is a simple text file, which mitigates some of the drawbacks of including multiple files in a component (complicated servicing and incorrect uninstall behavior being the main risks here).

<update date="9/6/2005"> I listed the logic incorrectly that Windows Installer uses when both the source and destination file are unversioned.  I have updated the 3rd bullet above based on the feedback in Stefan's comment. </update>

<update date="4/29/2008"> MSDN links in this post were broken, so I updated them to work again. </update>

<update date="8/3/2016"> Fixed broken link to Rob Mensching's component rules blog post. </update>