Wix Conditional custom action - wix

I have a Wix project which has several msi and a bootstrapper to run these msi.
In one of this msi, I want to run a custom action based in a parameter property.
In my Product.wxs, I have this code:
<CustomAction Id="MyAction" FileKey="myapp.exe" ExeCommand="-a -b"
Execute="deferred" Return="check" />
<InstallExecuteSequence>
...
<Custom Action="MyAction" After="CustomActionInstallService">
NOT Installed AND NOT PATCH AND MYPROPERTY=1
</Custom>
</InstallExecuteSequence>
From command line I run:
mybootstrapper MYPROPERTY=1
But the custom action doesn't run.
For testing, I have change my custom action like this:
<Custom Action="MyAction" After="CustomActionInstallService">
MYPROPERTY=1
</Custom>
with the same result, the custom action doesn't run.
Any ideas?
Thanks in advance

Within your bootstrapper (burn) script are you passing the property to the MSI? Something like this should be done within your burn project to pass down the property:
<Bundle>
<Variable Name="MYPROPERTY" bal:Overridable="yes"/>
<Chain>
<MsiPackage>
<MsiProperty Name="MYPROPERTY" Value="[MYPROPERTY]"/>
</MsiPackage>
</Chain>
</Bundle>

Related

In my Windows WiX Installer my Custom Actions don't run

I want to have the installer/uninstaller remove a folder that contains content generated by the application at run time. I figured a Custom Action would be the way to go.
We are using WiX 3.6.
(I want it in the installer sequence for a specific reason that is not important to this question.)
Here is my CustomAction definitions in the xml:
<Binary Id="CustomActionLib" SourceFile="$(var.CustomActionLibrary.TargetDir)$(var.CustomActionLibrary.TargetName).CA.dll" />
<CustomAction Id="DeleteLocalizedCA" Impersonate="yes" BinaryKey="CustomActionLib" DllEntry="DeleteLocalized" Return="check" />
<CustomAction Id="DeleteResourcesCA" Impersonate="yes" BinaryKey="CustomActionLib" DllEntry="DeleteResources" Return="check" />
Here are my references to them:
<InstallExecuteSequence>
<Custom Action="DeleteLocalizedCA" Before="InstallFiles"/>
<FindRelatedProducts Before="LaunchConditions" />
<RemoveExistingProducts After="InstallFinalize" />
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
</InstallExecuteSequence>
<InstallUISequence>
<Custom Action="DeleteLocalizedCA" Before="InstallFiles"/>
<FindRelatedProducts Before="LaunchConditions" />
</InstallUISequence>
I added the CustomActionLibrary project to the solution and added a reference to it from the installer project but it never runs, I never see it in the logs, nothing!
And thus my question, Why Don't my WiX Custom Actions Run?
After several hours of googling and reading (Blog posts, documentation, Stackoverflow, etc.) and testing I finally found a solution that none of my reading pointed to.
I had to put an InstallExecuteSequence to contain my references in a fragment that contained a ComponentGroup:
<Fragment>
<InstallExecuteSequence>
<Custom Action="DeleteLocalizedCA" Before="InstallFiles">NOT Installed</Custom>
</InstallExecuteSequence>
<ComponentGroup Id='StringsComponents'>
...
</ComponentGroup>
</Fragment>
The Fragment that I had previously put the CustomAction reference in only had steps but no Component or ComponentGroup so apparently doesn't do anything. (I am not the original author of the installer, just taking over for a co-worker who wasn't able to help me on this).
Hopefully this helps others who are struggling with the same issue.

WiX Remove files on uninstall but not update

