A few days ago I re-enabled Vista UAC on my development laptop.
I was developing a few features for Gallio that required being able to perform privilege elevations on Vista. So unless I got my act together to play nice with UAC, those features were just not going to work!
With reluctance I re-enabled UAC. Sometimes dog food, while nutritious, contains unpleasant additives.
Here's one of the new features from Gallio v3.0.7 in trunk:
When certain settings are changed, we show shield buttons:
And of course when clicking on one of those buttons we'll get the standard UAC elevation dialog. I can't show you that because by design UAC blocks out other applications including SnagIt.
How it works (very briefly)
To display shield buttons, set the Button's FlatStyle to System and then send the BCM_SETSHIELD message to the button using SendMessage. (Or borrow my ShieldButton class. Ignore the Mono bits.)
Before performing privilege elevation, check whether it is even needed for the current user. Here's one way. A better way might be to examine the Win32 ProcessToken directly.
public static bool CurrentUserHasElevatedPrivileges()
WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
To perform privilege elevation, first split the code into two parts: non-privileged and privileged. The privileged part will have to run in a separate process one way or another. Then decide how to perform the elevation using one of these options.
- Use the COM elevation moniker as in the CoCreateInstanceAsAdmin example to instantiate a COM object within an elevated process provided by the system. This method is convenient but it requires that you have registered your privileged code as a COMVisible managed class ahead of time. Consequently, we do not use this method in Gallio which is expected to be xcopy deployable with no prior installation of components required (besides copying files).
- Create a separate executable process that contains the privileged code. Be sure to embed an appropriate application manifest like the following either using the Visual Studio manifest build
option or mt.exe.
Our incantation of mt.exe looks a bit like this: "mt.exe -nologo -manifest Elevated.manifest -managedassemblyname:Gallio.Host.Elevated.exe -nodependency -outputresource:Gallio.Host.Elevated.exe;#1". Keep in mind that if you use mt.exe on a signed assembly that the signature will be invalidated and the assembly will need to be re-signed with "sn.exe -R ...". You might consider delay-signing the assembly at first, then merging the manifest, then re-signing the assembly. This way you can tell whether you're working with an assembly with a proper manifest because the original delay-signed copy will fail validation (normally) but the patched and re-signed copy will be good.
Here is the Elevated.manifest file that we use for Gallio.Host.Elevated.exe:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<description>Provides out of process hosting for Gallio components that require privilege elevation.</description>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
Launch the privileged process directly being sure to set a few extra properties:
var processStartInfo = new ProcessStartInfo("PrivilegedApp.exe");
processStartInfo.Verb = "runas"; // optional if you embedded a manifest
processStartInfo.UseShellExecute = true; // mandatory
processStartInfo.ErrorDialog = true; // mandatory
processStartInfo.ErrorDialogOwnerHandle = ownerForm.Handle; // recommended
Ok, I just glossed over a million little details...
If you are ever in a position to have to perform privilege elevation in a managed application, I highly recommend reading the relevant MSDN articles three times over.