Brian Heilig
Brian Heilig

Reputation: 648

Existing Uninstaller Broken During Upgrade - Patch MSI?

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?

Upvotes: 1

Views: 326

Answers (3)

Brian Heilig
Brian Heilig

Reputation: 648

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.

Upvotes: 0

Stein &#197;smul
Stein &#197;smul

Reputation: 42126

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:

Upvotes: 1

PhilDW
PhilDW

Reputation: 20780

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.

Upvotes: 1

Related Questions