I am a huge fan of the open source WiX Toolset1 for authoring Windows installers. I’ve used Installshield and Visual Studio Setup projects for installers past. I even had to reverse engineer an old VB Package and Deployment Wizard installer for a customer. It only took me 16 hours to re-author it with WiX (and that included testing). Sure, sometimes it would be nice to have a GUI for spitting out the XML, but with the control WiX offers me there’s no going back. If you’ve never experienced the power of WiX (including custom actions in C#), you are missing out!
This week I ran into a situation where I needed to do something that I didn’t think would be so hard to figure out.
- I really needed a per-user install.
- I really needed the installer to prompt for elevation.
I wanted a per-user install so that the application is only visible in Add/Remove Programs for the user who installed it. And my installer performed custom actions which required elevation and I didn’t want to require my users to launch the MSI with msiexec
from a command line with elevated privileges. Turns out that most people are looking for the opposite–a per-user install that doesn’t prompt for elevation, like this excellent post details. So according to those instructions, I figured I would just leave ALLUSERS undefined and then set the opposite InstallPrivileges attribute, namely Package/@InstallPrivileges="elevated"
(and Package/@InstallScope="perUser"
for good measure).
With those settings, running my installer from a standard user account without admin rights was not prompting for elevation and my installer was failing. Crap. Back to Googleing, it was surprising how many posts like this one I found which were answered only by saying that per-user installs should never require elevation. That may very well be the case, but I have a legitimate need to do this. Luckily, I found a post2 that offered the clue I needed: Package/@InstallScope
doesn’t support per-user, elevated packages!
All I had to do was omit the Package/@InstallPrivileges
(defaults to elevated) and Package/@InstallScope
attributes (continuing to leave ALLUSERS undefined). I also incorporated the trick I found from the MSDN blog above to prevent a user from setting ALLUSERS from an msiexec command line install.
<!-- NOTE: If you need to create a per-user installation (meaning it's not -->
<!-- visible in Add/Remove Programs from other logons) that prompts for -->
<!-- elevation, omit both the Package/@InstallPrivileges="elevated" and
<!-- Package/@InstallScope="perUser". -->
<Package InstallerVersion="200" Compressed="yes" />
<!-- Set the "All Users" option. -->
<!-- NOTE: For a per-user installation, the value of ALLUSERS below must be empty -->
<!-- as well. This property cannot be set to empty, but it does default to empty. -->
<!-- Thus just leave it off. -->
<!-- <Property Id="ALLUSERS" Value="" />-->
<!-- This condition adds an item to the LaunchCondition table of the MSI to block a user -->
<!-- from setting this property to something other than blank. -->
<Condition Message=”!(loc.LaunchCondition_AllUsers)”>
NOT ALLUSERS
</Condition>
<!-- This condition adds an item to the LaunchCondition table of the MSI to block a user -->
<!-- from installing this product unless they have administrative privileges on the system. -->
<Condition Message="You must have Administrative rights on this machine to install $(var.ProductName).">
<![CDATA[Privileged]]>
</Condition>
There you go, problem solved.