有时我们需要判断进程是否以管理员权限运行,比如在运行安装包时需要安装包进程以管理员权限运行,因为安装包将执行写注册表、注册组件等需要管理员权限的操作。如果没有申请到管理员权限,这些需要管理员权限的操作都会执行失败,则会导致安装失败。以QQ7.1安装包为例,如果当前以标准用户登录到系统中,并且UAC关闭,双击运行时将申请不到管理员权限,QQ会弹出如下的提示框:
我们的TL安装程序可以参考QQ的做法,避免出现没有管理员权限导致安装失败的问题,即如果没有申请到管理员权限,则直接弹出如上类似的提示。要弹出提示,则要判断当前安装程序的进程是否以管理员权限运行。那应该如何判断呢?
一、判断函数的实现
经查阅相关资料得知,调用GetTokenInformation函数,获取TOKEN_ELEVATION结构体信息,通过结构体中的TokenIsElevated字段就能判断出来,相关的代码如下:
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
if ( GetTokenInformation( hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRetLen ) )
{
if ( dwRetLen == sizeof(tokenEle) )
{
bElevated = tokenEle.TokenIsElevated;
}
}
CloseHandle( hToken );
return bElevated;
}
二、管理员权限与当前用户的类型、UAC开关的关系
对于需要管理员权限的程序,可以通过内嵌manifest文件的方式,设置requireAdministrator(需要管理员权限),这样在程序启动时会申请管理员权限。在VS2010中,可以在工程的属性中进行配置,如下图所示:
程序在什么情况下可以申请到管理员权限,在什么情况下申请不到,在这里就各种场景简单的说明一下。考虑当前的登入的用户类型、UAC的打开与关闭、程序本身有没有设置requireAdministrator属性。
1、UAC打开
此种情况只讨论设置requireAdministrator属性如何申请到管理员权限的情形。对于没有设置requireAdministrator属性的程序,肯定是以非管理员权限运行的。
(1)登录的是超级管理员Administrator
默认情况下,超级管理员Administrator是禁用的,可以通过这样的途径来开启:右键计算机 ->管理 ->系统工具 ->本地用户和组 ->用户 ->右键Administrator ->属性 ->取消账户禁用 ->注销(不行就重启)->登陆Administrator即可。超级管理员在用户管理中是可以重命名的。
对于设置了requireAdministrator属性的程序,启动时能申请到管理员权限。由于Administrator是超级管理员,权限最高的用户,所以在提权的时候不会弹出UAC提示窗口。
(2)登录的是管理员(非超级管理员Administrator)
(3)登录的是标准用户(非管理员)
输入管理员密码后,才能正常的启动程序。
2、UAC关闭
UAC关闭,应该只是关闭了UAC提示。此种情况设置了requireAdministrator属性的程序和没有设置requireAdministrator属性的程序都要嫁衣说明。
(1)登录的是超级管理员Administrator或者管理员(普通管理员)
当然还有很多示例可以拿过来加以验证。
(2)登录的是标准用户
由上可知,程序是申请不到管理员权限的,程序是可以启动的,但是所有执行需要管理员权限的操作都会返回失败。这就是QQ在申请不到管理员权限时弹出无法安装的提示的原因所在了。
三、IsRunasAdmin函数的说明
BOOL GetProcessElevation(TOKEN_ELEVATION_TYPE* pElevationType, BOOL* pIsAdmin)根据文中的意思,通过传出参数pIsAdmin可以得知进程是否以管理员权限运行。经测试,在标准用户登录且UAC打开时是有问题的,经调试,每次都会走到如下的分支:
{
HANDLE hToken = NULL;
DWORD dwSize;
// Get current process token
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
return(FALSE);
BOOL bResult = FALSE;
// Retrieve elevation type information
if (GetTokenInformation(hToken, TokenElevationType,
pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize)) {
// Create the SID corresponding to the Administrators group
byte adminSID[SECURITY_MAX_SID_SIZE];
dwSize = sizeof(adminSID);
CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, &adminSID,
&dwSize);
if (*pElevationType == TokenElevationTypeLimited) {
// Get handle to linked token (will have one if we are lua)
HANDLE hUnfilteredToken = NULL;
GetTokenInformation(hToken, TokenLinkedToken, (VOID*)
&hUnfilteredToken, sizeof(HANDLE), &dwSize);
// Check if this original token contains admin SID
if (CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin)) {
bResult = TRUE;
}
// Don't forget to close the unfiltered token
CloseHandle(hUnfilteredToken);
} else {
*pIsAdmin = IsUserAnAdmin();
bResult = TRUE;
}
}
// Don't forget to close the process token
CloseHandle(hToken);
return(bResult);
}
*pIsAdmin = IsUserAnAdmin();这样就有问题,IsUserAnAdmin是判断当前登录用户是否是管理员用户。而在标准用户登录且UAC打开时,启动requireAdministrator的程序,会弹出输入管理员密码的提示框,也可以以管理员权限运行的,即使当前登录的用户不是管理员用户。
四、判断其他的进程是否以管理员权限运行
上面的代码稍加改动一下,就可以实现判断其他进程是否以管理员权限运行了。假设我们知道目标进程的进程id(在测试时,我们可以到任务管理器中查看目标进程的进程id),则可以调用OpenProcess得到进程句柄,然后再将这个句柄传递给OpenProcessToken。相关代码如下所示:
DWORD dwPid = 2337; // 目标进程的进程id
HANDLE hProcess = ::OpenProcess( /*PROCESS_ALL_ACCESS*/PROCESS_QUERY_INFORMATION, FALSE, dwPid );
if ( hProcess == NULL )
{
strTip.Format( _T("OpenProcess to get the process handle failed, possible reason: the process id doesn't exsit, GetLastError: %d"), GetLastError() );
AfxMessageBox( strTip );
return;
}
BOOL bRunAsAdmin = IsRunasAdmin( hProcess );
if ( bRunAsAdmin )
{
strTip.Format( _T("Pid(%d) run as admin!"), dwPid );
}
else
{
strTip.Format( _T("Pid(%d) don't run as admin!"), dwPid );
}
AfxMessageBox( strTip );
BOOL IsRunasAdmin( HANDLE hProcess )注意,在调用OpenProcess获取进程句柄时,不要传递全权限的参数PROCESS_ALL_ACCESS,传递较低权限的参数PROCESS_QUERY_INFORMATION,避免因为程序的权限不够导致OpenProcess函数执行失败。至于为什么要使用PROCESS_QUERY_INFORMATION参数,可以参见MSDN中说明:
{
BOOL bElevated = FALSE;
HANDLE hToken = NULL;
CString strTip;
// Get target process token
if ( !OpenProcessToken( hProcess/*GetCurrentProcess()*/, TOKEN_QUERY, &hToken ) )
{
strTip.Format( _T("OpenProcessToken failed, GetLastError: %d"), GetLastError() );
AfxMessageBox( strTip );
return FALSE;
}
TOKEN_ELEVATION tokenEle;
DWORD dwRetLen = 0;
// Retrieve token elevation information
if ( GetTokenInformation( hToken, TokenElevation, &tokenEle, sizeof(tokenEle), &dwRetLen ) )
{
if ( dwRetLen == sizeof(tokenEle) )
{
bElevated = tokenEle.TokenIsElevated;
}
}
else
{
strTip.Format( _T("GetTokenInformation failed, GetLastError: %d"), GetLastError() );
AfxMessageBox( strTip );
}
CloseHandle( hToken );
return bElevated;
}
因为获取的进程句柄是给OpenProcessToken函数使用的,所以根据MSDN中的说明,使用PROCESS_QUERY_INFORMATION参数。