How can I automate testing an MSI is installable with UAC?

I want to verify that an MSI is installable by normal users. Is there any way to emulate the permissions flow of an install using UAC without having the graphical prompts?

Background:

I work with an application that is distributed on Windows via an MSI. We do automated testing on it by running msiexec and then testing the resulting application. This works, but we recently ran into an edge case it doesn't cover.

We recently added a CustomAction in our wix setup that is run at the end of the install and which requires elevated permissions to run. The problem is CustomActions, by default, run with Impersonate="yes" which means they run with the running user's permissions and not the elevated permissions granted by UAC.

In our testing, we're running from an administrator context so the install succeeds. However typical users will require a UAC prompt to grant the MSI temporary admin rights to install. Since the custom action is not running with that admin context, the install fails.

So what I want to do is set up an automated test environment that emulates the user experience more closely. UAC is generally designed to not be scripted for security reasons, which complicates the issue. So what I'm wondering is if there's any way to exercise the permissions flow in a way that is automateable, given I have full control over the environment.


Solution 1:

Elevated Rights: A per-machine setup can run with temporary admin rights - as you put it - without being a per-user install. This is done via Group Policy / Active Directory and distribution systems that handle this kind of scenario properly.

Emulate Elevated Rights: To emulate the elevated rights on a regular PC (without a distribution system), you can use a group policy / registry hack called AlwaysInstallElevated. Once applied, it essentially means that all users can run with elevated rights just launching an MSI. Sizable security hole, so don't use it for fun. You can, however, kick off any install and install with elevated rights - if that is what you need.

Deferred Mode Custom Action: Only deferred mode custom actions have elevated rights. Immediate mode custom actions can not elevate and if they try to do something to the system that requires admin rights you will get a runtime error. If you run such a failing MSI with "real" admin rights, then you will see the MSI appear to successfully install (though it will fail with elevated rights for a standard user).

Implementing a deferred mode custom action generally (for non-trivial purposes) requires some involved steps. Perhaps you are familiar with them? You can't access properties directly, you need to "send them to deferred mode" via a mechanism called CustomActionData (SO). And adding a marginal WiX sample for deferred mode.


UPDATE: New link to samples for deferred mode custom actions: How to hide the value of customactiondata in logs of MSI? - you will find links to this github sample (and other samples): https://github.com/glytzhkof/WiXDeferredModeSample


Lobbing you some links for now (too many, but just thought I'd hand you the bunch - I think the first link will do, 5 for classic technical article, 6 for a modern approach using JSON):

  • 1: Tip: MSI Properties and Deferred Execution
  • 2: How to Access Windows Installer Property in Deferred Execution
  • 3: It Wants Me to do What? Some Notes on CustomActionData in InstallShield
  • 4: Just Be Yourself: Understanding Windows Installer (MSI) Custom Action Contexts
  • 5: Installation Phases and In-Script Execution Options for Custom Actions in Windows Installer
  • 6: Beam Me Up: Using JSON to serialize CustomActionData

Privileged: If you want to ensure that the MSI package installs per-machine properly for a standard user, I would add a launch condition to the MSI to abort if privileged rights are not available (no chance of success). Note that this does not check for admin rights, but for privileged rights (elevated rights).

<Condition Message="Elevated rights required to install package.">Privileged</Condition>

msiexec.exe: And just for the record, adding a little batch to check for msiexec.exe error codes (not tested recently, but here goes - note that a number or error codes indicate success - for example "reboot initiated" and stuff like that):

1603 = Fatal error. See link above for more.

start /wait msiexec.exe /i Setup.msi /l*v Setup.log

if "%errorlevel%" == "0" goto OK
if "%errorlevel%" == "1603" goto err
if not "%errorlevel%" == "0" goto err

:OK
GOTO END

:err
rem print message and return errorlevel
echo "Error: Msiexec failed with errorlevel = %errorlevel%"
exit /b %errorlevel%

:END

Note that a number of exit codes indicate success (beyond just 0):

1641: ERROR_SUCCESS_REBOOT_INITIATED
3010: ERROR_SUCCESS_REBOOT_REQUIRED

There are probably others I have forgotten. Sample only.