Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Practical Means to Perform Functions with Administrator Privilege (Elevate Process Privilege)

0.00/5 (No votes)
10 Apr 2011 1  
Elevate process privilege without restarting it

MainForm.jpg RegistryFirst.jpg

Button1Click.jpg

Button1OK.jpg RegistryButton1OK.jpg

Introduction

Recently, I needed a new functionality in my project - clicking a button marked with a UAC shield and writing to registry HKLM. I don't expect the whole project to start with Administrator privilege, but to be elevated when necessary.

I referred to many articles introducing how to elevate process privilege. Most of them are just demos which restart the program with Administrator privilege. I suppose this is not practical in programming because this method cannot inherit the context of the previous program. Some other methods include invoking COM or another program, but it's complex to maintain another project, isn't it?

The advantage of my method introduced in this article is that you would not need to restart your program so that the context can be kept. Moreover, the end user will not see a restarting of the program, and he/she will consider it is THIS program which does the work, which would be a good operation experience :).

Background

My method sounds quite simple: to invoke another process which has the same executable path with the invoking one, and pass arguments to the invoked one.

Using the Code

Let's begin with a simple trick - adding an UAC shield to the button. Please refer to the final Reference Article section for details.

  • You must include this using in your project in order to import Win32 DLL functions:
    using System.Runtime.InteropServices;
  • Import Win32 DLL functions and define constants for the UAC shield:
    [DllImport("user32")]
    public static extern UInt32 SendMessage(IntPtr hWnd, UInt32 msg, 
    	UInt32 wParam, UInt32 lParam);
    private const int BCM_FIRST = 0x1600;
    private const int BCM_SETSHIELD = (BCM_FIRST + 0x000C);
  • Add a UAC shield to the button:
    private void AddUACShieldToButton(Button ctrl)
    {
        ctrl.FlatStyle = FlatStyle.System; // this is a MUST-DO.
        SendMessage(ctrl.Handle, BCM_SETSHIELD, 0, 0xFFFFFFFF);
    }

Now, let's see the starting part of the project. It is a program which behaves differently according to the arguments.

static void Main(String[] args)
{
    if (false == args.Length.Equals(0))
    {
        // there are arguments in command line, start the worker mode
        if(false == worker.IsAdmin())
        {
            MessageBox.Show("Bad syntax for the program.", 
		"ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        Process parent = worker.GetParent(Process.GetCurrentProcess());
        if (Application.ExecutablePath != parent.MainModule.FileName)
        {
            MessageBox.Show("You cannot start this program manually.", 
		"ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        switch (Convert.ToUInt32(args[0]))
        {
            case 0:
                worker.WriteHKLMSoftware(args[1]);
                break;
            case 1:
                worker.UpdateDNARegistry();
                break;
        }
    }
    else
    {
        // no argument in command line, start the GUI mode
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

Well, the program has 2 switches - a common GUI mode and a worker mode, which is separated by command line arguments. The end user will always starts the program with no arguments, so the GUI mode starts. When the user clicks button1, it calls the following method, if the process starts without Administrator privilege:

public static bool RunWorkerInstance(String exePath, String arg)
{
    Process workProcess = new Process();
    workProcess.StartInfo.UseShellExecute = true;
    workProcess.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
    workProcess.StartInfo.FileName = exePath;
    workProcess.StartInfo.Verb = "runas";
    workProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
    workProcess.StartInfo.Arguments = arg;
    try
    {
        workProcess.Start();
        workProcess.WaitForExit();
        workProcess.Close();
       return true;
    }
    catch ()
    {
        return false;
    }
}

When the invoking program reaches "workProcess.Start()", a UAC dialog will be shown to ask you whether to start the program. You can see the product name shown in the UAC dialog is the same with the running program, which will be a good operation experience to the end user.

And then, the invoked process starts and operates with Administrator privilege, which is achieved by the "runas" and UseShellExecute parameters. The invoking process waits for the invoked process to finish and then goes on.

Note, better set the invoked process to run hidden (workProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden) so that no DOS window will be shown.
Now let's see the arguments passed to the invoked process. My arguments layout is:

"{CommandWord} {arg1},{arg2},{arg3}..."

The arguments splitter is ',' which can separate the arguments into String[]. Of course, you can define your own arguments layout, but make sure the splitter character hex will NOT appear in the arguments list.

Note, the command line mode has 2 condition judgements. The first IsAdmin() (implemented in the worker.cs) is to prevent the end user invoking the command line mode without Administrator privilege. The second GetParent() (implemented in the worker.cs) is to prevent the end user invoking the command line mode by abusing arguments.

Well, now the control goes into the invoked process. The process goes into worker mode due to arguments. In the following function, you should already know the arguments layout, so you can interpret it.

public static void WriteHKLMSoftware(String args)
{
    String[] options = args.Split(CommandSplitter);
    String ServerIP = options[0];
    String ServerPort = options[1];
    // when you are in this function, you must have Administrator privilege
    RegistryKey software = Registry.LocalMachine.OpenSubKey
	("SOFTWARE", RegistryKeyPermissionCheck.ReadWriteSubTree);
    RegistryKey product = software.CreateSubKey(RegistryProductName);
    product.SetValue(RegistryServerIP, ServerIP);
    product.SetValue(RegistryServerPort, ServerPort);
    product.Close();
    software.Close();
}

The project is in Visual Studio 2008.

Reference Articles

History

  • 2011.04.08 - First published
  • 2011.04.09 - Source code and article updated

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here