1

I have a couple of MSI files installing different applications. All these packages share the same underlying runtime which basically is a set of DLLs. This runtime is pulled in each of the installers as a merge module. Installing a couple of these packages works just fine, always the latest version of the runtime stays on the system and when the last package is removed everything is removed from the system.

Now I had to split one of the DLLs into 2 and added a new component to the runtime installing the new DLL. This new DLL is linked with other libs of the runtime. Now assume the following scenario:

  • install an old package with the merge module for the runtime without the new DLL
  • install a new different package with a newer version of the merge module for the runtime. Now there are 2 packages on the system
  • remove the new package again

Now the old package is broken because:

  • the new component for the new DLL had a reference count of one as the old package didn't have it and therefore gets removed
  • the other runtime DLLs stay on the system because they are still referenced by the older package. However as they are new they are already linked with the new DLL that is now no longer present

So my question is:

  • Is there either a way to explicitly state in the WiX code that file A depends on file B so that it stays on the system until all references have been uninstalled?
  • or is there a way to explicitly downgrade the dependees in a way the dependency does not longer exists?
  • Am I doing something fundamentally wrong?

What I did try on a clean machine was to follow Stein Åsmul suggestion like this:

<Component Id='OldLibsNowDependingOnNewLib' Guid='C8DCD2AB-CBE5-4853-9B25-9D6FE1F678DD'>
  <File Id='LibOne' Name='LibOne.dll' Source='$(var.SourceDir)/LibOne.dll' />
  <File Id='LibTwo' Name='LibTwo.dll' Source='$(var.SourceDir)/LibTwo.dll' />
</Component>
<Component Id='NewLibComponent' Guid='CD2DB93D-1952-4788-A537-CE5FFDE5F0C8' Shared='yes'>
  <File Id='LibNew' Name='LibNew.dll' Source='$(var.SourceDir)/LibNew.dll' />
</Component>

However unfortunately this doesn't change the behaviour.

StefanB
  • 21
  • 4
  • Reading the below again I see a number of things that I should have improved. Can I ask how you are going with this? I am not 100% sure about how the .NET framework does assembly probing (load behavior). Let me just share a link (that I don't have time to read properly right now): [How the Runtime Locates Assemblies](https://learn.microsoft.com/en-us/dotnet/framework/deployment/how-the-runtime-locates-assemblies). – Stein Åsmul Dec 16 '18 at 23:04

1 Answers1

1

UPDATE: Looking in the SDK again, I see the flag msidbComponentAttributesShared for components. This looks promising for the problem you describe. Please try to enable this flag and recompile the version 2 of your setup (unless it is live).

Enable the Shared flag for the component in question (last part):

<Component Feature="Product" Shared="yes">

This seems to be for patch support, but maybe it will work for your case too. From the MSI SDK:

"If a component is marked with this attribute value in at least one package installed on the system, the installer treats the component as marked in all packages. If a package that shares the marked component is uninstalled, Windows Installer 4.5 can continue to share the highest version of the component on the system, even if that highest version was installed by the package that is being uninstalled."


I think the above should work, but don't have time to test right now. Leaving the below for review.


Short Answer: Use WiX's Burn (setup chainer) to install in sequence the application setup and a new, separate runtime setup that can be handled independently of your application setup versions.


Prerequisite Setup: Interesting case. This is why I like to split prerequisites into its own MSI package and deploy it via a Burn Bundle Bootstrapper. Burn is WiX's bootstrapper / downloader / chainer - essentially a way to run several setups in sequence - in a few different formats such as MSI, EXE, MSU, MSP. When doing this - putting the runtime in its own MSI - there are no entanglements and you get good decoupling of your runtime and application-specific files. In other words: you can update the runtime files on its own - with their own MSI. The files will even have a reference count of 1 meaning you can easily uninstall them all (not if you install via a merge module that also can be included in other packages - more below).

Merge Modules - Semi-Static Linking?: In a weird way merge modules are sort of semi-static linking. The whole merge module is a version - a binary bundle (think COM) - but its installation behavior is one of "higher version wins" only. Hence a single newer MSI with the newest merge module in it will update the shared files for all applications that use them. Uninstalling will then do what you see: preserve the files that were originally installed by older setups.

Options: One "solution" in your case could be to re-compile the older setup with the newer merge module and then reistall, which I understand you don't like. I don't like it either. I guess it is no solution at all. Some other suggestions:

  • Permanent component: You can set the hosting component for the new file to be permanent on the system. This is very easy, but also quite silly and not always very desirable. Uninstall will then not remove the file at all.
  • Prerequisite: This is my favorite option mentioned above. You compile a prerequisite MSI setup that installs the runtime components. This MSI can deliver updates to itself without affecting the main application. This is the primary benefit I am after: Cohesion & Coupling benefits.
    • Merge Modules: I would avoid merge modules altogether, but it is common to merge the same merge module into the prerequisite setup - if you have a merge module already.
      • In most cases merging a merge module is fine since you then install the prerequisite and then you can install and uninstall application versions at will without affecting the runtime since a different product (prerequisite MSI) installed the runtime - and that setup should stay behind and not be uninstalled.
      • If the merge module does not work and brings along the conflict that you already had, maybe try to combine with the msidbComponentAttributesShared "solution" mentioned above. Not tested by me so far. Always risky to suggest things like this, but it is "best effort".
    • WiX Include Files: I prefer to use WiX include files which allows me to pull in new files without re-authoring a whole merge module in binary format (think C++ include files as opposed to a merge module's COM-style binary reuse).
  • Side-By-Side: Many people prefer to install prerequisites side-by-side so that several versions of the runtime can co-exist. This may or may not involve the GAC. Switching runtime versions would then be a manifest-manipulation task. Generally somewhat confusing, but doable. You can use both merge modules and separate MSI files to deploy such runtimes - as described above. I would definitely use a prerequisite MSI.

I can't think of more right now, but I know I have forgotten something important this time. Let me persist what I have for now in case it sparks ideas for you.

Cumbersome Prerequisite Setups: Note that prerequisite MSI files are not so bad for corporate deployment since deployment systems will allow one to define relationships between MSI files and to set up deployment chains. For home users you can easily wrap everything in a large setup.exe.

Nonsense Options: Options that don't make sense would be to roll the new file into both setup versions. No gain, lots of overhead. Some people like to copy new files locally to the main installation folder. Does not work since the files it is linked to are likely elsewhere (runtime location). Static linking wouldn't be relevant in this case I think. Only as a last resort to solve a live problem I guess. Setting the SharedDllRefCounter flag will not affect MSI reference counting, it is for legacy reference counting (non-MSI setups), though tweaking this manually is an emergency "solution". The last resort people end up with is typically to abandon the runtime installation and install everything to the same installation folder. Then you have to always recompile everything for every release - which is what you want to avoid?


Some Links:

Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
  • Setting the Shared flag unfortunately doesn't change anything. From the WiX documentation which I stumbled on as well it was unclear to me what this was supposed to do. The MSDN stuff you did port makes more sense. Thanks for that but I guess I have to go for one of your more complicated solutions now. Anyway: This must be something others have problems with as well. Isn't this something pretty obvious?! – StefanB Dec 13 '18 at 14:18
  • I will have a look at this again when I have a chance. Please do not add your own answer as a "comment", but roll the content into your question as an update / edit. They are strict about that here on stackoverflow. And please remember to check on clean virtuals - I guess you did? – Stein Åsmul Dec 13 '18 at 14:24
  • Sorry, no chance to look at this today I am afraid. I am surprised that it didn't work with `msidbComponentAttributesShared`, but that is also what typically happens if you throw in a tip you don't have time to test... – Stein Åsmul Dec 14 '18 at 06:28