Question

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>
Was it helpful?

Solution

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.

OTHER TIPS

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="&quot;[TempFolder]\elevate.exe&quot; &quot;[TempFolder]\makesymlinks.cmd&quot;" />
<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>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top