管理员安装程序中如何始终获取当前用户的LocalAppData路径?
解决方案
一、始终获取原始用户的LocalAppData路径
当程序通过UAC提升后,进程运行在管理员上下文,直接调用SHGetKnownFolderPath会返回管理员账户的路径。要获取触发UAC的原始用户路径,需通过模拟原始用户会话实现,步骤如下:
- 打开当前进程的令牌,获取链接到原始用户的令牌(
TokenLinkedToken属性) - 模拟该原始用户,调用
SHGetKnownFolderPath获取路径 - 取消模拟并释放资源
示例代码(C++):
#include <windows.h> #include <shlobj.h> #include <sddl.h> WCHAR* GetOriginalUserLocalAppData() { HANDLE hCurrentToken = NULL; HANDLE hLinkedToken = NULL; WCHAR* pszPath = NULL; // 打开当前进程令牌 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, &hCurrentToken)) { goto Cleanup; } // 获取链接令牌(对应原始用户) DWORD dwSize = sizeof(HANDLE); if (!GetTokenInformation(hCurrentToken, TokenLinkedToken, &hLinkedToken, dwSize, &dwSize)) { // 无链接令牌,说明是管理员用户自身提升,直接获取当前路径 SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &pszPath); goto Cleanup; } // 模拟原始用户 if (ImpersonateLoggedOnUser(hLinkedToken)) { SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &pszPath); RevertToSelf(); } Cleanup: if (hCurrentToken) CloseHandle(hCurrentToken); if (hLinkedToken) CloseHandle(hLinkedToken); return pszPath; }
注意:使用完返回的路径后,需调用CoTaskMemFree释放内存。
二、判断当前用户是否为跨账号UAC提升(原本非管理员)
要判断是否是普通用户输入管理员凭据完成的提升,可通过比较当前进程令牌与原始用户令牌的SID是否不同实现:
- 获取当前进程令牌的用户SID
- 获取链接令牌的用户SID
- 对比两个SID,若不同则说明是跨账号提升(原本用户非管理员)
示例代码(C++):
BOOL IsCrossAccountElevation() { HANDLE hCurrentToken = NULL; HANDLE hLinkedToken = NULL; BOOL bResult = FALSE; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hCurrentToken)) { goto Cleanup; } // 获取链接令牌 DWORD dwSize = sizeof(HANDLE); if (!GetTokenInformation(hCurrentToken, TokenLinkedToken, &hLinkedToken, dwSize, &dwSize)) { // 无链接令牌,说明是管理员自身提升,不是跨账号 goto Cleanup; } // 获取当前用户SID TOKEN_USER* pCurrentUser = NULL; dwSize = 0; GetTokenInformation(hCurrentToken, TokenUser, NULL, 0, &dwSize); pCurrentUser = (TOKEN_USER*)LocalAlloc(LPTR, dwSize); if (!GetTokenInformation(hCurrentToken, TokenUser, pCurrentUser, dwSize, &dwSize)) { goto Cleanup; } // 获取原始用户SID TOKEN_USER* pLinkedUser = NULL; dwSize = 0; GetTokenInformation(hLinkedToken, TokenUser, NULL, 0, &dwSize); pLinkedUser = (TOKEN_USER*)LocalAlloc(LPTR, dwSize); if (!GetTokenInformation(hLinkedToken, TokenUser, pLinkedUser, dwSize, &dwSize)) { goto Cleanup; } // 对比SID if (!EqualSid(pCurrentUser->User.Sid, pLinkedUser->User.Sid)) { bResult = TRUE; } Cleanup: if (hCurrentToken) CloseHandle(hCurrentToken); if (hLinkedToken) CloseHandle(hLinkedToken); if (pCurrentUser) LocalFree(pCurrentUser); if (pLinkedUser) LocalFree(pLinkedUser); return bResult; }
如果该函数返回TRUE,则说明当前是普通用户通过输入管理员凭据完成的UAC提升,此时可提示用户选择其他安装位置(比如原始用户的LocalAppData或公共目录)。
内容的提问来源于stack exchange,提问作者Michael Chourdakis
相关产品推荐
相关产品推荐

