目录
1、管理员权限
2、UAC虚拟化
3、将程序配置为以管理员权限启动
4、判断程序有没有管理员权限
5、以管理员权限启动目标程序
6、开机自启动程序不能设置管理员权限
在Windows Vista之前,通常人们都是使用管理员身份运行应用程序的,应用程序可以随意读取和写入系统文件和注册表。但在Vista及以后的Windows系统中,引入了UAC用户账户控制的概念,程序默认都是使用非管理员权限(标准用户权限)运行的,这给许多程序员在开发上带来了困扰,本文就来详细讲述一下Vista及以上Windows系统的管理员权限与UAC虚拟化相关的相关话题,给大家提供一个借鉴与参考。
一般情况下,程序是以两类权限运行,一类是没有管理员权限的标准用户权限,一类是管理员权限。
有些操作是不需要管理权限的,有些操作则是需要管理员权限才能完成的。那到底哪些操作才需要管理员权限呢?需要管理员权限的操作主要出现在对文件和注册表的操作上。如果在系统关键路径中创建文件或者修改系统关键路径中的文件内容,程序是需要管理员权限的,系统的关键路径有C:\Program Files、C:\Windows、C:\Windows\System32等。如果程序没有管理员权限,是不能将ini配置文件及其他文件写到这些系统关键路径中的,需要保存到不需要管理员权限的路径中,比如当前用户的appdata路径中。
如果要向注册表HKEY_LOCAL_MACHINE节点下写注册表信息,也是需要管理员权限的。如果没有管理员权限,则没法向HKEY_LOCAL_MACHINE节点写入内容,只能将信息写入到HKEY_CURRENT_USER节点下。
此外,还有一些操作也需要管理员权限,比如向系统注册控件、创建并启动服务等。
如果程序要执行需要管理员权限的操作,则程序必须要以管理员权限运行起来。比如安装包程序,需要写注册表,需要向系统注册控件,必须要以管理员权限启动的。
在Windows Vista之前,通常人们是使用管理员身份运行应用程序的,应用程序可以随意读取和写入系统文件和注册表。Windows Vista为了保持对老的应用程序的兼容性,引入了UAC虚拟化的概念。通过将需要管理员权限的文件或注册表操作重定向到每个用户配置文件内可写入位置,以改善老的应用程序的兼容性。
例如,如果某个应用程序尝试写入到C:\Program Files\Test\Settings.ini,而用户没有写入到该目录(Program Files)的权限,写入操作将会被重定向到C:\Users\Username\AppData\Local\VirtualStore\Program Files\Test\settings.ini。如果某个应用程序尝试写入到注册表的HKEY_LOCAL_MACHINE\Software\Test\位置,则会被自动重定向到HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\Software\Test或HKEY_USERS\UserSID_Classes\VirtualStore\Machine\Software\Test。
下图所示的就是Windows中文件和注册表的虚拟化流程:
UAC虚拟化技术的目的是帮助老的程序改善应用程序兼容性问题,针对Windows Vista及以上系统而设计的新应用程序不应当依赖虚拟化技术,在执行需要管理员权限的操作时,需要以管理员权限运行应用程序。
关于UAC虚拟化与重定向的详细说明,可以查看微软的官方文章:
用户帐户控制数据重定向https://docs.microsoft.com/zh-cn/previous-versions/msdn10/ee781806(v=msdn.10)
有的程序需要以管理员权限运行的,那如何让程序默认以管理员权限运行呢?其实很简单,在工程属性中配置requireAdministrator以管理员权限启动就行了。具体是在工程属性窗口中的配置属性 -> 链接器 -> 清单文件 -> UAC执行级别中选择即可,如下图所示:
有些程序是需要以管理员权限运行的,因为执行的一些操作是需要管理员权限的。比如安装包程序,要向系统注册控件,要写注册表,这些操作都是需要管理员权限的。所以程序启动时需要先判断一个当前进程是否有管理员权限,判断当前进程是否有管理员权限的代码如下:
BOOL IsRunasAdmin()
{
BOOL bElevated = FALSE;
HANDLE hToken = NULL;
// Get current process token
if ( !OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ) )
{
return FALSE;
}
TOKEN_ELEVATION tokenEle;
DWORD dwRetLen = 0;
// Retrieve token elevation information
// Only win7 or above support TOKEN_ELEVATION structure
if ( GetTokenInformation( hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRetLen ) )
{
if ( dwRetLen == sizeof(tokenEle) )
{
bElevated = tokenEle.TokenIsElevated;
}
}
CloseHandle( hToken );
return bElevated;
}
一个没有管理员权限的程序,其启动起来的其他程序,也是没有管理员权限的。一个有管理员权限的程序,其启动起来的其他程序也是有管理员权限的。那一个没有管理员权限的程序是否能够启动一个有管理员权限的程序呢?答案是肯定的,是可以做到的。一个没有管理员权限的程序,可以调用ShellExecuteEx函数传入runas参数去启动另一个程序,这样这个被启动起来的程序就是以管理员权限启动的,启动起来后是有管理员权限的。以runas方式将启动程序的代码如下:
SHELLEXECUTEINFO si;
RtlZeroMemory( &si, sizeof( SHELLEXECUTEINFO ) );
si.cbSize = sizeof(SHELLEXECUTEINFO);
si.lpFile = _T("D:\\test.exe");
//si.lpParameters = lpCmdParam;
si.nShow = SW_SHOWNORMAL;
si.lpVerb = _T("runas");
BOOL bRet = ShellExecuteEx( &si );
if ( !bRet ) // TL启动失败
{
TCHAR achLog[256] = { 0 };
// 先取lasterror值
DWORD dwLastErr = GetLastError();
_stprintf( achLog, _T("ShellExecuteEx failed, GetLastError: %d."), dwLastErr );
WriteLog( achLog );
// 再取hInstApp错误代码
int nHInsVal = (int)si.hInstApp;
if ( nHInsVal <= 32 )
{
_stprintf( achLog, _T("ShellExecuteEx failure, errcode: %d."), nHInsVal );
WriteLog( achLog );
}
}
在调用ShellExecuteEx启动程序失败时,可通过SHELLEXECUTEINFO结构体中的hInstApp字段值来获取错误码,代码如上所示,错误码主要有以下几种:
Error Code | Reason |
---|---|
SE_ERR_FNF (2) | File not found. |
SE_ERR_PNF (3) | Path not found. |
SE_ERR_ACCESSDENIED (5) | Access denied. |
SE_ERR_OOM (8) | Out of memory. |
SE_ERR_DLLNOTFOUND (32) | Dynamic-link library not found. |
SE_ERR_SHARE (26) | Cannot share an open file. |
SE_ERR_ASSOCINCOMPLETE (27) | File association information not complete. |
SE_ERR_DDETIMEOUT (28) | DDE operation timed out. |
SE_ERR_DDEFAIL (29) | DDE operation failed. |
SE_ERR_DDEBUSY (30) | DDE operation is busy. |
SE_ERR_NOASSOC (31) | File association not available. |
所谓开机自启动,是跟随Windows系统一起启动,即在Windows系统启动起来进入桌面后,程序自动启动起来。对于设置开机自启动的程序不能设置以管理员权限启动,否则会开机自启动失败。出于安全考虑,Windows系统禁止以管理员权限启动的程序在开机时自启动,因为管理员权限启动起来的程序,可以修改系统关键目录中的文件。
如果主程序确实需要管理员权限启动,则可以另外写一个exe程序比如launcher.exe,该程序不设置以管理员权限启动,可以完成开机自启动,然后在该程序启动后将主程序以runas方式启动起来,这样主程序就以管理员权限启动起来。关于以runas启动目标程序的代码,上面已经给出来了,拿过来用就行了。