2

I have written and am maintaining a number of Wix installation source files which I use to build MSI files for distribution of my application.

I have not explicitly programmed for any kind of upgrading, updating, reinstallation or anything of the kind -- there is a single feature that consists of a number of components with stable GUIDs and I have observed that at least a clean installation does what I expect it to.

However, I (and anyone in possession of the MSI files I distribute) may seemingly install distinct versions of my application side-by-side using their respective (distinct) MSI files. Which isn't a problem in itself, except that I obviously use the same folder as installation target -- "%ProgramFiles(x86)%\Foobar" -- to install the application (version regardless) in. Meaning that in effect there is always ever one version that ends up being installed.

I would argue that Windows Installer behaves correctly in so far that it updates files from whichever MSI package it installed last. One interesting side-effect of this is that if the last MSI was of earlier version, the files in the application folders would be overwritten with the copies from that earlier version.

But none of that seems to be the actual problem to me. I want to fix the disparity between what is actually installed (a single application version) and what Windows tracks as installed -- in my case two records of two distinct application versions.

Since I install the application in a folder that doesn't depend on the version being installed, tracking multiple application versions by Windows is a mistake.

So I guess my question is, how do I fix this so that only one version is shown (reflecting reality) or what's the idiomatic approach in these kind of cases? I deliberately did not overspecify my Wix source code, hoping in return that Windows Installer would use some built-in intelligence to figure everything out on its own. But I may need to add some explicit upgrade or uninstall-previous-version-first instructions, I suppose.

My minified Wix source code (file "foobar.wxs") would look like this:

<?xml version="1.0" encoding="utf-8" ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
    <Product Name="Foobar" Manufacturer="ACME Inc." Id="*" UpgradeCode="ae9a7d6d-6c2d-446a-97d9-9dbe829d2ea8" Language="1033" Codepage="1252" Version="!(wix.PRODUCT_VERSION)">
        <Package Id="*" Languages="1033" SummaryCodepage="1252" Compressed="yes" InstallerVersion="200" />
        <Icon Id="foobar" SourceFile="!(wix.APPPATH)/foobar.ico" />
        <Property Id="ARPPRODUCTICON" Value="foobar" />
        <Property Id="ARPCOMMENTS" Value="Gives you full foobar powers" />
        <MediaTemplate EmbedCab="yes" CompressionLevel="high" />
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="DesktopFolder" />
            <Directory Id="ProgramFilesFolder">
                <Directory Id="INSTALLDIR" Name="Foobar" FileSource="!(wix.APPPATH)">
                    <Component>
                        <File Id="foobar.exe" Name="foobar.exe" />
                    </Component>
                    <!-- There are other components like above (assets) -->
                </Directory>
            </Directory>
            <Directory Id="ProgramMenuFolder">
                <Directory Id="foobar_menu" Name="Foobar">
                    <Component Id="foobar_shortcut" Guid="e80a6b95-a145-453a-b327-65a977e741fe">
                        <Shortcut Icon="foobar" Id="foobar_shortcut" Name="Foobar" Target="[foobar]foobar.exe" />
                        <Shortcut Directory="DesktopFolder" Icon="foobar" Id="foobar_desktop_shortcut" Name="Foobar" Target="[foobar]foobar.exe" />
                        <RegistryValue KeyPath="yes" Root="HKMU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" /> 
                        <RemoveFolder Id="remove_foobar_menu" On="uninstall" />
                    </Component>
                </Directory>
            </Directory>
            <Directory Id="CommonAppDataFolder">
                <Directory Id="app_data_foobar" Name="foobar">
                    <Component Guid="" Id="app_data_config_folder">
                        <CreateFolder />
                    </Component>
                    <Component Guid="" Id="app_data_config_folder_log_file">
                        <File Name="foobar.log" Source="foobar.log.template">
                            <!-- Add write access permission to the log file to members of "Users" group. -->
                            <!-- PermissionEx Sddl="D:AR(A;;GWGR;;;BU)" / -->
                            <!-- Bug with Windows Installer, can't use PermissionEx/MsiLockPermissionsEx table. See https://stackoverflow.com/questions/55145282/how-to-include-inherited-permissions-when-specifying-permissions-for-a-file-inst -->
                            <util:PermissionEx Append="yes" GenericWrite="yes" User="Users" />
                        </File>
                    </Component>
                </Directory>
            </Directory>
        </Directory>
        <Feature Id="foobar">
            <ComponentGroupRef Id="foobar" />
            <ComponentRef Id="foobar_shortcut" />
            <ComponentRef Id="app_data_config_folder" />
            <ComponentRef Id="app_data_config_folder_log_file" />
        </Feature>
    </Product>
</Wix>

I am compiling the object file with the following Windows Command Prompt line:

candle.exe -ext WixUtilExtension -out %TEMP% foobar.wxs

And then generating the MSI file with:

light.exe -ext WixUtilExtension -spdb "-dAPPPATH=%apppath%" "-dPRODUCT_VERSION=%version%" -out %TEMP%\foobar-%version%.msi %TEMP%\foobar.wixobj

(using Wix 3.11.1.2318)

Armen Michaeli
  • 8,625
  • 8
  • 58
  • 95

1 Answers1

2

Upgrade Code: As long as you have set an upgrade code (which identifies a bunch of related products) you can use a major upgrade element to indicate products that are to be uninstalled as part of a new MSI's installation.

MajorUpgrade Element: Just inject a MajorUpgrade element for default treatment of major upgrades into your existing WiX source. It is a sort of "magic element" doing a lot for you making a number of (usually good) assumptions. There are older and more flexible ways to do it - if you need more detailed control (for legacy purposes usually - auto-magic does not cover all bases):

<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />

The above is the standard use for all WiX files created in Visual Studio.

Note: I will try to tune up this answer shortly with more links, but give that a go first?

First link: Using Visual Studio to make WiX files. The Hello WiX and Visual Studio-type of scenario.


Major Upgrade Suggested Reading: A few things to know about major upgrades. All WiX markup essentially revolves around the compiled MSI's Upgrade table. It is there that major upgrade logic is configured. Custom actions could also affect things, and a few other things such as launch conditions perhaps.

Further:

Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
  • Thank you, Stein. I followed your advice and added the `MajorUpgrade` element with my chosen text for the `DowngradeErrorMessage` attribute. I can see now that whenever I install an MSI packaging a newer version of the application, there remains only one record of the installed application -- the newer version. That solves my problem entirely. I tried to allow downgrades (I didn't have any requirement on not allowing downgrades) but Wix warned me about allowing these with some half-cryptic suggestion, so I decided not to allow them anyway. – Armen Michaeli Nov 19 '19 at 14:18
  • 1
    Added a few links that might help to understand major upgrades better. If you want to install product at the same time - as separate products or side-by-side as in version 1 and 2 installed at the same time, I would use separate upgrade codes "per product line". That means version 1 and version 2 would have different upgrade codes and hence be upgraded and managed separately. How to do this depends on the scenario and what you need. Do not use new upgrade code if you never want them to use older versions. That would be my take on it. – Stein Åsmul Nov 19 '19 at 16:12
  • 1
    I should add that you can insert multiple upgrade codes in the upgrade table and hence uninstall other product lines as well - even competitive products if you are crazy enough **`:-)`**. Call legal first! [This "uninstall multiple product lines" is actually shown here](https://stackoverflow.com/a/51803320/129130). You would obviously only uninstall your own products. – Stein Åsmul Nov 19 '19 at 16:24