Close a systemtray app before uninstalling using wix - 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

Related

WiX will not uninstall due to conditions in CustomAction

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?

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?

WIX CustomAction that runs when install fails

I have a simple Wix project and I want to add the custom action that will run no matter what the result of the install is - success, error, cancel etc.
I've tried CustomAction with Execute="rollback" - it doesn't seem working.
In order to check if my custom actions are called on error I've added this custom action to generate an error (it returns ERROR_INSTALL_FAILURE from the DLL):
<CustomAction Id="TestBad" BinaryKey="testDll" DllEntry="TestBad" HideTarget="yes" Impersonate="no" Execute="immediate" Return="check"/>
and didn't find even one way to call the custom action on error.
Is there any way to do this?
EDIT
I've forgot to mention that I need it to run at the end of install process - I want to know the result of the installation process.

MSI - is it possible to determine if a shared component is already installed by another product

Can someone please explain the odd MSI behavior in the following scenario:
I have multiple Wix projects which all share the same feature containing a single component:
<Feature Id="VTWSK" Level="1" Directory="MyCommonFolder">
<Component Id="vtwsk" Guid="{47EC2941-738D-4486-9D86-A443A40C5F94}" Directory="TempDriversFolder">
<File Id="vtwsk.key" Source="..." KeyPath="yes"/>
...
</Component>
</Feature>
I make sure that all products resolve MyCommonFolder to the same target directory so that component key path is indeed the same. I also have a custom action which should be executed if and only if the component is installed for the first time on the target machine, so I use a following condition:
<InstallExecuteSequence>
<Custom Action="InstallVTWSK" After="InstallFiles">$vtwsk=3</Custom>
</InstallExecuteSequence>
It works fine when the first product is installed on target machine, however, the second product fails to detect that vtwsk component is already installed. Logs from the second product installation:
MSI (s) (B0:AC) [13:18:22:202]: Feature: VTWSK; Installed: Absent; Request: Local; Action: Local
...
MSI (s) (B0:AC) [13:18:22:202]: Component: vtwsk; Installed: Absent; Request: Local; Action: Local; Client State: Unknown
Thus, the component state is still 3, and corresponding custom action is executed for the second time (and fails, due it's nature which I can't easily control). I would rather expected "Installed: Local; Request: Local; Action: Null".
Is it even possible to run a custom action conditionally only when the component (or feature) is installed on the target machine for a first time?
All other things being correct, what's happening is that the component is installed every time so that condition is true. There are still two or three products that install that component and the reference count in that location gets incremented, and the fact that there is one file is not relevant. The component is still being installed by each product. You really want to run that CA the first time the file is installed. I'd look at having that CA create a registry item that says the file is initialized, and use it to see if it needs initializing. Or otherwise make the CA smart enough to see that the initialization has been done so you just call it every time. Or if the fact that the file is there is sufficient then you can do a search for the file's component ID and if it's there then maybe assume it's been initialized by a previous install.

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]"/>