已加入Azure AD的Windows系统获取登录用户UPN失败问题
这个问题我之前帮人排查过,TranslateNameW在Azure AD环境下失效是有明确原因的,我来给你拆解并提供可行方案:
你当前的代码里手动构造了SAM格式的用户名L"AAD\\naga",然后调用TranslateNameW尝试转成UPN,但这个API是为**本地Active Directory(on-premise AD)**设计的——它依赖域控制器的查询服务,而加入Azure AD的设备并没有传统的本地域控制器,L"AAD"也不是一个可被解析的合法域,所以会抛出错误:
The specified domain either does not exist or could not be contacted
在本地AD环境中,Domain\Username的格式是有效的SAM名称,但Azure AD的身份模型和本地AD完全不同,TranslateNameW无法处理这种云身份场景。
针对Azure AD加入的设备,推荐两种Win32原生方案来获取用户UPN:
方案1:直接获取当前登录用户的UPN(最简便)
使用GetUserNameExWAPI,指定NameUserPrincipal格式,直接从系统身份缓存中读取当前登录用户的UPN,不需要手动构造SAM名称:
wchar_t user_upn[1024] = { 0 }; DWORD len = sizeof(user_upn) / sizeof(wchar_t); // 注意这里要按字符数计算长度 if (GetUserNameExW(NameUserPrincipal, user_upn, &len)) { wprintf(L"Successfully got UPN: %ws\n", user_upn); } else { wprintf(L"Failed to get UPN: %ld\n", GetLastError()); }
这个API对Azure AD加入的设备兼容性很好,无论是Windows 10还是Windows Server 2012 R2都能正常工作。
方案2:获取系统中所有登录用户的UPN
如果需要枚举Windows Server 2012 R2上的所有登录用户的UPN,可以通过WMI查询Win32_UserAccount类,过滤Azure AD租户域名来获取目标用户信息:
#include <windows.h> #include <wbemidl.h> #include <comdef.h> #pragma comment(lib, "wbemuuid.lib") void GetAllAADUserUPNs(const wchar_t* tenantDomain) { HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { wprintf(L"CoInitializeEx failed: 0x%lx\n", hr); return; } IWbemLocator* pLoc = NULL; hr = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc); if (FAILED(hr)) { wprintf(L"CoCreateInstance failed: 0x%lx\n", hr); CoUninitialize(); return; } IWbemServices* pSvc = NULL; hr = pLoc->ConnectServer( L"ROOT\\CIMV2", // WMI命名空间 NULL, // 用户名 NULL, // 密码 0, // 语言 NULL, // 标志 0, // 授权 0, // 上下文 &pSvc // 服务指针 ); if (FAILED(hr)) { wprintf(L"ConnectServer failed: 0x%lx\n", hr); pLoc->Release(); CoUninitialize(); return; } // 设置代理安全 blanket hr = CoSetProxyBlanket( pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE ); if (FAILED(hr)) { wprintf(L"CoSetProxyBlanket failed: 0x%lx\n", hr); pSvc->Release(); pLoc->Release(); CoUninitialize(); return; } IEnumWbemClassObject* pEnumerator = NULL; wchar_t query[512]; swprintf_s(query, L"SELECT UserPrincipalName FROM Win32_UserAccount WHERE Domain='%ws'", tenantDomain); hr = pSvc->ExecQuery( L"WQL", query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator ); if (FAILED(hr)) { wprintf(L"ExecQuery failed: 0x%lx\n", hr); pSvc->Release(); pLoc->Release(); CoUninitialize(); return; } IWbemClassObject* pclsObj = NULL; ULONG uReturn = 0; while (pEnumerator) { hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); if (uReturn == 0) break; VARIANT vtProp; VariantInit(&vtProp); hr = pclsObj->Get(L"UserPrincipalName", 0, &vtProp, 0, 0); if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { wprintf(L"AAD User UPN: %ws\n", vtProp.bstrVal); } else { wprintf(L"Failed to retrieve UPN for user\n"); } VariantClear(&vtProp); pclsObj->Release(); } // 清理资源 pEnumerator->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); } // 调用示例:替换成你的Azure AD租户域名,比如"contoso.com" // GetAllAADUserUPNs(L"contoso.com");
注意事项:
- 调用这个方法时,需要替换
tenantDomain为你的实际Azure AD租户域名(比如contoso.com); - 运行服务的账户需要有WMI查询权限,建议使用本地系统账户或具有管理员权限的账户。
不要尝试手动构造Azure AD用户的SAM格式名称(比如AzureAD\username),这种格式是系统内部使用的,TranslateNameW等传统AD API无法解析它。优先使用上面推荐的现代API或WMI方案来适配云身份场景。
内容的提问来源于stack exchange,提问作者naga

