I'm building an InstallShield project, which does a [Major Upgrade][1] to an existing install (i.e. I changed the Product Code
while retaining the Upgrade Code
).
The uninstaller of the previous version (which is already deployed) is broken, and fails to run some scripts. This in itself is not a serious problem, as all relevant files are removed beforehand.
The problem is that when the new installer attempts to uninstall the previous version, it aborts when the uninstaller fails. I've tried setting the 'Continue On Failure' flag (Media-->Upgrades-->MajorUpgrade-->Advanced-->Additional Settings), but with no effect.
Questions:
- Is there a way to configure IS to silently remove the previous version?
- If not, can I write some custom VB-action to silently uninstall the previous version?
EDIT
Not sure this is relevant, but here is what is wrong with the previous uninstaller:
The Installation sequence includes some custom actions. Two of these call executables which in turn install some device drivers. Obviously, they should only run when an installation is underway. Unfortunately, the previous designer did not set the 'NOT REMOVE' condition, which causes the actions to run during an uninstall operation. Thus the uninstaller fails (as it cannot find the driver executables).
Altering or removing the existing package via a minor upgrade is a good approach for fixing what's there. But ensuring this happens before your major upgrade is a quandary. You can't integrate it into a single Basic MSI package, mostly because this is not possible in Windows Installer itself. While you can add a custom action to the UI sequence, this will not work for anyone installing it silently. And you can't do anything in the execute sequence due to restrictions in Windows Installer.
If you can use a Suite/Advanced UI project, you could theoretically deliver one exe that could install the minor and then the major upgrade; I'm not sure what the hurdles are with this approach; normally the Suite isn't used to install a temporary package like the minor upgrade would be, nor does it have a clean way to automate package removal during installations.
Here are two options to help your users get where they need with the minimum confusion.
Identify, instruct, and abort
The ActionProperty of your major upgrade is filled with the product code of related packages that are present on the machine. Typically this is a single product code. If you follow the pattern of the ISPreventDowngrade entry and custom action, you can use a type 19 "error" custom action to show a message and abort the major upgrade when the older version is present. The message should instruct the user either to uninstall the older version of the product before installing the new version; or, alternately, to apply the minor upgrade and then continue.
Make the condition on the custom action as specific as possible; if you know a single product code exhibits the behavior, make your condition check for its value in the action property. If all specific range of versions exhibit this behavior (but older ones work fine), consider adding a secondary "detect only" major upgrade and condition your custom action against the secondary upgrade's action property.
Note: You can try to couple this with a UI sequence action that attempts to automate the uninstall or minor upgrade. But definitely keep the conditions in the execute sequence to ensure silent uninstallation failures get good information in the log files.
Try to ignore errors
(Whoops; after writing this, I reread where you said you've tried this approach and it doesn't work for you. So you're probably back to Identify, instruct, and abort, or perhaps using a Suite/Advanced UI project to deliver things. I'm leaving this for posterity, however, as I believe it includes good suggestions to avoid additional problems with using this option.)
I haven't used this, so I don't know the full ramifications of its behavior and I won't recommend it. But there's an option in Attributes column of the Upgrade table that allows you to ignore failures during the removal of the older version. InstallShield exposes this on the Advanced tab as Continue on Failure.
If you're 100% certain that your older version ends up in a satisfactory state, and your new version will work after this, you might try this. I would strongly recommend some thorough QA on this approach, including faking a newer major version to ensure that further upgrades still do the right thing.
Similar to the advice I gave in Identify, instruct, and abort, try to set the Continue on Failure for as few prior versions as possible. For example, you may want to split your Upgrade entries into three version ranges: those before the problem (don't continue); those affected by the problem (continue); those after the problem, i.e. future major upgrades (don't continue). This avoids hiding any similar problems that arise in the future so that you can explicitly choose how to handle them as they come up.
This is more of a comment than an answer, but it became too long for a comment.
Generally the real fix is to create a minor upgrade (or a minor upgrade patch) to "hotfix" the error in the uninstall sequence and then uninstall the product the normal way (will work regardless if it is a major upgrade uninstall or a manually triggered uninstall). This is possible because a minor upgrade doesn't uninstall the existing product (the failing uninstall custom actions will never run), but just updates the product "in place" (or overwrites it). This allows you to fix whatever was wrong in the uninstall sequence before it is called.
I have successfully used this approach many times to fix critical errors in corporate packages that may have a lot of installed instances and the uninstall sequence is failing miserably. However, it is not exactly a trivial thing to do - it will take time and testing effort. I generally deliver the update as a patch as well, but a minor upgrade should suffice (patching is complicated).
The easiest approach for your minor upgrade package is probably to set a condition that will always be false on all failing uninstall custom actions so that they never run once uninstall is actually triggered. This will obviously leave "some junk" on the box, but you may be able to ignore that or better yet clean it up. Be careful with cleanup code though - it tends to include new bugs that you then have to deal with in due course. Deployment is a process where each added release cycle, bug fix and tweak opens up new possibilities for unexpected errors that then add new complexity and unpredictability for the next release. Keep things as simple as possible. Simply put: just more stuff that can break without adding any benefit. Definitely clean things up if there are operational problems if you don't.
I won't get into too much detail with regards to how a minor upgrade should be implemented. The Installshield help file is quite good on this topic and should help you get things done. I would make an update that does nothing other than change the conditions on the uninstall actions.
Once you have a minor upgrade, you need to apply it using the appropriate command line OR just use Installshield's setup.exe to do this for you. Again see the Installshield help file ("Run-Time Behavior for Minor Upgrades" - this help page should be all you need).
The command line to apply a minor upgrade without a setup.exe wrapper is generally:
Adding
the v option
forREINSTALLMODE
is very important if you install manually via msiexec.exe without a setup.exe wrapper. This caches the new MSI on the system, and is crucial for minor upgrades to work correctly - in particular for your purpose of fixing the uninstall sequence.The MSDN documentation for Applying Minor Upgrades.
If there was only one deployment without that condition, then you could write a custom action where you can run the command line below:
msiexec.exe /x {PRODUCT_CODE_OF_OLD_MSI} REMOVE="ALL" /qn
Another trick that may work is to Schedule RemoveExistingProducts action before InstallInitialize action as stated here: How to uninstall previous version as part of msi install?
You could also write your own custom MSI DLL that terminates running processes before continuing.