如果程序不是以管理员身份运行,操作本地文件会提示:System.UnauthorizedAccessException异常
Vista 和 Windows 7 操作系统为了加强安全,增加了 UAC(用户账户控制) 的机制,如果 UAC 被打开,用户即使是以管理员权限登录,其应用程序默认情况下也无法对系统目录,系统注册表等可能影响系统运行的设置进行写操作。这个机制大大增强了系统的安全性,但对应用程序开发者来说,我们不能强迫用户去关闭UAC,但有时我们开发的应用程序又需要以 Administrator 的方式运行,即 Win7 中 以 as administrator 方式运行,那么我们怎么来实现这样的功能呢?
我们在 win7 下运行一些安装程序时,会发现首先弹出一个对话框,让用户确认是否同意允许这个程序改变你的计算机配置,但我们编写的应用程序默认是不会弹出这个提示的,也无法以管理员权限运行。本文介绍了 C# 程序如何设置来提示用户以管理员权限运行。
首先在项目中增加一个 Application Manifest File
默认的配置如下:
< asmv1:assembly manifestVersion ="1.0" xmlns ="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv1 ="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2 ="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" >
< assemblyIdentity version ="1.0.0.0" name ="MyApplication.app" />
< trustInfo xmlns ="urn:schemas-microsoft-com:asm.v2" >
< security >
< requestedPrivileges xmlns ="urn:schemas-microsoft-com:asm.v3" >
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
If you want to utilize File and Registry Virtualization for backward
compatibility then delete the requestedExecutionLevel node.
-->
< requestedExecutionLevel level ="asInvoker" uiAccess ="false" />
</ requestedPrivileges >
</ security >
</ trustInfo >
</ asmv1:assembly >
我们可以看到这个配置中有一个 requestedExecutionLevel 项,这个项用于配置当前应用请求的执行权限级别。这个项有3个值可供选择,如下表所示:
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. |
asInvoker : 如果选这个,应用程序就是以当前的权限运行。
highestAvailable: 这个是以当前用户可以获得的最高权限运行。
requireAdministrator: 这个是仅以系统管理员权限运行。
默认情况下是 asInvoker。
highestAvailable 和 requireAdministrator 这两个选项都可以提示用户获取系统管理员权限。那么这两个选项的区别在哪里呢?
他们的区别在于,如果我们不是以管理员帐号登录,那么如果应用程序设置为 requireAdministrator ,那么应用程序就直接运行失败,无法启动。而如果设置为 highestAvailable,则应用程序可以运行成功,但是是以当前帐号的权限运行而不是系统管理员权限运行。如果我们希望程序在非管理员帐号登录时也可以运行(这种情况下应该某些功能受限制) ,那么建议采用 highestAvailable 来配置。
关于requestedExecutionLevel 设置的权威文档请参考下面链接:
Create and Embed an Application Manifest (UAC)
下面是修改后的配置文件:
< asmv1:assembly manifestVersion ="1.0" xmlns ="urn:schemas-microsoft-com:asm.v1"
xmlns:asmv1 ="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2 ="urn:schemas-microsoft-com:asm.v2"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" >
< assemblyIdentity version ="1.0.0.0" name ="MyApplication.app" />
< trustInfo xmlns ="urn:schemas-microsoft-com:asm.v2" >
< security >
< requestedPrivileges xmlns ="urn:schemas-microsoft-com:asm.v3" >
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
If you want to utilize File and Registry Virtualization for backward
compatibility then delete the requestedExecutionLevel node.
-->
< requestedExecutionLevel level ="requireAdministrator" uiAccess ="false" />
</ requestedPrivileges >
</ security >
</ trustInfo >
</ asmv1:assembly >
下面再来看看程序如何知道当前运行在系统管理员权限还是非系统管理员权限:
using System.Security.Principal
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
UAC权限的提权与降权
原文地址:http://www.codeproject.com/KB/vista-security/VistaElevator.aspx
有空就回复一个哈
源代码分享在优快云上的链接:http://download.youkuaiyun.com/detail/wsyjz/3934006 C++代码
演示程序分享在优快云上的链接:http://download.youkuaiyun.com/detail/wsyjz/3934004
若是积分不够,可以在原文地址中找到下载链接。
另外添加2个相关文章(只有提权,没有提到降低权限):
UAC 编程入门 1:进程Mandatory Level检查和自我提升权限 C#代码,英文 -->VB.Net代码
[C#]Enable UAC Shield icons and run as administrator 比较简短的中文说明,C#代码
欢迎转载,但最好请注明 Jero 翻译。
- 已提权、已经提升权限的进程——可以理解为使用管理员权限运行的。
- 未提权、没有提升权限的进程——可以理解为使用 非 管理员权限(既普通用户权限)运行的。
UAC机制是由Vista引出,并且由于Windows 7的内核与Vista框架相同,所以本文中提到的Vista的UAC权限相关对于Windows 7完全适用。
正文中斜体都是我添加的说明。
正文
当你为Windows Vista开发程序的时候,你常常遇到一个问题:如何使用编程技巧来控制一个应用程序的执行权限(是否从普通用户提权到管理员,或是从管理员降级到普通用户)。当用户运行一个程序的时候,它的执行权限是由应用程序清单(manifest)中的requestedExecutionLevel属性值所决定的,以及Vista/7的用户帐户控制(UAC)根绝需要来采取适当的行为(例如弹出UAC提权确认的窗口,等)。然而,如果一个程序需要执行一个新的不同权限的程序应该如何?
例如:
- 一个程序普通运行的时候是采用非管理员的权限(没有经过提升权限,普通用户的权限),然后在运行过程中检测到了程序有新版本可用。为了能够让自身更新,它需要启动一个单独的进程来提升自身的权限,这样才能正确执行升级。在这种情况下,一个普通用户的权限需要建立一个新的提升到至少是管理员权限的程序。
- 大多数安装程序让用户选择在安装结束以后运行其程序。安装程序是以提权过后的进程运行的,但是新的程序需要以普通的,未经过提升的权限来执行。
译者注:简单理解就是以普通用户的权限运行了A程序,然后用A程序运行B程序,但是B程序被提升到管理员权限,而不是继承A程序的权限(如果打开UAC的话,这个提权过程系统会弹出窗口进行确认的)
或是:C程序是通过管理员权限执行的,然后用C程序运行D程序,但是D程序不继承C程序的管理员权限,而是降级到一个普通用户的权限。(对于降级,系统是不会提示的)
微软已经提供了一个相对简单的方法来完成上面提到的第一个任务(即A程序使B程序提升权限),通过指定ShellExecuteEx API的一个参数为“runas”。但是,由于某些原因,微软并没有提供一个相似的方法来执行一个相反的过程:从一个已提权的程序来启动一个未提权的程序(即C程序使D程序降级)。这篇文章中,我将会展示如何解决这个问题,以及相关的问题。
检测当前程序的执行权限
首先,如何检测一个程序当前的执行权限?源码中的 VistaTools.cxx 文件包含的两个函数对这个问题得到了答案。
第一个函数是 GetElevationType(),它使用了 Win32 API GetTokenInformation() 来获得当前程序其令牌(token)的权限类型,它所可能返回的值为:
- TokenElevationTypeDefault - 用户没有使用一个分隔的令牌(权限机制)。这个值说明了UAC已经禁用,或者是程序是有一个非管理员组的普通用户运行的。
- TokenElevationTypeFull - 程序已经获得提权(至少是管理员权限的)
- TokenElevationTypeLimited - 程序没有经过提权(普通权限运行的)
注意:只有当UAC开启的时候,后面2个值才能返回,而且当前用户是管理员组中的一员(就是这个用户有一个分隔的令牌 <既是当前用户是管理员,可以执行普通用户权限的程序,也可以运行管理员权限的程序>)
第二个函数是 IsElevated() 它也调用了 GetTokenInformation() API,但是它获取的是TokenElevation 类的信息。它只能返回下面两项之一:
- S_OK - 当前进程已经提权过。这个数值表明了UAC已经开启,而且当前程序是由管理员提权的;或者UAC已经禁用了,但是当前用户是管理员组中的一员。
- S_OK - 当前进程已经提权过。这个数值表明了UAC已经开启,而且当前程序是由管理员提权的;或者UAC已经禁用了,但是当前用户是管理员组中的一员。
- S_FALSE - 当前进程没有经过提权(受限的)。这个数值表明了UAC已经开启了,而且当前进程只是普通的执行,没有经过提权;或是UAC已经禁用了,进程只是由一个普通用户执行。
使用这两个函数,一个程序可以确定它执行的(权限的)确切情况。
运行一个提权的程序
如果一个未提权的程序需要运行一个提权的程序,所有它需要做的是调用 ShellExecuteEx() API ,然后指示一个参数为"runas",源码中的函数 RunElevated() 就是这样达到提权目的的:
BOOL
RunElevated( HWND hwnd,
LPCTSTR pszPath,
LPCTSTR pszParameters = NULL,
LPCTSTR pszDirectory = NULL )
{
SHELLEXECUTEINFO shex;
memset( &shex, 0, sizeof( shex) );
shex.cbSize = sizeof( SHELLEXECUTEINFO );
shex.fMask = 0;
shex.hwnd = hwnd;
shex.lpVerb = _T("runas");
shex.lpFile = pszPath;
shex.lpParameters = pszParameters;
shex.lpDirectory = pszDirectory;
shex.nShow = SW_NORMAL;
return ::ShellExecuteEx( &shex );
}
从一个已提权的进程来执行一个没有提权的进程
从相反的方向(从一个已经提权的进程来执行一个没有提权的进程)变得非常复杂。如果父进程已经提权了的,那么它所执行的所有子程序都将继承它的已经提权的权限,而且无论子程序的清单(manifest)中的 requestedExecutionLevel 属性值是如何指定的。由于某些原因,微软并没有提供一个API来直接降低进程的权限,所以我们需要想出一个间接的办法来达到目的。
这个诀窍是使用Windows Vista自带的任务计划程序(Task Scheduler)来建立以低权限执行的任务,并且要求这个任务建立以后应当立即执行。最终的结果是相同的,犹如进程直接启动一样。
本文的源码包含的函数 RunAsStdUser() 正是这么做的。它是基于MSDN样本“Registration Trigger Example(注册任务以后立即执行的例子)”,而且它涉及了十几个COM接口与任务计划程序交流,以及设立一个任务通过普通(未提权)的权限。我没有将这些源码的函数包含在这里,因为那是相当乏味的;你可以在 VistaTools.cxx 文件中找到它。
通过事实来证明以上
这个演示程序(VistaElevator)说明了如何通过编程方法来执行提权和降权。当你运行它的时候,它显示了一个对话框来展示进程运行权限的相关信息,通过 GetElevationType() 和 IsElevated() 这两个函数来获取(函数说明看上面)。它也提供了你两个如何重启进程的选择,提权或是取消。基于你的选择,VistaElevator 调用 RunElevated() 或是 RunAsStdUser() 函数 (依旧看上面) 来重启自身,并在要求的权限下。
作者:Andrei Belogortseff