1

I wrote a custom action to help during upgrade of my product (from 1.0 to 1.1). Now I need to upgrade from 1.1 to 1.2 but the existing uninstaller is failing during upgrade. I got the execution conditions of my custom action wrong. (Lesson learned, always test upgrading to the next version before deploying).

Right now it seems my best option is to modify the InstallExecuteSequence table in the existing .msi to disable the failing custom actions. I'll have to create another custom action to browse the registry, locate the existing .msi in C:\ Windows\Installer, patch it, and then continue with the upgrade. This sounds like a terrible, error prone solution, but I'm really at a loss. This was supposed to be an automatic, silent upgrade pushed down from a remote cloud.

Another option would be to write a batch script to uninstall the existing product, then execute the new installer.

Any advice?

EDIT This question is already answered here: I screwed up, how can I uninstall my program?

Brian Heilig
  • 592
  • 4
  • 16

3 Answers3

1

The supported way to do this is a patch (by which I mean an MSP file, not coding to alter the cached MSI file). That's by far the most straightforward way to get out of the situation. After that, do the upgrade. Using WiX you could probably put the MSP and the upgrade in a bundle.

In any case, you wouldn't do your proposed change with another MSI. A small executable can do what you propose, and:

MsiGetProductInfo (ProductCode, …, INSTALLPROPERTY_LOCALPACKAGE)

is how you find the cached MSI.

PhilDW
  • 20,260
  • 1
  • 18
  • 28
1

Conditioning: What condition did you set on the failing custom action? And more importantly, what is the new condition you are intending to use? It sounds like regular uninstall works but major upgrade fails? The typical problem is that uninstall fails altogether, and then the usual solution is a minor upgrade which I will quickly describe.

Minor Upgrade: Normally what I use is a minor upgrade to fix whatever is wrong in the current install's (un)installation sequence(s). A minor upgrade does not uninstall the existing installation, it upgrades it "in-place", and the uninstall sequence is hence never called and thusly you avoid all its errors from manifesting themselves. There is no need to browse to the cached MSI file and hack it manually if you do things correctly in your minor upgrade. The updating of the cached MSI will happen auto-magically by the Windows Installer Engine provided you install with the correct minor upgrade command line.

Future Upgrades: A minor upgrade will generally always work if you make it simple enough, but the problem is usually applying it since it often targets only a single, previous version. When you get to the next release and if you then use a major upgrade, you will see the error in your original MSI manifest itself on uninstall if you are upgrading an installation that never had the minor upgrade applied - in other words it is still the oldest version of your installation. This is generally solved by a setup.exe launcher which will install the minor upgrade if need be. The bad news is that you need to keep that update in every future release - if you want to avoid any upgrade errors. Or in a corporate environment you would use the distribution system to check what is already on the box and install accordingly. If your manual uninstall works correctly (but major upgrade uninstall fails), all you should need to do is to push an uninstall command line to msiexec.exe as the first command to run via your setup.exe I think. Then there is no need to include any minor upgrade binaries in your setup.exe launcher.

Detect & Abort?: Michael Urman's answer here explains how it might be difficult to make sure that the minor upgrade is present on the box before applying the next version of your software: InstallShield fails because of a bad uninstall. He suggests making your package better at detecting whether a new upgrade can be safely applied.


Some Links:

Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
  • The existing condition is "UPGRADINGPRODUCTCODE OR WIX_UPGRADE_DETECTED". I'm intending to change it to "WIX_UPGRADE_DETECTED". Yes, regular uninstall works. I can remotely push down and execute a batch script. I can apply a minor upgrade patch, and then the major upgrade. I can also do everything in batch: copy old configuration files out, uninstall previous version, install new version. I'll have to modify the installer to execute silently using parameters passed on the command line. – Brian Heilig Jul 30 '18 at 15:02
  • 1
    Oh, that old chestnut. [**See here for similar issue**](https://stackoverflow.com/a/51090120/129130) (link primarily for others who find this). During a major upgrade **`UPGRADINGPRODUCTCODE`** is set in the old setup uninstalling and not in the new setup that is being installed. *So by the looks of it, your changed condition means you only want to run the custom action in the newer, installing setup - not during the old setup's uninstall*. And [an example of how complex conditions are hard to test](https://stackoverflow.com/a/47944773/129130). – Stein Åsmul Jul 30 '18 at 18:07
  • 1
    And [**how to test complex conditions**](https://stackoverflow.com/a/49683886/129130) using custom actions to evaluate conditions at runtime. Towards bottom "***Property Debugger Demo***". The proof is in the pudding as they say - I like to run the setup in various installation modes (`install`, `repair`, `self-repair`, `major upgrade`, `patch`, `uninstall`, `etc...`) with these property evaluator custom actions showing message boxes in order to see what happens at runtime. Remember to take them out of your final setup though :-). – Stein Åsmul Jul 30 '18 at 18:11
0

Here is a hack that I got working, but based on the answers above it looks like it's not the preferred way.

[CustomAction]
public static ActionResult Patch11Installer(Session session)
{
    string localPackage = NativeMethods.GetMsiInstallSource("{MY-PRODUCT-CODE}");
    if (String.IsNullOrEmpty(localPackage))
    {
        session.Log("Failed to locate the local package");
        return ActionResult.Failure;
    }

    session.Log($"Found local package at {localPackage}");

    using (Database database = new Database(localPackage, DatabaseOpenMode.Direct))
    {
        foreach (string action in new string[] { LIST OF CUSTOM ACTION NAMES })
        {
            session.Log($"Modifying condition for action {action}");
            database.Execute($"UPDATE InstallExecuteSequence SET Condition='WIX_UPGRADE_DETECTED' WHERE Action='{action}'");
        }

        database.Commit();
    }

    return ActionResult.Success;
}

The custom action calls MsiGetProductInfo to query for the v1.1 MSI using the v1.1 product code which I obtained from installer log files. It then opens the MSI database and modifies the Condition property of the InstallExecuteSequence table for the list of custom actions that are failing. It changes the Condition from "UPGRADINGPRODUCTCODE OR WIX_UPGRADE_DETECTED" to "WIX_UPGRADE_DETECTED". UPGRADINGPRODUCTCODE is the property that's causing the uninstall to fail during a major upgrade as this property is passed to the uninstaller and contains the new product code; the product code for v1.2 in my case. Here is the custom action definition in my installer file.

<CustomAction Id="Patch11Installer" Return="check" Impersonate="yes" Execute="immediate" BinaryKey="MyUpgradeCustomActions" DllEntry="Patch11Installer" />

I'll look into implementing a minor upgrade as suggested in other answers. I just thought I would leave this solution here.

Brian Heilig
  • 592
  • 4
  • 16
  • 1
    I suppose you could run this from its own executable or script (Scheduled tasks? Logon script?) if you are in a corporate environment - in other words without using a custom action at all. You could also put it in its own "fixer MSI" that you deploy once. How are you scheduling this in your MSI by the way? I hope you are in the **`InstallExecuteSequence`**? (or the action won't run in silent mode). Also: **immediate mode custom actions will not run elevated** - they always run as the launching user (which could happen to be an admin with UAC elevation enabled - making it appear to work). – Stein Åsmul Jul 30 '18 at 18:21
  • 1
    **Continuing on elevation**: however a standard user installing via elevated rights will see the MSI fail because ***"genuine admin rights"*** are needed, not just ***"elevated rights"*** (temporary admin rights granted by group policy / active directory for the duration of the install - and only for actions between **`InstallInitialize`** and **`InstallFinalize`** in the **`InstallExecuteSequence`**). – Stein Åsmul Jul 30 '18 at 18:23