I have an app that can log when given the correct flags at install time (/logLevel=debug on install gets passed to the app when the service starts). Our update process is a automated uninstall then install with a new MSI package. I know there is built in patch functionality with WiX, but this is our process.
Similarly with the logLevel parameter, I'd like to pass something to the effect of UPDATE="true" on the command line during uninstall. When this parameter is passed to the uninstaller it should not delete the log files. Currently we delete the files every time, but would like to retain the log files during an update. This is what I am trying to extend as of right now:
<?if $(var.BUILD_CONFIG) = "Debug" ?>
<?else?>
<CustomAction Id="Cleanup_logfile" Directory="TempTest"
ExeCommand="cmd /C "del %systemroot%\temp\hexis_hawkeye_g.log.*""
Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="Cleanup_logfile" Before="RemoveFiles" >
REMOVE="ALL"
</Custom>
</InstallExecuteSequence>
<?endif?>
And I've been playing with code similar to something like the following but it doesn't seem to work:
<?if $(var.BUILD_CONFIG) = "Debug" ?>
<?else?>
<?if '[UPDATE]' = "true" ?>
<?else?>
<CustomAction Id="Cleanup_logfile" Directory="TempTest"
ExeCommand="cmd /C "del %systemroot%\temp\hexis_hawkeye_g.log.*""
Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="Cleanup_logfile" Before="RemoveFiles" >
REMOVE="ALL"
</Custom>
</InstallExecuteSequence>
<?endif?>
<?endif?>
I'm not sure if I'm not initializing the UPDATE variable correctly, or if this really is some pre-processing that cannot be implemented in this fashion. I would think it would not work because these constructs are described on the preprocessor doc page, however, the /logLevel and various other parameters seem to work fine at run-time installation. I'm totally WiX illiterate and have been trying to read their documentation to no avail, any helpful links appreciated.
The problem as I see it: during a major upgrade when the application is uninstalled (and later on installing the new version) REMOVE=ALL is also true during uninstalling the application, so the files will be deleted.
You need to additionally check if the UPGRADINGPRODUCTCODE is also set or not, which would only be true during an update.
Check this answer where the correct condition is given (and bookmark the question as I did, it is very very useful for all the possible states and conditions ;-)).
The correct condition should be the following in your case:
<InstallExecuteSequence>
<Custom Action="Cleanup_logfile" Before="RemoveFiles" >
(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")
</Custom>
</InstallExecuteSequence>
This is probably a bit hackish, but I was able to pass what I wanted by insinuating from the LOGLEVEL what action to take instead of passing an arbitrary variable:
msiexec.exe /x {blah-blah-guid-blah} INSTALLLEVEL=2
And for the configuration of my custom action:
<?if $(var.BUILD_CONFIG) = "Debug" ?>
<?else?>
<CustomAction Id="Cleanup_logfile" Directory="TempTest"
ExeCommand="cmd /C "if [INSTALLLEVEL] GEQ 2 del %systemroot%\temp\hexis_hawkeye_g.log.*""
Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" />
<InstallExecuteSequence>
<Custom Action="Cleanup_logfile" Before="RemoveFiles" >
REMOVE="ALL"
</Custom>
</InstallExecuteSequence>
<?endif?>

Wix custom action execute cmd not working

I'm trying to execute a custom action during a wix installation and getting an error when the custom action gets called. I want to add the permission group "Everyone" with full control to a folder using cacls. When I run it from cmd it works fine but from the installer it doesnt work. Below is the error message from the wix installation log.
Info 1721.There is a problem with this Windows Installer package. A program required for this install to complete could not be run. Contact your support personnel or package vendor. Action: SetPermissions, location: , command: "c:\Windows\SysWOW64\cmd.exe" cacls "c:\Program Files\Test" /g everyone:f /e
Here is my custom action in the wix file
<CustomAction Id="SetPermissions" Property="PermissionsAction" ExeCommand="" [SystemFolder]cmd.exe" cacls "[Folder]." /g everyone:f /e" Execute="immediate" Return="ignore" />
<CustomAction Id="PermissionsAction" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>
<InstallExecuteSequence>
<Custom Action="SetPortalDataPermissions" Before="InstallFinalize">
</Custom>
</InstallExecuteSequence>
You can do what you want to do within Wix without custom actions:
<CreateFolder Directory="DirectoryToSetPermissions">
<util:PermissionEx User="Everyone" GenericAll="yes" />
</CreateFolder>
To use the Util extension you have to add a reference to WixUtilExtension assembly and add the UtilExtension namespace to the Wix tag on your wsx file like so:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

WiX: make symbolic link with UAC enabled

I want to execute a custom action in a Windows Installer (with WiX script) that makes symbolic links at the end of installation. mklink requires administrator privilege, as the installer restricts. This is what I wrote:
<CustomAction Id="mklink_cmdline" Property="QtExecCmdLine" Value='"[SystemFolder]cmd.exe" /c mklink "[SystemFolder]my_app.dll" "[INSTALLDIR]my_app.dll"' />
<CustomAction Id="mklink_exec" BinaryKey="WixCA" DllEntry="CAQuietExec" Return="ignore" />
...
<InstallExecuteSequence>
<Custom Action="mklink_cmdline" Before="InstallFinalize">
...
</Custom>
<Custom Action="mklink_exec" After="mklink_cmdline">
...
</Custom>
...
</InstallExecuteSequence>
This works perfectly if UAC is completely disabled. However, when enabling UAC in any level, this custom action fails with
CAQuietExec: You do not have sufficient privilege to perform this operation.
even if I allowed in the consent window. I tried to change Execute to deferred, Impersonate to no, or change package's InstallPrivileges to elevated, none of them works.
Any suggestion I can bypass? Thank you!
Edit: revised code with deferred custom action
<CustomAction Id="mklink_cmdline" Property="mklink_exec" Value='"[SystemFolder]cmd.exe" /c mklink "[SystemFolder]my_app.dll" "[INSTALLDIR]my_app.dll"' />
<CustomAction Id="mklink_exec" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Impersonate="no" Return="ignore" />
...
<InstallExecuteSequence>
<Custom Action="mklink_exec" Before="InstallFinalize">
...
</Custom>
<Custom Action="mklink_cmdline" Before="mklink_exec">
...
</Custom>
...
</InstallExecuteSequence>
Does it work when ran from an administrator command prompt? I assume it does.
From what I found the msi cannot raise the UAC level which is what you need here. I had to create a setup.exe that wrapped the msi as an embedded resource and executed it. The setup.exe includes the app.manifest requesting administrator execution level which raises the UAC level appropriately:
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assemblyIdentity version="1.0.0.0" name="Setup.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
</asmv1:assembly>
I may just not understand WIX, custom actions and UAC enough, but this is what I ended up doing.
Are you scheduling it between InstallInitialize and InstallFinalize when you mark it for Deferred? Your Before and after looks a little wierd:
InstallFinalize
_cmdline before InstallFinalize
_mkline_exec after _cmdline
Sounds a little nondeterministic. You might find _cmdline occurring after InstallFinalize and deferred won't work there.
Try:
InstallFinalize
_exec before InstallFinalize
_cmldline before _exec
If it's actually mklink that is requiring elevation, you might try using SysInternals junction.exe instead.
I ended up bundling elevate.exe from wintellect, deploy it to some temp folder and supply it with a path to command-line script which created all symbolic links. Than it was invoked via the custom action.
Command line file in turn has some goodness inside to detect proper program files folder. or get it from the command line, if needed.
It appears that even though WiX correctly elevates the custom action, msi (or Windows Installer) itself doesn't grant it sufficient rights to properly run mklink command.
Also note that Impersonate="yes" in the CA. I believe that's what will let msi to show elevation dialog box when it executes the action.
command line file:
cd /D %~p0
IF EXIST "%PROGRAMFILES(x86)%" SET PROGFILES=%PROGRAMFILES(x86)%
IF "%PROGFILES%".=="". SET PROGFILES=%PROGRAMFILES%
SET INSTALLPATH=%PROGFILES%\MyGreatProduct
SET DATAPATH=%PROGRAMDATA%\MyGreatProduct
IF NOT "%~1."=="." SET INSTALLPATH=%~1
IF NOT "%~2."=="." SET DATAPATH=%~2
IF EXIST "%INSTALLPATH%" mklink "%INSTALLPATH%\veryimportant.ini" "%DATAPATH%\veryimportant.ini"
in the wxs file:
<Component Directory="TempFolder" Id='Comp_Temp_Makesymlinks' Guid='47a58219-1291-4321-4321-176987154921'>
<File Id='makesymlinks_cmd' Source='makesymlinks.cmd'>
<Permission User='Everyone' GenericAll='yes' />
</File>
<File Id='elevate_exe' Source='elevate.exe'>
<Permission User='Everyone' GenericAll='yes' />
</File>
</Component>
<SetProperty Id="CA_MakeSymLinksCmd" Before="CA_MakeSymLinksCmd" Sequence="execute"
Value=""[TempFolder]\elevate.exe" "[TempFolder]\makesymlinks.cmd"" />
<CustomAction Id="CA_MakeSymLinksCmd" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="yes" />
<InstallExecuteSequence>
<Custom Action="CA_MakeSymLinksCmd" Before="InstallFinalize"><![CDATA[NOT Installed AND VersionNT >= 600 ]]></Custom>
</InstallExecuteSequence>

Sequencing a custom action in WiX before "LaunchConditions"

Is it possible to sequence a custom action before "LaunchConditions"?
This is my custom action:
<CustomAction
Id="CA_vcAppRunning"
BinaryKey="vcShowMsg"
DllEntry="IsAppRunning"
Return="check"
Execute="immediate"/>
Sequenced in <InstallExecuteSequence/>
<Custom Action="CA_vcAppRunning" Before="LaunchConditions" />
I tried this, opened the MSI file in Orca and found that my custom action is sequenced at "99".
But when I tried to install, it never got called.
I want to schedule this before LaunchConditions as this custom action is supposed to set a property which is used in the LaunchCondition (if the application is running, exit the installer/updater).
Don't schedule it in before LaunchConditions, schedule it after FindRelatedProducts and then add a second custom action that blocks install based on the results from your first CA.
This is the same method used to prevent downgrading in many tutorials, e.g.
<CustomAction Id="CA_BlockOlderVersionInstall" Error="!(loc.LaunchCondition_LaterVersion)" />
<InstallExecuteSequence>
<LaunchConditions After="AppSearch" />
<Custom Action="CA_BlockOlderVersionInstall" After="FindRelatedProducts">
<![CDATA[NEWERVERSIONDETECTED]]>
</Custom>
</InstallExecuteSequence>
<InstallUISequence>
<LaunchConditions After="AppSearch" />
<Custom Action="CA_BlockOlderVersionInstall" After="FindRelatedProducts">
<![CDATA[NEWERVERSIONDETECTED]]>
</Custom>
</InstallUISequence>