33

We use wix to create our setups. For upgrading, we use major upgrades as demonstrated in this answer by Rob Mensching. (In newer wix versions you can use the MajorUpgrade element.) This normally works well. The old product is removed, then the new product is installed.

However, apparently the above is not completely equivalent to manually uninstalling the old product and then manually installing the new product.

Consider for example the following scenario:

  • version 1.0 of our product is released, containing version 5.0 of a thirdparty dll
  • version 1.1 of our product is released, containing version 5.1 of the same thirdparty dll
  • version 1.2 of our product is released, downgrading to version 5.0 of the thirdparty dll again because we discovered that the new version introduced more problems than it solved.

Apparently with the wix upgrade logic linked above, the 3rdparty dll will disappear when upgrading from release 1.1 to 1.2. A repair is necessary to restore it.

Is there another way to upgrade, which would work for this scenario? I guess what I am looking for is upgrade logic which allows the downgrading of components, by behaving exactly as if one manually uninstalls the old product and then manually installs the new product.

Community
  • 1
  • 1
Wim Coenen
  • 66,094
  • 13
  • 157
  • 251

6 Answers6

9

We also encountered this problem where lower-versioned DLLs were not getting reinstalled on a major upgrade. I thought it was strange that the installer would decide which files to install based on the versioning of existing files, then completely uninstall everything, but still only install what what files had been determined to install before uninstalling the old product. This seems like it might be a bug in Windows Installer...

To fix this problem we moved the RemoveExistingProducts action above the CostFinalize action.

I know the documentation on MSDN recommends placing the RemoveExistingProducts afterInstallValidate, and I'm not sure if putting it before the InstallValidate action has any negative side effects for minor upgrades, but we have decided to only perform major upgrades for our products so this solution appears to work for us.

ksun
  • 1,369
  • 2
  • 13
  • 21
  • 1
    How did you get away with that? If I try to do this on our installer, I get this error: `ICE27: 'RemoveExistingProducts' Action in InstallExecuteSequence table in wrong place. Current: Costing, Correct: Execution` – jalf Mar 31 '14 at 11:44
  • We are using InstallShield to build our installs, and it has allowed us to put the actions in this order. Perhaps whatever application you're using to build your installers does some kind of verification check for a certain order and doesn't allow this sequence. If so, there may be some way of disabling this check, but I don't know if it would be recommended. – ksun Mar 31 '14 at 16:37
  • A quick Google search let me know that the ICE stands for Internal Consistency Evaluator, and this must be running during your installer build. To get around it, you could build the installer with the normal sequence and then open the MSI with a tool like Instedit and manually change the sequence values in the InstallExecuteSequence table. – ksun Mar 31 '14 at 16:53
  • 1
    @jalf actually, looking at the sequence. We moved the `RemoveExistingProducts` before `CostInitialize` as well. It may not be valid to insert it between the costing actions. – ksun Mar 31 '14 at 17:14
  • 1
    @jalf Okay, this MSDN article explains valid placement of the RemoveExistingProducts action. http://msdn.microsoft.com/en-us/library/aa371197(v=vs.85).aspx. -- One of the options was 'Between the InstallValidate action and the InstallInitialize action. In this case, the installer removes the old applications entirely before installing the new applications. This is an inefficient placement for the action because all reused files have to be recopied.' – ksun Mar 31 '14 at 17:21
  • @ksun Wouldn't this solution cause feature states to not be migrated on a silent installation with no UI? Moving REP action to before costing means it also moves before MigrateFeatureStates. If a UI is used to install, there's no consequence because MigrateFeatureStates has already run and will be skipped. However, if the InstallUISequence is not run, I'd think that MigrateFeatureStates would actually run in InstallExecuteSequence (vs being skipped), and then not find any feature state to migrate from the old version that was already uninstalled. – James Johnston Jun 30 '16 at 18:26
4

Behaviors like this generally have to do with the sequencing of RemoveExistingProducts. If it occurs late enough, Windows Installer will have figured out that there's a newer version of the .dll on the machine, so version 1.2 doesn't need to install it. However when the RemoveExistingProducts results in removing the .dll, nothing puts it back.

Things to try including changing the sequencing of RemoveExistingProducts, and lying about the version of the .dll in your 1.2 package (report a version number higher than the bad one). The downside of the latter is poor impacts on repairs or patching, as the .dll always looks out of date.

