Robert Hegner
Robert Hegner

Reputation: 9396

File not copied (only) during upgrade

Overview

For the new version v1.9.0 of my product I created a new MSI installer. The previous version of the application is v1.7.0.

Uninstalling the old version and then installing the new version works fine.

But when I try to update the old version with the v1.9.0 installer, there is exactly one file (NLog.dll) missing. All other files are copied just fine.

I'm using heat.exe to create components for all the dependencies of my application. So NLog.dll is treated exactly in the same way as every other file, but it is the only file which shows this strange behavior.

Log snippets for NLog.dll from the upgrade to v1.9.0

These are the relevant log snippets for this file.

Line 5174:

MSI (s) (E4:1C) [17:12:42:343]: WIN64DUALFOLDERS: Substitution in 'C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.dll' folder had been blocked by the 1 mask argument (the folder pair's iSwapAttrib member = 0).

Line 37733:

MSI (s) (E4:1C) [17:13:12:252]: Executing op: FileRemove(,FileName=NLog.dll,,ComponentId={53AAD98D-AFDB-4D70-ADCC-5305C3174ED5})
RemoveFiles: File: NLog.dll, Directory: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\
MSI (s) (E4:1C) [17:13:12:260]: Verifying accessibility of file: NLog.dll
MSI (s) (E4:1C) [17:13:12:264]: Note: 1: 2318 2:  
MSI (s) (E4:1C) [17:13:12:267]: Note: 1: 2318 2: 

Line 50386:

MSI (s) (E4:DC) [17:13:22:442]: Executing op: ComponentRegister(ComponentId={D4B31A07-4F5F-4DAA-8280-9A782110477A},KeyPath=C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.dll,State=3,,Disk=1,SharedDllRefCount=2,BinaryType=0)
1: {C2F509F4-A1F9-4377-89FE-59B4DB664FB7} 2: {D4B31A07-4F5F-4DAA-8280-9A782110477A} 3: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.dll 
MSI (s) (E4:DC) [17:13:22:442]: WIN64DUALFOLDERS: Substitution in 'C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.dll' folder had been blocked by the 1 mask argument (the folder pair's iSwapAttrib member = 0).

Log snippets for some other file from the upgrade to v1.9.0

For comparison, here are the log snippets for some random other file (NLog.Extensions.Logging.dll) which updates as expected.

Line 5173:

MSI (s) (E4:1C) [17:12:42:342]: WIN64DUALFOLDERS: Substitution in 'C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll' folder had been blocked by the 1 mask argument (the folder pair's iSwapAttrib member = 0).

Line 37738:

MSI (s) (E4:1C) [17:13:12:267]: Executing op: FileRemove(,FileName=NLog.Extensions.Logging.dll,,ComponentId={2CE3E451-CBCA-4D6A-953A-0EEC1F23FE33})
RemoveFiles: File: NLog.Extensions.Logging.dll, Directory: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\
MSI (s) (E4:1C) [17:13:12:269]: Verifying accessibility of file: NLog.Extensions.Logging.dll
MSI (s) (E4:1C) [17:13:12:272]: Note: 1: 2318 2:  
MSI (s) (E4:1C) [17:13:12:276]: Note: 1: 2318 2:  

Line 50389:

MSI (s) (E4:DC) [17:13:22:442]: Executing op: ComponentRegister(ComponentId={0DFB8E8D-FD31-430F-A84B-C21D7BCB296B},KeyPath=C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll,State=3,,Disk=1,SharedDllRefCount=2,BinaryType=0)
1: {C2F509F4-A1F9-4377-89FE-59B4DB664FB7} 2: {0DFB8E8D-FD31-430F-A84B-C21D7BCB296B} 3: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll 
MSI (s) (E4:DC) [17:13:22:442]: WIN64DUALFOLDERS: Substitution in 'C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll' folder had been blocked by the 1 mask argument (the folder pair's iSwapAttrib member = 0).

Line 51640:

MSI (s) (E4:DC) [17:13:23:148]: Executing op: FileCopy(SourceName=c7dqtb1j.dll|NLog.Extensions.Logging.dll,SourceCabKey=filC5C20BE40C004EEC9809A0196347239A,DestName=NLog.Extensions.Logging.dll,Attributes=512,FileSize=24064,PerTick=65536,,VerifyMedia=1,,,,,CheckCRC=0,Version=1.3.0.804,Language=0,InstallMode=58982400,,,,,,,)
MSI (s) (E4:DC) [17:13:23:149]: File: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll;    To be installed;    Won't patch;    No existing file
MSI (s) (E4:DC) [17:13:23:149]: Source for file 'filC5C20BE40C004EEC9809A0196347239A' is compressed
InstallFiles: File: NLog.Extensions.Logging.dll,  Directory: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\,  Size: 24064

Analysis of the log snippets

As you can see, the first three snippets look pretty much the same for NLog.dll and other files. But the last part, where the file is actually being copied, is just missing in the case of NLog.dll.

There is nothing in the log which would explain (to me) why NLog.dll is not being copied.

Log snippets of the original v1.7.0 install

I don't know if this is relevant in any way, but here are the related log snippets for these two files from the original install. Maybe these matter because of the Ids they had during the original install.

Line 5316 (NLog.dll):

MSI (s) (E4:94) [17:11:14:734]: Executing op: ComponentRegister(ComponentId={53AAD98D-AFDB-4D70-ADCC-5305C3174ED5},KeyPath=C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.dll,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
1: {D6410853-B366-4D05-A1A3-93FC3EFF982A} 2: {53AAD98D-AFDB-4D70-ADCC-5305C3174ED5} 3: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.dll 
MSI (s) (E4:94) [17:11:14:734]: WIN64DUALFOLDERS: Substitution in 'C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.dll' folder had been blocked by the 1 mask argument (the folder pair's iSwapAttrib member = 0).

Line 5319 (NLog.Extensions.Logging.dll):

MSI (s) (E4:94) [17:11:14:734]: Executing op: ComponentRegister(ComponentId={2CE3E451-CBCA-4D6A-953A-0EEC1F23FE33},KeyPath=C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
1: {D6410853-B366-4D05-A1A3-93FC3EFF982A} 2: {2CE3E451-CBCA-4D6A-953A-0EEC1F23FE33} 3: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll 
MSI (s) (E4:94) [17:11:14:735]: WIN64DUALFOLDERS: Substitution in 'C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll' folder had been blocked by the 1 mask argument (the folder pair's iSwapAttrib member = 0).

Line 19183 (NLog.dll):

MSI (s) (E4:94) [17:11:17:881]: Executing op: FileCopy(SourceName=NLog.dll,SourceCabKey=fil3052FED1115C64C0B25CEB4ED20F217C,DestName=NLog.dll,Attributes=512,FileSize=422400,PerTick=65536,,VerifyMedia=1,,,,,CheckCRC=0,Version=5.0.0.0,Language=0,InstallMode=58982400,,,,,,,)
MSI (s) (E4:94) [17:11:17:882]: File: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.dll;   To be installed;    Won't patch;    No existing file
MSI (s) (E4:94) [17:11:17:882]: Source for file 'fil3052FED1115C64C0B25CEB4ED20F217C' is compressed
InstallFiles: File: NLog.dll,  Directory: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\,  Size: 422400

Line 31615 (NLog.Extensions.Logging.dll):

MSI (s) (E4:94) [17:11:22:509]: Executing op: FileCopy(SourceName=c7dqtb1j.dll|NLog.Extensions.Logging.dll,SourceCabKey=filC5C20BE40C004EEC9809A0196347239A,DestName=NLog.Extensions.Logging.dll,Attributes=512,FileSize=8704,PerTick=65536,,VerifyMedia=1,,,,,CheckCRC=0,Version=1.0.0.0,Language=0,InstallMode=58982400,,,,,,,)
MSI (s) (E4:94) [17:11:22:509]: File: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\NLog.Extensions.Logging.dll;    To be installed;    Won't patch;    No existing file
MSI (s) (E4:94) [17:11:22:509]: Source for file 'filC5C20BE40C004EEC9809A0196347239A' is compressed
InstallFiles: File: NLog.Extensions.Logging.dll,  Directory: C:\Program Files (x86)\Cisco\DiagnosticBridge\bin\,  Size: 8704

Some relevant configuration snippets

For gathering the dependencies, I run heat.exe like this:

"%WIX%\bin\heat" dir "$(ProjectDir)\obj\PublishOutput" -dr BinFolder -ke -srd -sreg -cg MyComponentGroup -var var.TempPublishDir -gg -out "$(ProjectDir)\obj\MyContent.wxs"

The resulting components then look like this:

<Component Id="cmpE15B2B75697ADA78CA21A063FF464A7F" Directory="BinFolder" Guid="{876C7C40-4FD9-464E-9282-5CE83B56C4C9}">
    <File Id="fil3052FED1115C64C0B25CEB4ED20F217C" KeyPath="yes" Source="$(var.TempPublishDir)\NLog.dll" />
</Component>
<Component Id="cmpF5B8522DF5AB91BD2DBBA73CBCD944B8" Directory="BinFolder" Guid="{EFB396C4-4F10-4C1B-92FB-75D5C616A708}">
    <File Id="filC5C20BE40C004EEC9809A0196347239A" KeyPath="yes" Source="$(var.TempPublishDir)\NLog.Extensions.Logging.dll" />
</Component>

And the relevant part of the product definition:

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    <Product Id="*" Name="xxx" Language="1033" Version="1.9.0.0" Manufacturer="xxx" UpgradeCode="ab9f8a5a-aa60-4327-9299-3f928136a6e4">
        <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
        <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
    </Product>
</Wix>

Things I've considered

All of this would not really explain why the upgrade works fine for all other files, but not for one. Also I have deployed several previous versions of my product with a similar installer, and I never had this problem before.

Upvotes: 7

Views: 1861

Answers (3)

memory of a dream
memory of a dream

Reputation: 1267

You're trying to downgrade NLog.Extensions.Logging.dll This is not allowed by the default configuration of MSI, however you can overwrite that by explicitly specifying the REINSTALLMODE of your istaller.

This can be achieved by overwriting the respective property in the product section of your main wix file to allow for downgrades

<Product Id="*"
       Name="MY SOFTWARE $(var.Version) $(var.Platform)"
       Language="1033"
       Version="$(var.Version)"
       Manufacturer="The Umbrela Corporation"
       UpgradeCode="someguid-guid-guid-guid-someguid">
...   
<Property Id="REINSTALLMODE" Value="dmus"/>
...

Upvotes: 3

Robert Hegner
Robert Hegner

Reputation: 9396

The crucial hint for solving this problem came from Bob Arnson:

You're trying to downgrade the version of NLog.dll. There's a bug in MSI that causes the file to be removed and not reinstalled.

And indeed, the newest version of my software references an older version of NLog.dll than the previous version of my software.

NLog.dll is actually an indirect dependency of my software. I'm referencing NLog.Web.AspNetCore.dll which in turn references NLog.Extensions.Logging.dll and NLog.dll, so I did not even realize that there was a version downgrade of NLog.dll.

Forcing customers to uninstall/reinstall was not an option. But I found a solution here.

I created the following XSL transform which removes the three individual NLog components from the heat output, and instead inserts a new component which includes all three files, and where KeyPath is set on the file which I reference directly:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:wix="http://schemas.microsoft.com/wix/2006/wi" xmlns="http://schemas.microsoft.com/wix/2006/wi" exclude-result-prefixes="xsl wix">

  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
  <xsl:strip-space elements="*"/>

  <!-- Identity transform -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <!-- Remove individual components for NLog files and add the files in a single component, see CT-1886 -->
  <!-- https://stackoverflow.com/questions/44765707/how-to-exclude-files-in-wix-toolset -->
  <xsl:key name="NLogFilesToRemove" match="wix:Component[contains(wix:File/@Source, 'NLog.dll') or contains(wix:File/@Source, 'NLog.Extensions.Logging.dll') or contains(wix:File/@Source, 'NLog.Web.AspNetCore.dll')]" use="@Id" />
  <xsl:template match="*[self::wix:Component or self::wix:ComponentRef][key('NLogFilesToRemove', @Id)]" />
  <xsl:template match="wix:ComponentGroup">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
      <Component Id="cmpBundledNLog" Directory="BinFolder" Guid="0d590e51-18bd-455a-9edf-c4ff34cce42e">
        <File Id="fileNLogDll" KeyPath="no" Source="$(var.TempPublishDir)\NLog.dll" />
        <File Id="fileNLogExtensionsLoggingDll" KeyPath="no" Source="$(var.TempPublishDir)\NLog.Extensions.Logging.dll" />
        <File Id="fileNLogWebAspNetCoreDll" KeyPath="yes" Source="$(var.TempPublishDir)\NLog.Web.AspNetCore.dll" />
      </Component>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

I'm not sure about the complications this will create in the future (at some point I'd like to get rid of this workaround), but for now it seems to work fine.

Upvotes: 1

Bob Arnson
Bob Arnson

Reputation: 21896

You're trying to downgrade the version of NLog.dll. There's a bug in MSI that causes the file to be removed and not reinstalled. The only workaround is to uninstall completely and then reinstall.

Upvotes: 6

Related Questions