65.9K
CodeProject 正在变化。 阅读更多。
Home

执行管理员权限函数的实用方法(提升进程权限)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (4投票s)

2011年4月7日

CPOL

3分钟阅读

viewsIcon

22144

downloadIcon

555

无需重启即可提升进程权限

MainForm.jpg RegistryFirst.jpg

Button1Click.jpg

Button1OK.jpg RegistryButton1OK.jpg

引言

最近,我的项目需要一个新的功能 - 点击一个带有 UAC 盾牌的按钮,并写入注册表 HKLM。我不希望整个项目都以管理员权限启动,而是在必要时提升权限。

我参考了许多介绍如何提升进程权限的文章。其中大部分只是演示,即以管理员权限重新启动程序。我认为这在编程中是不实用的,因为这种方法无法继承前一个程序的上下文。其他一些方法包括调用 COM 或另一个程序,但这维护另一个项目很复杂,不是吗?

本文介绍的方法的优点是,您无需重新启动程序,因此可以保留上下文。此外,最终用户不会看到程序的重新启动,并且他/她会认为这是这个程序在工作,这将是一个很好的操作体验:)。

背景

我的方法听起来很简单:调用另一个具有与调用者相同可执行路径的进程,并将参数传递给被调用的进程。

Using the Code

让我们从一个简单的技巧开始 - 向按钮添加 UAC 盾牌。请参阅最后的参考文章部分了解详情。

  • 您必须在您的项目中包含这个 using,才能导入 Win32 DLL 函数
    using System.Runtime.InteropServices;
  • 导入 Win32 DLL 函数并定义 UAC 盾牌的常量
    [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);
  • 向按钮添加 UAC 盾牌
    private void AddUACShieldToButton(Button ctrl)
    {
        ctrl.FlatStyle = FlatStyle.System; // this is a MUST-DO.
        SendMessage(ctrl.Handle, BCM_SETSHIELD, 0, 0xFFFFFFFF);
    }

现在,让我们看看项目的启动部分。这是一个根据参数表现不同的程序。

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());
    }
}

嗯,该程序有 2 个开关 - 一个通用的 GUI 模式和一个工作模式,它们通过命令行参数分隔。最终用户总是启动没有参数的程序,因此 GUI 模式启动。当用户点击 button1 时,如果该进程启动时没有管理员权限,它会调用以下方法

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;
    }
}

当调用程序达到 "workProcess.Start()" 时,将显示一个 UAC 对话框,询问您是否启动该程序。您可以看到 UAC 对话框中显示的产品名称与正在运行的程序相同,这将为最终用户带来良好的操作体验。

然后,被调用的进程以管理员权限启动和运行,这是通过 "runas" 和 UseShellExecute 参数实现的。调用进程等待被调用的进程完成,然后继续。

注意,最好将调用的进程设置为隐藏运行 (workProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden),这样就不会显示 DOS 窗口。
现在让我们看看传递给被调用进程的参数。我的参数布局是

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

参数分隔符是 ',',它可以将参数分隔成 String[]。当然,您可以定义自己的参数布局,但请确保分隔符字符的十六进制代码不会出现在参数列表中。

注意,命令行模式有 2 个条件判断。第一个 IsAdmin() (在 *worker.cs* 中实现) 是为了防止最终用户在没有管理员权限的情况下调用命令行模式。第二个 GetParent() (在 *worker.cs* 中实现) 是为了防止最终用户通过滥用参数来调用命令行模式。

好了,现在控制权进入被调用的进程。由于参数的原因,该进程进入工作模式。在以下函数中,您应该已经知道参数布局,因此您可以解释它。

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();
}

该项目使用 Visual Studio 2008。

参考文章

历史

  • 2011.04.08 - 首次发布
  • 2011.04.09 - 源代码和文章已更新
© . All rights reserved.