【题外话】
从Vista开始,由于增加了UAC(用户账户控制,User Account Control)功能,使得管理员用户平时不再拥有能控制所有功能的管理员权限了,所以在调用很多比较重要的功能时需要提升权限来实现。有时候写的程序需要调用这种权限,那么大概就是分为运行前就提升以及运行后再提升两种,在这里整理如下。
【文章索引】
如果整个程序都需要使用管理员权限的话(甚至主界面上显示的内容都需要管理员权限才行),那么可以让程序一运行时就提升管理员权限,就如同大部分的安装程序一样。程序运行时提高权限通常采用设置manifest文件的方式,可以在项目中添加“应用程序清单文件”,添加完成后会生成如下图所示的一个文件。除此之外,也可以通过选择项目属性,然后进入“安全性”选项卡,然后选择“启用 ClickOnce 安全设置”后也会在项目的“Properties”目录下生成app.manifest文件。
在注释中很明确的说明了如果要在程序中如果需要更高的权限需要修改哪部分,不过非常好奇,这段注释并没有说明应该修改成哪种方式。
在http://blogs.msdn.com/b/winsdk/archive/2010/05/31/dealing-with-administrator-and-standard-user-s-context.aspx搜索到了这两者的区别,区别如下:
Possible requested execution level values
Value |
Description |
Comment |
asInvoker |
The application runs with the same access token as the parent process. |
Recommended for standard user applications. Do refractoring with internal elevation points, as per the guidance provided earlier in this document. |
highestAvailable |
The application runs with the highest privileges the current user can obtain. |
Recommended for mixed-mode applications. Plan to refractor the application in a future release. |
requireAdministrator |
The application runs only for administrators and requires that the application be launched with the full access token of an administrator. |
Recommended for administrator only applications. Internal elevation points are not needed. The application is already running elevated. |
区别即是,highestAvailable按当前账号能获取到的权限执行,而requireAdministrator则是以具有完整权限的管理员运行。如果当前账户是管理员账户的话,那么两者都是可以的通过提升权限来获取到管理员权限的;而如果当前账户是Guest的话,那么highestAvailable则放弃提升权限而直接运行,而requireAdministrator则允许输入其他管理员账户的密码来提升权限。
其中App1使用的是highestAvailable,而App2则使用的是requireAdministrator,可以看出在Administrator用户下都需要提升权限来运行,在关闭UAC的时候都不需要提升权限。而比如在Guest下highestAvailable放弃了提升权限,同时如果使用requireAdministrator的话则会提示类似下图的输入其他管理员账户密码的对话框:
所以,如果一个程序必须要求管理员权限才能执行或者才能执行得有意义(比如主界面上的信息需要管理员权限才能显示之类的),那么不妨设置为requireAdministrator,即使使用Guest登陆的话也需要提升管理员权限;否则也可设置为highestAvaliable。
【二、程序运行后提升权限】
如果程序默认不需要权限就能运行大部分功能,只是在个别功能上需要管理员权限的话,那么可以采用程序运行后,当用户需要提升权限的时候再提升权限重新运行程序。由于权限是按进程来的,所以如果需要提升整个程序的权限,只能以管理员权限创建进程以后再结束本程序,或者以管理员权限运行其他程序或者程序通过不同参数来执行不同功能。以管理员权限执行程序其实非常简单,只要将ProcessStartInfo对象的Verb属性设置为“runas”即可,例如如下的代码即可以管理员权限重启本程序。
1 ProcessStartInfo psi = new ProcessStartInfo();
2 psi.FileName = Application.ExecutablePath;
3 psi.Verb = "runas";
4
5 try
6 {
7 Process.Start(psi);
8 Application.Exit();
9 }
10 catch (Exception eee)
11 {
12 MessageBox.Show(eee.Message);
13 }
当然,运行其他程序也是一样的。
除此之外,我们可能还需要在这个按钮或菜单上绘制UAC盾牌的图标,其实系统已经提供了这样的方法。
1 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
2 public static extern int SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, IntPtr lParam);
3
4 public const UInt32 BCM_SETSHIELD = 0x160C;
调用的时候只要将按钮的FlatStyle设置为System,然后采用如下的代码就可以了,最后一项如果设为0的话则会取消显示UAC的盾牌图标。
1 SendMessage(button1.Handle, BCM_SETSHIELD, 0, (IntPtr)1);
不过如果要往菜单上或者WPF的Button上绘制UAC盾牌的图标就没法这样去做了,不过好在我们还可以获取到系统图标,不嫌弃的话可以用.NET自带的System.Drawing.SystemIcons.Shield,其实好多软件用的就是这个图标,原图如下(32×32):
当然,也可以通过DllImport的方式从系统中获取系统内置的图标,其中UAC盾牌的图标的ID是77,代码如下。
1 [DllImport("shell32.dll", SetLastError = false)]
2 public static extern Int32 SHGetStockIconInfo(SHSTOCKICONID siid, SHGSI uFlags, ref SHSTOCKICONINFO psii);
3
4 public enum SHSTOCKICONID : uint
5 {
6 SIID_SHIELD = 77
7 }
8
9 [Flags]
10 public enum SHGSI : uint
11 {
12 SHGSI_ICON = 0x000000100,
13 SHGSI_SMALLICON = 0x000000001
14 }
15
16 [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
17 public struct SHSTOCKICONINFO
18 {
19 public UInt32 cbSize;
20 public IntPtr hIcon;
21 public Int32 iSysIconIndex;
22 public Int32 iIcon;
23
24 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
25 public string szPath;
26 }
然后如下调用就可以将UAC盾牌的图标设置到菜单上了:
1 SHSTOCKICONINFO iconInfo = new SHSTOCKICONINFO();
2 iconInfo.cbSize = (UInt32)System.Runtime.InteropServices.Marshal.SizeOf(iconInfo);
3 SHGetStockIconInfo(SHSTOCKICONID.SIID_SHIELD, SHGSI.SHGSI_ICON | SHGSI.SHGSI_SMALLICON, ref iconInfo);
4 Icon icon = Icon.FromHandle(iconInfo.hIcon);
5
6 menu.Image = icon.ToBitmap();
图中menu1是使用的System.Drawing.SystemIcons.Shield,menu2使用的通过shell32.dll获取到的图标,button1是使用的SendMessage直接显示的UAC的图标。
当然,还是应该判断一下系统的版本的,确保系统是Vista及以后的版本,否则就不需要提升权限了。判断是否是Vista只需要判断系统主版本号是否大于等于6就可以了,例如以下的代码。
1 Boolean afterVista = (Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Major >= 6);
如果要判断当前是否以管理员身份运行,只需引用“System.Security.Principal”这个命名空间,然后就可以通过如下的代码获取当前是否以管理员在运行。
1 WindowsIdentity identity = WindowsIdentity.GetCurrent();
2 WindowsPrincipal principal = new WindowsPrincipal(identity);
3 Boolean isRunasAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
除了获取当前是否是以管理员运行,还可以通过DllImport的方式获取到当前用户是否是管理员用户以及当前进程是否提升了权限(仅限Vista及以上的版本)等等,详情可以见相关链接2中的代码。
【相关链接】
- 编写C#程序让其在Win7 下以管理员权限运行:http://www.cr173.com/html/11557_1.html
- UAC self-elevation (CSUACSelfElevation):http://code.msdn.microsoft.com/windowsdesktop/CSUACSelfElevation-644673d3
- How to add an uac shield icon to a MenuItem:http://www.peschuster.de/2011/12/how-to-add-an-uac-shield-icon-to-a-menuitem/