WiX will not uninstall due to conditions in CustomAction - wix

I have an MSI that uses two CustomActions. The first adds some stuff to the registry. It runs every time except during uninstall. The second removes a key from the registry to be run only during uninstall.
Both CustomActions work fine, only now the MSI won't uninstall.
By "it won't uninstall" I mean neither the file that is installed is deleted nor is the MSI removed from Programs and Features when uninstall is run. The only thing is does do during uninstallation is the Custom Action that removes stuff from the registry.
Note that the problem happens on a fresh Virtual Machine, so it's not like there are old versions with different GUIDs hanging around.
Also I don't think that there's a problem with the Features, as there's only one feature -- a textfile installation (it's a toy installation -- until I get this right it's not going in the real installer).
Here is the <InstallExecutiveSequence> code from both Custom Actions. The one where I put stuff into the registry, to be done every time except uninstall:
<InstallExecuteSequence>
<Custom Action="CustomAction_CreateKey"
After="InstallInitialize">
</Custom>
</InstallExecuteSequence>
And here is the same XML from where I delete from the registry, to be done only during uninstallation:
<InstallExecuteSequence>
<Custom Action="CustomAction_DeleteKey"
After="InstallInitialize">
(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
</InstallExecuteSequence>
The uninstall logs aren't that helpful (at least, to me). There's this:
MSI (s) (BC:6C) [15:27:15:733]: Note: 1: 1725
MSI (s) (BC:6C) [15:27:15:733]: Note: 1: 2205 2: 3: Error
MSI (s) (BC:6C) [15:27:15:733]: Note: 1: 2228 2: 3: Error 4: SELECT
Message FROM Error WHERE Error = 1725
MSI (s) (BC:6C) [15:27:15:733]: Note: 1: 2205 2: 3: Error
MSI (s) (BC:6C) [15:27:15:733]: Note: 1: 2228 2: 3: Error 4: SELECT
Message FROM Error WHERE Error = 1709
MSI (s) (BC:6C) [15:27:15:733]: Product: CustomActionDeleteKeySetup --
Removal failed.
And this:
Calling custom action DeleteKey!DeleteKey.CustomActions.DeleteTheKey
Exception thrown by custom action:
System.Reflection.TargetInvocationException: Exception has been thrown
by the target of an invocation. --->
Microsoft.Deployment.WindowsInstaller.InstallerException: Cannot
access session details from a non-immediate custom action
at
Microsoft.Deployment.WindowsInstaller.Session.ValidateSessionAccess()
at Microsoft.Deployment.WindowsInstaller.Session.set_Item(String
property, String value)
at DeleteKey.CustomActions.DeleteTheKey(Session session)
--- End of inner exception stack trace ---
I'm not really sure what to look for in the log files.
To sum up: Custom Actions won't let me uninstall. I'm pulling my hair out.
EDIT
I managed to get it working, but I don't know why. It has something to do with the conditions.
The conditions were this: when uninstalling and only uninstalling, I wanted to remove the key. So -- as shown above -- here's the <InstallExecuteSequence> code:
<InstallExecuteSequence>
<Custom Action="CustomAction_DeleteKey"
After="InstallInitialize">
(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
</InstallExecuteSequence>
And then I wanted to create the key when doing everything except installing. Before, I didn't have any conditions in the <InstallExecuteSequence> bit, but when I change it to this, it seems to work (that is, the custom actions work and it uninstalls):
<InstallExecuteSequence>
<Custom Action="CustomAction_CreateKey"
After="InstallInitialize">
(NOT installed) OR (CHANGE) OR (REPAIR)
</Custom>
</InstallExecuteSequence>
The question is: why does it work? Is it because earlier I was trying to run both Custom Actions during uninstallation?

Related

Wix CustomAction with After='InstallFinalize' executed in upgrade to next version?

I am using the following code to start a Setup program at the end of my Installation (also with elevated permissions):
<Product ... >
...
<InstallExecuteSequence>
<Custom Action='LaunchSetupAction' After='InstallFinalize'/>
</InstallExecuteSequence>
<!-- Workaround required for Windows7, Setup.exe can not be started directly because of UAC
http://stackoverflow.com/questions/2325459/executing-a-custom-action-that-requires-elevation-after-install -->
<Property Id="WixShellExecTarget" Value='[#SetupEXE]'/>
<CustomAction Id="LaunchSetupAction" BinaryKey="WixCA" DllEntry="WixShellExec" Execute="immediate" Return="check" Impersonate="yes"/>
...
<MajorUpgrade DowngradeErrorMessage="A later version of [ProductName] is already installed. Setup will now exit." />
</Product>
This works well when then program is installed freshly, but when it is upgraded from a previous version with the same code, the installation reports a fatal error and fails.
In the Windows installer log I have found the following:
Action start 23:42:22: LaunchSetupAction.
...
MSI (s) (38:88) [23:42:22:342]: Invoking remote custom action. DLL: C:\WINDOWS\Installer\MSI679.tmp, Entrypoint: WixShellExec
...
WixShellExec: WixShellExecTarget is C:\Program Files\Me\MySetup.exe
...
WixShellExec: Error 0x80070002: ShellExec failed with return code 2
...
WixShellExec: Error 0x80070002: failed to launch target
...
Action ended 23:42:22: LaunchSetupAction. Return value 3.
Action ended 23:42:22: INSTALL. Return value 3.
Interesting enough these error messages (file not found?) is also there when I comment out the CustomAction in the new version of the installer.
Why is this CustomAcount executed from the previous version to be uninstalled?
Is there any way I can stop it from being run? After a certain point of the upgrade, the MySetup.exe is just not there anymore and will always cause a fatal error ...
That custom action has no condition on it, so it will be called on all types of installer operations, such as uninstall, add/remove features etc. Your upgrade is an install of the new product and an uninstall of the old product.
You need this kind of thing on your custom action:
Wix: Run custom action based on condition
so a condition of 'Not Installed' causes the CA to run only if the product is not already installed.
You can't really fix this because the uninstall for that product is embedded in the system. You'd need to fix that older version of the product by rebuilding the exact same product with the custom action condition fixed and then fix it with:
msiexec /i [path to new MSI] REINSTALL=ALL REINSTALLMODE=vomus
If you're very familiar with MSI and Orca, you may be able to locate the cached MSI file in C:\windows\installer, open it with Orca, go the InstallExecuteSequence and give that custom action a condition of 0, then save it. That basically corrects the uninstall sequence so that your upgrade will work. Caveat Emptor.
It also appears that a plain uninstall would give you same error - did you every try to do an uninstall test?

Is there an updated truth table for WiX custom action conditions?

I am trying to get some custom actions working properly on my install.
This is the table I am using for my logic in Wix (provided by ahmd0)
The problem is that it just doesn't seem to be working. Here are my conditions currently:
<Custom Action='RemoveService' After='InstallInitialize'>Installed</Custom>
<Custom Action='WaitForFileLocks' After='InstallInitialize'>Installed</Custom>
<Custom Action='InstallService' Before='InstallFinalize'>NOT REMOVE ~= "ALL" OR UPGRADINGPRODUCTCODE</Custom>
<Custom Action='MergeConfigFiles' Before='InstallFinalize'>NOT REMOVE ~= "ALL" OR UPGRADINGPRODUCTCODE</Custom>
The behavior I expect from this:
RemoveService and WaitForFileLocks should be run on uninstalls and upgrades.
InstallService and MergeConfigFiles should be run on installs and upgrades.
The behavior I am seeing:
RemoveService and WaitForFileLocks are running on uninstall, but NOT upgrades.
InstallService and MergeConfigFiles is being run on install, but NOT upgrades.
Is the table I am using wrong? Is my logic correct? Could my issue be Windows' recognition of what an "upgrade" is? I checked in Add/Remove programs after I increased all 3 version numbers for my install and the new version is properly updating there.

Understanding MSI uninstall - EXECUTEACTION = INSTALL

I'm having issues with with a custom action I want to run during uninstall.
The custom action is responsible for unregistering a system level windows service.
Custom action:
<CustomAction Id='UnregisterService'
Directory='INSTALLDIR'
ExeCommand='[INSTALLUTIL] /u "/config=[INSTALLDIR]\configInstall.xml" " ...
Return='ignore'
Execute='deferred' />
This action scheduled in the InstallExecuteSequence and should be invoked on uninstall and upgrade:
<InstallExecuteSequence>
<Custom Action="PreventDowngrading" After="FindRelatedProducts">NEWPRODUCTFOUND</Custom>
<RemoveExistingProducts After="InstallValidate" />
<Custom Action='UnregisterService' After='InstallFiles'>UPGRADEFOUND OR (Installed AND NOT UPGRADINGPRODUCTCODE)</Custom>
<Custom Action='RemAgApps' After='UnregisterService'>UPGRADEFOUND OR (Installed AND NOT UPGRADINGPRODUCTCODE)</Custom>
...
</InstallExecuteSequence>
The problem is that when I run uninstall from the ARP, uregister action does not invoked (as written in msi logs):
Skipping action: UnregisterService (condition is false)
When I dig deeper into the log (which is terrible...) I see that there is no 'uninstall' action, only install:
Property(C): EXECUTEACTION = INSTALL
Property(C): ACTION = INSTALL
Product: BLABLA -- Installation completed successfully.MSI (c) (BC:50) [14:35:37:067]: Windows Installer installed the product. Product Name: BLABLA . Product Version: 5.1.0. Product Language: 1033. Manufacturer: BLABLA Team. Installation success or error status: 0.
My questions are :
What is the flow of msi uninstall? (No couldn't find it in the web...)
Why I get the 'Install' action during uninstall?
How can I force uninstall to run my 'unregisterService' custom action?
Thanks!
Custom actions should be avoided until you fully understand Windows Installer to avoid reinventing the wheel with a more fragile solution. To remove a service during uninstall you merely need:
<ServiceControl Id="myService" Name="MyService" Stop="both" Remove="uninstall"/>
Read this good article: InstallSite: Installation Phases and In-Script Execution
Windows installer installation transactions can take many forms. Initial install, repair, maintenance install, uninstall, upgrade, patch and so on. The installer tracks the state change of features, components and products at different levels so the concept of install/uninstall is more detailed.

Close a systemtray app before uninstalling using wix

I know similar questions have been asked in the past but I still haven't managed to find a solution to my problem as of yet.
I've got a system tray app that's running and I want to close it before uninstall begins and displays the "FileInUse" dialog but whatever I'm doing doesn't appear to work. In order to close my system tray app, I need to create a file in the folder where it is installed. The app then deletes the file and closes itself.
I'm getting the following issues depending on what I've tried:
1) The "FileInUse" dialog gets displayed. No good
2) Failing to call my custom action which create a file to notify my system tray app that it should close.
Error 1 ICE77: CloseAgentMonitor is a in-script custom action. It must be
sequenced in between the InstallInitialize action and the InstallFinalize action in
the InstallExecuteSequence table
3) Fails to pass my application folder as a CustomData parameter to the CustomAction if I set it to Immediate instead of Immediate but if I set any it to Deferred, I get the error mentioned in 2)
4) I've tried different scenarios from calling the custom action before RemoveFiles, InstallValidate, InstallFinalize.
As I'm not sure what are the correct sequences, can someone tell me how and when to call my CustomAction so that it is triggered the "Remove" button is closed or before the files starts to get removed.
I want to do this when uninstalling the files and before the FileInUse dialog is displayed.
Note that it's really important that I can handle this in both silent or visual uninstall.
Thanks.
UPDATE:
I probably should have posted my wix code:
<!-- Set variables required by the CloseAgentMonitor CustomAction -->
<CustomAction Id="CloseAgentMonitorSetProp"
Return="check"
Property="CloseAgentMonitor"
Execute="immediate"
Value="APPLICATIONFOLDER=[APPLICATIONFOLDER]" />
<!-- Define CustomAction to close the Agent on uninstall -->
<CustomAction Id="CloseAgentMonitor"
Return="check"
Execute="immediate"
BinaryKey="CustomActions.CA"
DllEntry="CloseAgentMonitor" />
<InstallExecuteSequence>
<!- Make sure to set the props before the CloseAgentMonitor custom action -->
<Custom Action="CloseAgentMonitorSetProp" Before="CloseAgentMonitor">
<![CDATA[(Installed AND NOT UPGRADINGPRODUCTCODE)]]>
</Custom>
<Custom Action="CloseAgentMonitor" Before="InstallValidate">
<![CDATA[(Installed AND NOT UPGRADINGPRODUCTCODE)]]>
</Custom>
...
Changing the CustomAction to immediate and set it to be called before InstallValidate sorts out the problem mentioned in 2, but it brings back the error mentioned in point 3, where it appears that my CustomActionData is not being set, even thought it should be since it is called before the CustomAction.
You can clearly see from my logs that it is:
MSI (s) (30:08) [16:22:47:148]: Doing action: CloseAgentMonitorSetProp
MSI (s) (30:08) [16:22:47:148]: Note: 1: 2205 2: 3: ActionText
Action 16:22:47: CloseAgentMonitorSetProp.
Action start 16:22:47: CloseAgentMonitorSetProp.
MSI (s) (30:08) [16:22:47:148]: PROPERTY CHANGE: Adding CloseAgentMonitor property.
Its value is 'APPLICATIONFOLDER=C:\Program Files (x86)\Company\Client\'.
Action ended 16:22:47: CloseAgentMonitorSetProp. Return value 1.
But as you can see when my CustomAction is called, it triggers an error when trying to access the APPLICATIONFOLDER.
MSI (s) (30:08) [16:22:47:148]: Doing action: CloseAgentMonitor
MSI (s) (30:08) [16:22:47:148]: Note: 1: 2205 2: 3: ActionText
Action 16:22:47: CloseAgentMonitor.
Action start 16:22:47: CloseAgentMonitor.
MSI (s) (30:1C) [16:22:47:148]: Invoking remote custom action. DLL:
C:\Windows\Installer\MSI57B2.tmp, Entrypoint: CloseAgentMonitor
MSI (s) (30:C0) [16:22:47:148]: Generating random cookie.
MSI (s) (30:C0) [16:22:47:148]: Created Custom Action Server with PID 2528 (0x9E0).
MSI (s) (30:F4) [16:22:47:195]: Running as a service.
MSI (s) (30:F4) [16:22:47:195]: Hello, I'm your 32bit Impersonated custom action server.
SFXCA: Extracting custom action to temporary directory: C:\Windows\Installer\MSI57B2.tmp-\
SFXCA: Binding to CLR version v4.0.30319
Calling custom action CustomActions!CustomActions.CustomActions.CloseAgentMonitor
CloseAgentMonitor - Start
IsAgentMonitorRunning - Start
Checking if Agent Monitor is running.
Agent Monitor running: True
IsAgentMonitorRunning - End
Checking CustomActionData - Start
Checking CustomActionData - End
Exception thrown by custom action:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of
an invocation. ---> System.Collections.Generic.KeyNotFoundException: The given key was
not present in the dictionary. at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at Microsoft.Deployment.WindowsInstaller.CustomActionData.get_Item(String key)
at CustomActions.CustomActions.CloseAgentMonitor(Session session)
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object arguments, Signature sig,
Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object parameters,
Object arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder
binder, Object parameters, CultureInfo culture)
at Microsoft.Deployment.WindowsInstaller.CustomActionProxy.InvokeCustomAction(Int32
sessionHandle, String entryPoint, IntPtr remotingDelegatePtr)
CustomAction CloseAgentMonitor returned actual error code 1603 (note this may not be 100%
accurate if translation happened inside sandbox)
Action ended 16:22:47: CloseAgentMonitor. Return value 3.
Action ended 16:22:47: INSTALL. Return value 3.
I figured it out!!
I can't believe it was that simple! Spend hours looking into it when all I had to do, was change the session.CustomActionData["APPLICATIONFOLDER"] to session["APPLICATIONFOLDER"].
I originally started using the session variables but hit various problems so I ended up using CustomActionData but I've just checked and all my custom action were deferred up to now.
So if I get Wix correctly, you should use CustomActionData in deferred CustomAction and you should use the session's variables in immediate CustomAction.
So code in your custom action should be:
string applicationFolder = session["APPLICATIONFOLDER"];
instead of
string applicationFolder = session.CustomActionData["APPLICATIONFOLDER"];
Hope this helps someone else!
The files in use detection is performed by the InstallValidate action, which is before InstallInitialize. Assuming your code works ok, run it as an immediate custom action, and before InstallValidate should be ok.
Why do you need to create a file in that same folder? It doesn't seem related to your files-in-use problem. The usual method of closing a running app is to send it a close down message. WiX has a CloseApp custom action that will do that.
And there's this, which says similar things:
WiX close application before uninstall - close open applications message

