CheckTokenMembership 是判断当前进程是否属于 Administrators 组最可靠轻量的方式,通过传入 nullptr 令牌和构造的 Admin SID 调用,需手动 FreeSid;不可依赖已过时的 IsUserAnAdmin()。

CheckTokenMembership 判断当前进程是否在 Administrators 组
Windows 下判断“是否拥有管理员权限”,本质不是看 UAC 提权状态,而是看当前进程的访问令牌(access token)是否属于 BUILTIN\Administrators 组。最可靠、最轻量的方式是调用 CheckTokenMembership ——它不依赖 UAC 虚拟化、不触发提权弹窗、也不需要打开令牌句柄。
常见错误是直接检查 IsUserAnAdmin(),这个函数已被微软标记为过时,且在启用了 UAC 的系统上可能返回误导性结果(比如标准用户运行的程序即使没提权也可能返回 TRUE,因兼容性 shim 干预)。
实操要点:
-
CheckTokenMembership第一个参数传nullptr表示使用当前线程的访问令牌,无需手动调用OpenProcessToken - 第二个参数需传入
SID,必须用AllocateAndInitializeSid构造SECURITY_BUILTIN_DOMAIN_SID+DOMAIN_ALIAS_RID_ADMINS - 调用后必须用
FreeSid释放 SID,否则内存泄漏 - 该函数在 Windows XP SP2+ 全平台可用,无额外 manifest 或清单要求
完整可编译的 C++ 检查函数(含错误处理)
以下是一个最小可行实现,已通过 VS2019/2022 + Win10/11 测试,支持 Unicode 和 ANSI 编译模式:
立即学习“C++免费学习笔记(深入)”;
bool IsRunningAsAdmin() {
BOOL bIsAdmin = FALSE;
PSID pAdminSid = nullptr;
SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(&sia, 2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&pAdminSid)) {
return false;
}
if (!CheckTokenMembership(nullptr, pAdminSid, &bIsAdmin)) {
bIsAdmin = FALSE;
}
if (pAdminSid) {
FreeSid(pAdminSid);
}
return bIsAdmin != FALSE;
}
注意:CheckTokenMembership 返回值为 BOOL(非零表示调用成功),真正结果存在输出参数 &bIsAdmin 中。不能只看函数返回值来判断权限。
为什么 GetTokenInformation + TokenGroups 不推荐
有人会想到用 GetTokenInformation 获取全部组 SID,再遍历比对 DOMAIN_ALIAS_RID_ADMINS。这看似更“透明”,但实际问题不少:
- 需要先调用
OpenProcessToken,得处理TOKEN_QUERY权限和失败路径,代码膨胀明显 - 返回的
TOKEN_GROUPS结构中,每个SID_AND_ATTRIBUTES的Attributes字段必须检查是否含SE_GROUP_ENABLED—— 否则可能误判被禁用的管理员组成员 - 遍历逻辑容易漏掉嵌套组(如管理员组里再加了一个域组),而
CheckTokenMembership内部已做递归解析 - 性能差:一次完整枚举平均比
CheckTokenMembership慢 3–5 倍(尤其在域环境中)
UAC 提权后仍返回 false?检查你的 manifest
如果程序明确以 requireAdministrator 运行(manifest 中设置了 level="requireAdministrator"),但 IsRunningAsAdmin() 仍返回 false,大概率是 manifest 没生效或被覆盖:
- 确保资源文件(.rc)中包含正确的
CREATEPROCESS_MANIFEST_RESOURCE_ID类型引用 - VS 项目属性 → 链接器 → 清单文件 → “启用清单” 必须为“是”,且“UAC 执行级别”设为
requireAdministrator - 不要在代码中调用
SetThreadToken(NULL, NULL)或类似操作,会意外替换当前线程令牌 - 若用
ShellExecute以runas启动子进程,子进程的令牌是新的,但父进程的令牌不变 —— 别在父进程中误判
真正麻烦的地方往往不在 API 调用本身,而在令牌来源是否干净、manifest 是否被构建系统忽略、以及多线程环境下是否意外切换了线程令牌。每次怀疑结果不准,优先用 Process Explorer 查看进程的 “Security” 页签,直接核对 Token 的 Groups 列表。


