Michael Urman
  • 15,737
  • 2
  • 28
  • 44
  • Scheduling `RemoveExistingProducts` after `InstallFinalize` has another undesired effect: the dll is kept at the newer version. I also considered overriding the file version in the setup, but this has the unfortunate consequence that I will have to manually update that fake version number each time the DLL changes :-( – Wim Coenen Nov 20 '10 at 17:03
  • Why would you have to keep updating it? Wasn't there just a specific version which doesn't work, and you're overriding that so the one previous to it will reinstall? Perhaps lie that it is exactly the newer version and set REINSTALLMODE=emus. – Michael Urman Nov 22 '10 at 14:19
  • The problem already occurs during **costing.** At this point, MSI sees that a newer version is present, and decides not to install the new file. You'll see `Disallowing installation of component: XXX since the same component with higher versioned keyfile exists` in the log. So you'd have to schedule RemoveExistingProducts **before** costing. I don't see how that could be done without violating ICE27 and ICE63. – Andreas Sep 17 '14 at 14:19
  • For what it's worth, some ICEs seem to be overzealous. That said, any scenario wherein you need to downgrade a file according to its version, at least using Windows Installer, is going have repercussions. – Michael Urman Sep 18 '14 at 12:03
  • For a nice writeup on this, see Aaron Stebner's recent blog post: [Why Windows Installer removes files during a major upgrade if they go backwards in version numbers](http://blogs.msdn.com/b/astebner/archive/2015/11/16/10654678.aspx) – Michael Urman Nov 23 '15 at 18:53
  • @MichaelUrman the post is gone – john k Oct 11 '21 at 14:31
  • @johnktejik [Found it](https://learn.microsoft.com/en-us/archive/blogs/astebner/why-windows-installer-removes-files-during-a-major-upgrade-if-they-go-backwards-in-version-numbers) by searching for "Aaron Stebner blog" and following the 2015-11-16 date from the original URL. – Michael Urman Nov 30 '21 at 13:20
1

Try to schedule RemoveExistingProducts earlier, right after InstallValidate, and change the value of REINSTALLMODE property to amus. This way the old product will be completely removed before any files from the new product are copied, and a mode will force re-install of the files.

Alexey Ivanov
  • 11,541
  • 4
  • 39
  • 68
  • 5
    This answer is not helpful. The suggested placement of `RemoveExistingProducts` after `InstallValidate` doesn't help. Setting `REINSTALLMODE` to `amus` is very dangerous because `a` force re-installs ALL files, including shared system files that you might *want to not* downgrade. For example, imagine you have some merge modules that install system runtime DLLs. These DLLs are later patched with a security patch by the vendor. When your package is later installed with `amus`, you'll overwrite the newer, safer DLLs from the vendor. – James Johnston Jul 10 '12 at 21:22
  • @JamesJohnston so what would you do instead? Assuming you *don't* have merge modules installing system-wide DLL's, `amus` really seems like the only feasible solution I've been able to find. (But I'd love to hear of a better solution) – jalf Mar 31 '14 at 11:54
  • 2
    @jalf: after an extensive search, I came to the conclusion that `REINSTALLMODE=amus` is the only sensible solution. However, it can only be used if there are no shared components in your instal. – Andreas Sep 17 '14 at 14:05
  • 1
    amus will ruin your life if you have shared components between products. – Robin Jan 05 '16 at 10:42
0

It's sub-optimal, but I fixed the same problem by renaming the third party dll and changing the GUID on the component node associated with it in the .wxs file.

Jackson Pope
  • 14,520
  • 6
  • 56
  • 80
0

Years later, this thread helped me in the right direction. An example for completeness with RemoveExisitingProducts moved before costing:

<Upgrade Id="UPGRADE-GUID-HERE">
    <UpgradeVersion OnlyDetect="no" Property="UPGRADABLEFOUND"
        Maximum="$(var.ProductVersion)" IncludeMaximum="yes" />
    <UpgradeVersion OnlyDetect="yes" Property="NEWERFOUND"
        Minimum="$(var.ProductVersion)" IncludeMinimum="no" />
</Upgrade>

<InstallExecuteSequence>
    <Custom Action="NoDowngrade" After="FindRelatedProducts">NEWERFOUND</Custom>
    <RemoveExistingProducts Before="CostInitialize" />
</InstallExecuteSequence>

<CustomAction Id="NoDowngrade" Error="A newer version of $(var.ProductName) is already installed." />
double-beep
  • 5,031
  • 17
  • 33
  • 41
Michel Jansson
  • 1,803
  • 1
  • 13
  • 14
0

Here's my final solution based on the answer given by @Spacemani.

It produces MSI table entries (Upgrade, LaunchCondition etc.) similar to this

<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" />

but gives you full control of the InstallExecuteSequence.

<!-- Product upgrade -->
<Upgrade Id="$(var.UpgradeCode)">
  <UpgradeVersion OnlyDetect="no" Property="WIX_UPGRADE_DETECTED"
                  Maximum="$(var.ProductVersion)" IncludeMaximum="no" IncludeMinimum="no"
                  MigrateFeatures="yes" />
  <UpgradeVersion OnlyDetect="yes" Property="WIX_DOWNGRADE_DETECTED"
                  Minimum="$(var.ProductVersion)" IncludeMinimum="no" />
</Upgrade>
<InstallExecuteSequence>
  <RemoveExistingProducts Before="CostInitialize" />
</InstallExecuteSequence>
<Condition Message="!(loc.DowngradeErrorMessage)">NOT WIX_DOWNGRADE_DETECTED</Condition>

Note that you need to suppress ICE27 errors in your .wixproj file like this.

<PropertyGroup>
  <SuppressIces>ICE27</SuppressIces>
</PropertyGroup>
candritzky
  • 351
  • 4
  • 11