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






4.17/5 (4投票s)
无需重启即可提升进程权限
引言
最近,我的项目需要一个新的功能 - 点击一个带有 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。
参考文章
- 在需要提升权限执行管理员任务时,向按钮添加 UAC 盾牌 (https://codeproject.org.cn/KB/vista-security/UAC_Shield_for_Elevation.aspx)
历史
- 2011.04.08 - 首次发布
- 2011.04.09 - 源代码和文章已更新