Saturday, 2 February 2008

Windows Vista and UAC

I wanted a way to automatically start Fonebook with windows but I wanted to be able to change if it started automatically from within the application. The easiest way to do this was through the registry but not all users will have permission to edit the registry and if UAC is enabled then even administrators wont have permission by default!

Now the standard in vista is to add a shield to buttons which require admin rights and then present the UAC dialog when its clicked. Below shows the screens running under XP and then Vista to show what I wanted to achieve:




The code to do this is:-
[DllImport("user32")] 
public static extern UInt32 SendMessage (IntPtr hWnd, UInt32 msg, UInt32 wParam, UInt32 lParam);

internal const int BCM_FIRST = 0x1600; //Normal button
internal const int BCM_SETSHIELD = (BCM_FIRST + 0x000C); //Elevated button
static internal bool IsAdmin()
{
WindowsIdentity id = WindowsIdentity.GetCurrent();
WindowsPrincipal p = new WindowsPrincipal(id);
return p.IsInRole(WindowsBuiltInRole.Administrator);
}

static internal void AddShieldToButton(Button b)
{
b.FlatStyle = FlatStyle.System;
SendMessage(b.Handle, BCM_SETSHIELD, 0, 0xFFFFFFFF);
}

internal static void RestartElevated()
{
Process oProcess = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = true;
startInfo.WorkingDirectory = Environment.CurrentDirectory;
startInfo.FileName = "FonebookRegister.exe";
startInfo.Verb = "runas";
startInfo.Arguments = "\"" + Application.ExecutablePath + "\"";
Process p = Process.Start(startInfo);
}
Lets go through the code a bit.

The IsAdmin part just checks to see if the current user is in the administrator role (so you know if you need to elevate privileges or not). The AddShieldToButton simply takes a button then modifies the style to have the shield on it and the RestartElevated runs a proxy program with the verb "Runas". This allows the program to go from filtered admin to full admin.

The reason I use a proxy program is because you cannot change a token of a running process so you need to start a new process with the correct permission. This site has a load of good info about vista permissions and is really worth a read http://codefromthe70s.org/vistatutorial.asp

Now all we need to do is check when the page loads if the user is not an admin and then add the shield to the button. When the button is pressed we check to see if the user is not an admin and run the RestartElevated portion of the code. If the user clicks cancel to the UAC prompt then you will get an error of type System.ComponentModel.Win32Exception so you will need to explicitly handle this (I haven't shown this code above but is it basically a try catch around the process.start bit and you need to check for a ERROR_ELEVATION_REQUIRED message). If the user is an admin then you can execute the same proxy program do do the admin bit and then you are using the same code base.

Nice and simple eh!

Ross

No comments: