WiX: make symbolic link with UAC enabled - wix

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>

Related

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?>

prop.exe elevated execution in Wix

This question seems to have been answered ad nauseam on this web site but I cannot get Wix to run an exe with Administrator rights (Windows 8.1 64-bit).
The installer I develop copies the prop.exe utility (http://prop.codeplex.com/) to a folder under Program Files (appfolder) as well as a file (my_file.propdesc) which needs to be registered/unregistered by prop.exe like:
prop schema register my_file.propdesc (at the end of installation)
prop schema unregister my_file (at the beginning of uninstallation)
These two command lines need to be run with Administrator privileges. Because these should also be run without a command prompt, I've used CAQuietExec with another CustomAction preparing the argument for CAQuietExec (prop.exe is 32-bit so it's CAQuietExec instead of CAQuietExec64 if I am not mistaken).
<CustomAction Id='PropReg_Prep' Property='PropReg' Value='"[appfolder]prop.exe" schema register "[appfolder]my_file.propdesc"' Execute='immediate' />
<CustomAction Id='PropUnReg_Prep' Property='PropUnReg' Value='"[appfolder]prop.exe" schema unregister "[appfolder]my_file.propdesc"' Execute='immediate' />
<CustomAction Id="PropReg" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="no" />
<CustomAction Id="PropUnReg" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="no" />
The custom actions are executed as per:
<InstallExecuteSequence>
<Custom Action="PropReg_Prep" After="CostFinalize" >NOT Installed</Custom>
<Custom Action="PropUnReg_Prep" After="CostFinalize" >Installed</Custom>
<Custom Action="PropUnReg" After="InstallInitialize" >Installed</Custom>
<Custom Action="PropReg" After="InstallFiles" >NOT Installed</Custom>
</InstallExecuteSequence>
I cannot get prop to register/unregister my_file.propdesc. Could someone help?
You are scheduling the actions as Execute='immediate' this runs as the user that executes the installer.
Switch to using Execute='deferred' this will run as the system account. Assuming that the prop.exe doesn't require a full profile to run this should work.
When you're not impersonating you're running with the system account, that's got admin privileges but if you are expecting it to be a user account then there might be issues.
The thing that looks weird to me is that [appfolder]prop.exe for a number of reasons. It doesn't look like a proper application folder, so make sure it's correct. It also needs to be in uppercase, making it a public property, and you should mark it Secure="yes" in the property element. The issue is that it may not get properly transferred from your immediate CA into the execute sequence where it's used.
p.s. Do the install creating a verbose log so you can see how those directory vales are actually being resolved at run time.

Elevated custom action before removing files

I'm trying to write an Installer for my Windows Service using WiX. My executable can register/unregister itself as a Windows Service using the command line parameters --install and --uninstall. This is what I came up with:
<CustomAction Id='InstallAsService' FileKey='CCWirelessServer.exe' ExeCommand='--install' Return='check' Impersonate='no' Execute='deferred' />
<CustomAction Id='InstallAsServiceRollback' FileKey='CCWirelessServer.exe' ExeCommand='--uninstall' Return='check' Impersonate='no' Execute='rollback' />
<CustomAction Id='UninstallAsService' FileKey='CCWirelessServer.exe' ExeCommand='--uninstall' Return='check' Impersonate='no' Execute='deferred' />
<InstallExecuteSequence>
<Custom Action='InstallAsService' After='InstallFiles' >NOT Installed</Custom>
<Custom Action='InstallAsServiceRollback' Before='InstallAsService' >NOT Installed</Custom>
<Custom Action='UninstallAsService' Before='RemoveFiles' >Installed</Custom>
</InstallExecuteSequence>
Both install and uninstall basically work. But during uninstall I get the following message:
The setup must update files or services that cannot be updated while the system is running. If you choose to continue, a reboot will be required to complete the setup.
Despite this error message, the service gets unregistered and the files are deleted without a reboot. To me this looks like the installer is checking if CCWirelessServer.exe is opened before it executes my custom action.
So my question is: How do I need to modify my install execute sequence so that this error message does no longer appear?
If you are developing for Windows Installer > 3.1 you can take a look at the MSIRESTARTMANAGERCONTROL-property to see if it it set properly or if other values would would stop displaying the message.
I could suppress the message using the following values:
<Property Id="MSIRESTARTMANAGERCONTROL" Value="Disable" Secure="yes" />

Wix: how to forcefully kill a process/task?

I need to forcefully kill a process that is running in the background before attempting to delete any files, when running an Uninstall from an MSI created with Wix. The main application consist of a trayicon which reflects the status of the bg-process monitoring local windows services (made on C#, though this may not be so relevant going further).
I first tried the following:
<File Id='FooEXE' Name='Foo.exe' Source='..\Source\bin\Release\Foo.exe' Vital='yes' />
...
<InstallExecuteSequence>
<Custom Action="CloseTray" Before="InstallValidate" />
</InstallExecuteSequence>
...
<CustomAction Id="CloseTray" ExeCommand="-exit" FileKey="FooEXE" Execute="immediate" Return="asyncWait" />
The tray icon is immediately closed after confirming application-close dialog, but the Foo.Exe task still appears on the taskmgr after the uninstall completed. Also,the following error message was given:
Thats why, then I tried this:
<InstallExecuteSequence>
<Custom Action="Foo.TaskKill" Before="InstallValidate" />
</InstallExecuteSequence>
...
<CustomAction Id="Foo.TaskKill" Impersonate="yes" Return="asyncWait" Directory="WinDir" ExeCommand="\System32\taskkill.exe /F /IM Foo.exe /T" />
After obtaining the same result, tried:
<Property Id="QtExecCmdLine" Value='"[WinDir]\System32\taskkill.exe" /F /IM Foo.exe'/>
...
<InstallExecuteSequence>
<Custom Action="MyProcess.TaskKill" Before="InstallValidate" />
</InstallExecuteSequence>
...
<CustomAction Id="MyProcess.TaskKill" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="immediate" Return="ignore"/>
Sample which I took from here: How to kill a process from WiX
lately when all else failed, I also tried this without any success:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
...
<InstallExecuteSequence>
<Custom Action="WixCloseApplications" Before="InstallValidate" />
</InstallExecuteSequence>
...
<util:CloseApplication Id="CloseFoo" CloseMessage="yes" Description="Foo is still running!" ElevatedCloseMessage="yes" RebootPrompt="yes" Target="Foo.exe" />
This one gave me a different error:
I'm thinking on building a statue in honor to this process that just refuses to die!!! ... either that or think a problem on the application side exists, where I should add something like Application.Exit(); or Environment.Exit(0); at some line inside Program.cs.
Is there any other thing I could do at either Wix or my application to attempt closing it successfully at Uninstall?
Thanks!
Personally I think the best option for you to go with is the in-built CloseApplication method rather than your previous options.
The error you are getting for that (Error code 2762) is because you are trying to schedule the action in the immediate sequence but have the ElevatedCloseMessage="yes" set which triggers it as a deferred action. Either remove this attribute or schedule it in the deferred sequence.

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