WIX property value substitution and CAQuietExec

So far, I'm NOT finding WIX to be my favorite most intuitive, easy-to-use, language/system.
I created a custom dialog, entered data, and see the values in the install log.
MSI (c) (C8:A4) [14:42:37:137]: PROPERTY CHANGE: Modifying VARRADIOBUTTONENVIRONMENT property. Its current value is 'Dev'. Its new value: 'QA'.
MSI (c) (C8:A4) [14:42:41:448]: PROPERTY CHANGE: Modifying VARTEXTSETTINGSFILENAME property. Its current value is 'C:\Path\SettingsFileGenerator.xml'. Its new value: 'Test1234.txt'.
Later, when it comes time to use the value I see this:
Action ended 14:42:43: InstallFinalize. Return value 1.
MSI (s) (BC:F8) [14:42:43:676]: Doing action: QtExec1
Action 14:42:43: QtExec1.
Action start 14:42:43: QtExec1.
MSI (s) (BC:F4) [14:42:43:682]: Invoking remote custom action. DLL: C:\Windows\Installer\MSIC4A7.tmp, Entrypoint: CAQuietExec
MSI (s) (BC!BC) [14:42:43:690]: PROPERTY CHANGE: Deleting QtExecCmdLine property. Its current value is '"[INSTALLLOCATION]XmlPreprocess.exe /i:web.config /e:[VARRADIOBUTTONENVIRONMENT] "'.
CAQuietExec: Error 0x8007007b: Command failed to execute.
CAQuietExec: Error 0x8007007b: CAQuietExec Failed
Action ended 14:42:43: QtExec1. Return value 3.
Action ended 14:42:43: INSTALL. Return value 3.
Property(S): StartIIS7ConfigTransaction = ScaConfigureIIs
And at the end when it dumps all the properties it shows this:
Property(S): VARRADIOBUTTONENVIRONMENT = QA
Property(S): VARTEXTSETTINGSFILENAME = Test1234.txt
Property(S): VerifyCurrentPropValueOfEnv = [VARRADIOBUTTONENVIRONMENT]
Here are fragments of my code. I just changed property ids to all-caps and added the secure="yes" based on another post I saw. I've tried with and without the Secure="yes".
<Property Id="VARRADIOBUTTONENVIRONMENT" Secure="yes">Dev</Property>
<Property Id="VARTEXTSETTINGSFILENAME" Secure="yes">C:\Path\SettingsFileGenerator.xml</Property>
...then later...
<Property Id="VerifyCurrentPropValueOfEnv" Value="[VARRADIOBUTTONENVIRONMENT]"/>
<Property Id="QtExecCmdLine" Value=""[INSTALLLOCATION]XmlPreprocess.exe /i:web.config /e:[VARRADIOBUTTONENVIRONMENT] ""/>
<CustomAction Id="QtExec1" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="immediate" Return="check"/>
<InstallExecuteSequence>
<Custom Action="QtExec1" After="InstallFinalize"><![CDATA[NOT(Installed)]]></Custom>
</InstallExecuteSequence>
At the end of the install, I see that XmlPreProcess.exe is installed in the installation directory.
Questions:
Is there something I'm doing wrong to get the values to substitute?
I'm using CAQuietExec because I was told it would log better errors and echo the output of the command window to the install log. I don't really care about not seeing the command window. I'm not sure how to find out what this means:
"CAQuietExec: Error 0x8007007b: Command failed to execute.". I can't tell if this is a WIX error trying to call XmlPreprocess.exe or if it got into XmlPreprocess and then threw the error.
Thanks,
Neal Walters
Looking at the log extract I think I can see the problem, you've quoted the entire command line, try this instead:
<Property Id="QtExecCmdLine" Value=""[INSTALLLOCATION]XmlPreprocess.exe" /i:"[INSTALLLOCATION]web.config" /e:[VARRADIOBUTTONENVIRONMENT]"/>