Windows Process Elevation and C#.NET

A Windows process inherits the permissions of the user that executes it. Most applications run fine as the currently logged in user. Some applications however need special Administrator permissions to perform certain actions (like updating machine registry settings, updating/writing to certain system locations, etc).

If you have UAC (User Access Control) enabled on a Windows Vista/7 machine (or Windows Server variants), you may have seen a prompt saying that an application requires administrator rights and asks you to confirm or deny.


As a developer, you may have developed an application that runs mostly fine under the current user, but has some feature or function that requires admin access, and so you want to know how to invoke one of these UAC elevation prompts to request temporary admin permission for your program -- you can't.

A process inherits the permissions of the user when it is first launched, and it is stuck with these permissions until terminated. So you either run your application as the current user, or you request that the application be ran as an administrator. In .Net, this can be done  by using a manifest file and setting requestedExecutionLevel to requireAdministrator. So when a user double-clicks your application, it triggers an elevation prompt like the above from the beginning.

Splitting your application into separate processes
However, running an application as an Administrator when only one or two specific features require such permissions is not very secure, and does not inspire a lot of trust. So the common approach is to run the application in restricted mode as the current user, and when it needs to execute an Admin function, the application spawns a new process re-launching itself with a command-line argument that identifies the feature to execute. The process is spawned as an Admin user, so it triggers the elevation prompt, the user clicks ok, the application interprets the command line switch, executes the admin code, and terminates.

In C# this would look something like:
-------------------------------------------------------------------------
public static void runProcessAsAdmin(String appFullPath, String commandLine) {
    WindowsPrincipal pricipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
    bool IsAdmin = pricipal.IsInRole(WindowsBuiltInRole.Administrator);
    if (!IsAdmin) {
        try {
            Process p = new Process();
            p.StartInfo.Verb = "runas";
            p.StartInfo.FileName = appFullPath;
            p.StartInfo.Arguments = commandLine;
            p.Start();
        } catch (Exception ex) {
            Log.error("Action Failed", "Could not launch process to perform this action - " + ex.Message);
        }
    } else {
         //process is already admin, so could launch action directly in same process without spawning new
    }
}

public static void main(String[] args) {
   if(args.Length > 0) {
      if(args[0] == "someAction") {
          SomeClass.someAction();
      }
   } else {
      runProcessAsAdmin(Application.ExecutablePath, "someAction");
   }
}
-------------------------------------------------------------------------

So in this code, your application starts, and if there are no command line arguments it calls the function runProcessAsAdmin(), which spawns a new process, setting the "runas" Verb which requests Admin rights and triggers the elevation prompt, and passes a command line argument of "someAction". Now when the new process starts as an admin user, it interprets the "someAction" argument and runs a different code branch.

You basically need to exploit this design-pattern so that by default, your application runs the standard code-branch, which may involve initializing some settings and opening a main form. Whenever it needs to do something different, it calls the runProcessAsAdmin function with an action verb, which runs the privileged code in a new process.

The main complications with this technique are that the spawned process cannot access the state of the original process, and so you'll need to use settings files or a database connection or some other mechanism to exchange information between the two processes. You'll also need to run initialization code in your second code-branch to start logging services, etc, and make sure they don't lock/compete with the other process.

De-elevation (admin process spawning a restricted process)
You may also be in a situation where you need to do the reverse though. That is, your main application has good reason to run as an Admin user from the beginning. But, in some instances, you need to run a process as the currently logged in user. One situation where you may need to do this is with drag-and-drop support. A process that is run as an administrator does not support receiving drag-drop events from Windows Explorer, or external processes running at a lower permission level. This is a by-design security feature which cannot be bypassed. So in this case, you may want to launch a Form that has a drop area as a process running as a logged in user. Spawning a non-admin process from an elevated process is actually a little tricky.

One solution that I've found is to use the Task Scheduler Managed Wrapper library to schedule a new task to run immediately on creation as the current logged in user authenticating with the existing identity token (so no password is required). To do this, first download and reference the above library in your project. Then use the following function:

-------------------------------------------------------------
public static void runProcessAsCurrentUser(String appFullPath, String commandLine, String schedName) {
    using (TaskService ts = new TaskService()) {
        TaskDefinition td = ts.NewTask();
        td.RegistrationInfo.Description = schedName;
        td.Triggers.Add(new RegistrationTrigger());
        td.Actions.Add(new ExecAction(appFullPath, commandLine, null));
        ts.RootFolder.RegisterTaskDefinition(schedName, 
            td,
            TaskCreation.CreateOrUpdate, 
            System.Security.Principal.WindowsIdentity.GetCurrent().Name, 
            null, TaskLogonType.InteractiveToken
        );
        ts.RootFolder.DeleteTask(schedName);
    }
}
-------------------------------------------------------------

So this creates a scheduled task, using the given application path, commandLine, and a name for the task. The Task is registered to run immediately on creation as the current user, then immediately deleted so it leaves nothing behind.

This is a bit of a hack, and not the most elegant solution, but I'm not sure if it's possible to replicate this without the Task Scheduler because of the InteractiveToken part -- i.e., you can launch a process as any user using standard .Net, but I believe you have to provide a password or prompt for one. There seems to be a way to extract the Token using unmanaged Win32 API so you don't have to use the Task Scheduler, but it seems just as convoluted.

The above method however works well and is transparent to the user. And the Task Scheduler library can come in useful for many other features in your program as well (and it's MIT licensed), so it's actually quite handy to have.


Comments

  1. Very nice post. Already had to figure this out myself previously using C (and ShellExecuteEx), but its nice to see someone else reaching the same solution....

    ReplyDelete

Post a Comment

Popular posts from this blog

Wkhtmltopdf font and sizing issues

Import Google Contacts to Nokia PC Suite

Can't delete last blank page from Word