Note: This is part of a series, you can find the rest of the parts in the series index.
Windows Vista introduced a feature called User Account Control (UAC) which added the following to Windows (in addition to a lot of hair pulling by some users). Visually it brought a small shield overlay icon which indicates to the user that when you click that icon or button you will be prompted to confirm your action and possibly to enter an administrators username and password. This was introduced to stop people from shooting themselves in the foot by making certain actions which could break Windows require special privileges (called administrator privileges, which I find is confusing with administrator users and groups. So I call it root privileges).
I have been a fan of this idea since it was launched and as a developer I have kept it turned on, mainly because the my customers may not have it turned off. Imagine the scenario where I have it off and something works and on a customers machine it fails because UAC is on. I see a Works on my machine scenario coming up.
Pull has a part of it which actually bumps up against UAC – registering protocol handlers. I do not want the entire of Pull to need root privileges when running, I only want the small part where you can register or unregister a protocol handler to run in root privileges.
Multiple Processes
The first issue is that root privileges are given to an entire process, and you cannot give it to a thread or method or some sub part. To solve this for Pull, meant creating a second executable file named ProtocolHandler.exe, which takes a few command line parameters and handles the registering and unregistering of protocol handlers.
This enables Pull to launch this second executable with the required root privileges and have it do the dirty work without Pull needing any root privileges.
Running with Root Privileges
Kicking off another process in C# is very easy thanks to the Process class which handles the launching with the Start method (line 11 below). The Process class knows which process to run thanks to the ProcessStartInfo class which is setup before hand and passed to the StartInfo property.
To enable root privileges in the new process all you need to do is set the Verb property of ProcessStartInfo to runas (line 6 below).
Something Pull wants is to wait for the process to finish running, so that the user is confused by it immediately returning and nothing has happened yet. This is solved by using the WaitForExit method on the Process (line 12 below).
private static void RunProtocolHandler(string arguments) { ProcessStartInfo processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = Path.Combine(Directory.GetCurrentDirectory(), "ProtocolHandler.exe"); processStartInfo.Arguments = arguments; processStartInfo.Verb = "runas"; using (Process process = new Process()) { process.StartInfo = processStartInfo; process.Start(); process.WaitForExit(); } }
The Shield
The final component was to follow the UI guidelines and place a shield icon on the buttons which launch the other application, so that the user is aware this will require root privileges. While you can just grab a shield image and place it on the button that is not recommended because:
- What if the logo changes in future Windows versions? – you are out of date
- What if the shield is not needed, because the person is running with root privileges already?
To handle this for me I have a small class called UACShield which offers two methods:
- IsAdmin: this simple method returns true if you have root privileges and false if you don’t. This is done using pure .NET and just checking if the user has the Administrator role (line 14 below).
- AddShieldToButton: this method takes a button, and if a user is not an admin adds the shield icon. It does this by calling into the Win32 API and calling the SendMessage to update the button. One caveat of this is that the button’s flat style must match the system’s flat style. This means that if you have some special UI tweaks on the button this may break those tweaks.
internal class UACShield { private class NativeMethods { [DllImport("user32")] public static extern UInt32 SendMessage(IntPtr hWnd, UInt32 msg, UInt32 wParam, UInt32 lParam); public const int BCM_SETSHIELD = 0x160C; //Elevated button } public static bool IsAdmin() { using (WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent()) { return new WindowsPrincipal(currentIdentity).IsInRole(WindowsBuiltInRole.Administrator); } } public static void AddShieldToButton(Button button) { if (IsAdmin()) { // no need for admins return; } button.FlatStyle = FlatStyle.System; NativeMethods.SendMessage(button.Handle, NativeMethods.BCM_SETSHIELD, 0, 0xFFFFFFFF); } }