0

I have a package whose previously installed versions contain a series of poorly designed custom actions: they do stuff that can be done out of the box. Thus I'm removing those for the future versions.
A series of bugs is casued by a condition for those custom actions - they all run the second time during an uninstall step during an upgrade. In order to circumvent the undesirable behavior I'd like to supply a Custom Action which modifies the database of a previously installed application if one was detected.

Given an application name, version and custom action name, how can I disable it for an uninstall step during a custom action on upgrade?


My attempt to ammend the Custom Action condition failed when I run my code within a C++ custom action during an upgrade installation: MsiOpenDatabaseW throws access violation when executed within an msi custom action
Basically I am using MsiOpenDatabaseW to modify the cached msi database. It works when running within a standalone executable, but causes access violation within a Custom Action.

Sergey Kolesnik
  • 3,009
  • 1
  • 8
  • 28
  • What is the exact condition that is causing trouble? – Stein Åsmul Jan 11 '22 at 05:16
  • @SteinÅsmul the Condition itself is irrelevant for the question and I can successfully fix it with an MSI database query when running a standalone exe with `MsiOpenDatabaseW` and query commands. The problem is that `MsiOpenDatabaseW` can't be run during a running installation https://stackoverflow.com/questions/70660365/msiopendatabasew-throws-access-violation-when-executed-within-an-msi-custom-acti?noredirect=1&lq=1 – Sergey Kolesnik Jan 11 '22 at 09:41
  • If you use a property of your own - in other words it is not a built-in property from MSI itself - perhaps you can modify its value with a custom action to ensure the condition is false so the problematic custom action does not run? This is only possible in special cases and as always there is a potential for new bugs when you resort to workarounds. – Stein Åsmul Jan 11 '22 at 15:53
  • @SteinÅsmul as I said, I can successfully modify a *cached* installer's Custom Action condition to yield `false` during an uninstall step. The problem is that I receive a segfault when run `MsiOpenDatabaseW` within a Custom Action during a major upgrade installation. I have linked the related question. – Sergey Kolesnik Jan 11 '22 at 16:27
  • I don't have much time to look at this right now, but here is something you can skim: [Problematic uninstall due to custom dialogs showing unexpectedly](https://stackoverflow.com/a/53876981/129130) (or a custom action runs unexpectedly - same problem, different expression). [Here is a quick summary of issues with UPGRADINGPRODUCTCODE and WIX_UPGRADE_DETECTED](https://stackoverflow.com/questions/51075759/run-wix-custom-action-only-during-uninstall-and-not-during-major-upgrade/51090120#51090120). – Stein Åsmul Jan 11 '22 at 17:37
  • 1
    The link about problematic uninstalls due to dialogs or custom actions running unexpectedly shows how you can easily use a minor upgrade patch to fix the installed product's uninstall sequence before it is invoked - the problem is [the delivery method for the patch](https://stackoverflow.com/a/46935947/129130). – Stein Åsmul Jan 11 '22 at 17:41

2 Answers2

1

You need to amend the 'Condition' of the InstallExecuteSequence/InstallUISequence for the Custom Action of the cached (installed) MSI. An example can be found here, which searches for the cached MSI based on the ProductCode and amends the Condition of a Custom Action in the InstallExecuteSequence:

https://www.alkanesolutions.co.uk/2012/12/08/editing-msi-files-in-the-windows-installer-cache/

Captain_Planet
  • 1,228
  • 1
  • 12
  • 28
  • this is exactly what I mean. But hte example in VBA. What C++ API should I use in my amending Custom Action? – Sergey Kolesnik Dec 21 '21 at 20:02
  • and can I use an existing MSI `HANDLE` that is passed as an argument to my custom action to do the searching? – Sergey Kolesnik Dec 21 '21 at 20:03
  • If you're doing it as part of a Major Upgrade for example, you'd need to run your action before RemoveExistingProducts. I don't think there's a way of grabbing a handle on the upgrading MSI during that action. The best you could do it perhaps query the Upgrade table of that particular Session object, iterate through the ProductCode(s) or previous versions, and modify the cached MSI (if they exist). – Captain_Planet Dec 22 '21 at 08:17
  • 1
    You could get an MSIHANDLE to the current session I think (if your Custom Action is Immediate), but not the session of another MSI. https://learn.microsoft.com/en-us/windows/win32/msi/accessing-the-current-installer-session-from-inside-a-custom-action – Captain_Planet Dec 22 '21 at 08:31
  • I think I could `MsiOpenDatabaseA` with the path of a cached `.msi`, which I'd somehow get from the ProductCode <- UpgradeCode – Sergey Kolesnik Dec 22 '21 at 08:35
1

EDIT: Some colleagues wrote a free guide on installation testing. Maybe it will be useful in the future, to avoid such costly mistakes.

A simpler solution I recommend is to manually fix the condition on your machine and rebuild the MSI.

Then take this MSI and include it in the next version of your application, and run a custom action to re-cache it, before the RemoveExistingProduct standard action.

msiexec /fv <path of the fixed msi>

This way, the installer with the wrong condition is re-cached before the upgrade is triggered and you don't need to write code that actually modifies the cached MSI dirrectly

Bogdan Mitrache
  • 10,536
  • 19
  • 34
  • This is not an option. There are clients who have installed various previous versions that can't be simply rebuilt (the project is large with a lot of convolutd legacy code). The simplest solution is the custom amending action – Sergey Kolesnik Dec 22 '21 at 08:28