如何擷取Windows系統登陸使用者名稱
關鍵字 :擷取登陸使用者名稱 GetUserName WTSQuerySessionInformation
一般用 GetUserName(或 GetUserNameEx )函數可得到當前登陸登陸使用者名稱(但不總會得到,下面會分析),此系統函數在Win95、WinNT 及以後所有作業系統中都可用。代碼如下:
BOOL CSecurityTool::GetCurrProcessUser(CString& strName)
{
BOOL bRet(TRUE);
strName = _T("");
DWORD dwSize = MAX_PATH;
TCHAR *pszName = new TCHAR[dwSize];
if (!GetUserName(pszName, &dwSize))
{
delete[] pszName;
pszName = new TCHAR[dwSize];
bRet = GetUserName(pszName, &dwSize);
}
strName = pszName;
delete[] pszName;
return bRet;
}
此函數目的準確來說是擷取當前線程的使用者名稱(MSDN語:retrieves the user name of the current thread)。如果是NT service(NT服務程式)將此進程啟動,得到的結果是NT Service進程的使用者名稱,即“SYSTEM”,而不是登陸使用者名稱;同理,如果此進程是通過CreateProcessAsUser建立的,GetUserName擷取的使用者將是“AsUser”的使用者名稱。另外,如果當前線程正impersonate其他使用者環境(用函數ImpersonateLoggedOnUser可達到此目的),它擷取的將是其他使用者名稱。因此,此函數只能在特定環境中才可以擷取登陸使用者名稱。那如何不因進程本身運行環境的不同,而準確地擷取登陸使用者名稱呢? 我們首先看看Windows XP作業系統,它提供了WTSQuerySessionInformation函數,這個函數可以擷取會話(session)相關資訊,其中一個用 途是擷取會話的登陸使用者。代碼如下:
BOOL CSecurityTool::GetLogUserXP(CString& strName)
{
BOOL bRet = FALSE;
strName = _T(""); //for xp or above
TCHAR *szLogName = NULL;
DWORD dwSize = 0;
if (WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSUserName, &szLogName, &dwSize))
{
strName = szLogName;
WTSFreeMemory(szLogName);
bRet = TRUE;
}
return bRet;
}
如果使用者還沒有登陸,擷取的使用者名稱將為空白(譬如在NT service程式中)。雖然MSDN中指明WTSQuerySessionInformation可以在win2000 pro 中使用,但由於安裝win2000 professional時,terminal service是沒有安裝的(除非用特殊方法如第三方工具可以安裝terminal service) ,所以調用此函數會失敗,需要尋找其他方法。
再看Win2000:查閱了許多資料,未能發現在Win2000中直接擷取登陸使用者名稱的系統函數,看來只有曲線救國了。
由於Explorer.exe進程的使用者肯定是當前登 陸使用者,所以擷取到它的使用者名稱就等於擷取到登陸使用者名稱。具體實現:首先枚舉系統所有進程,找到Explorer.exe進程ID,然後通過ID擷取此 進程的令牌(Token),再擷取令牌的使用者資訊,即為登陸使用者名稱。代碼如下:
//擷取win2000登陸使用者
BOOL CSecurityTool::GetLogUser2K(CString& strName)
{
BOOL bRet = FALSE;
HANDLE hSnapshot = NULL;
strName = _T("");
__try
{
// Get a snapshot of the processes in the system
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == NULL)
{
__leave;
}
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32); // Find the "System" process
BOOL fProcess = Process32First(hSnapshot, &pe32);
while (fProcess)
{
if (lstrcmpi(pe32.szExeFile, TEXT("explorer.exe")) == 0)
{
TCHAR szUserName[MAX_PATH];
if (GetProcessUser(pe32.th32ProcessID, szUserName, MAX_PATH))
{
bRet = TRUE;
strName = szUserName;
}
break;
}
fProcess = Process32Next(hSnapshot, &pe32);
}
if (!fProcess)
{
__leave; // Didn''t find "System" process
}
}
__finally
{
// Cleanup the snapshot
if (hSnapshot != NULL) CloseHandle(hSnapshot);
}
return bRet;
}
//擷取進程的使用者名稱
BOOL CSecurityTool::GetProcessUser(DWORD dwProcessID, TCHAR *szUserName, DWORD nNameLen)
{
BOOL fResult = FALSE;
HANDLE hProc = NULL;
HANDLE hToken = NULL;
TOKEN_USER *pTokenUser = NULL;
__try {
// Open the process with PROCESS_QUERY_INFORMATION access
hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessID);
if (hProc == NULL) { __leave; }
fResult = OpenProcessToken(hProc, TOKEN_QUERY, &hToken);
if (!fResult)
{
__leave;
}
DWORD dwNeedLen = 0;
fResult = GetTokenInformation(hToken,TokenUser, NULL, 0, &dwNeedLen);
if (dwNeedLen > 0)
{
pTokenUser = (TOKEN_USER*)new BYTE[dwNeedLen];
fResult = GetTokenInformation(hToken, TokenUser, pTokenUser, dwNeedLen, &dwNeedLen);
if (!fResult)
{
__leave;
}
}
else
{
__leave;
}
SID_NAME_USE sn;
TCHAR szDomainName[MAX_PATH];
DWORD dwDmLen = MAX_PATH;
fResult = LookupAccountSid(NULL, pTokenUser->User.Sid, szUserName, &nNameLen, szDomainName, &dwDmLen, &sn);
}
__finally
{
if (hProc)
::CloseHandle(hProc);
if (hToken)
::CloseHandle(hToken);
if (pTokenUser)
delete[] (char*)pTokenUser;
return fResult;
}
}
熟悉win2000系統的同仁肯定會發現此方法存在缺陷:eXPlorer.exe進程可能不存在(被使用者kill掉或自己中斷了),這時候這個方法就擷取不到登陸使用者名稱。但在沒有更好方法前,只能將就。 總結
因此,軟體中如果需要擷取登陸使用者名稱,要根據具體情況選擇不同的方法。如果確信自己的進程一定在登陸使用者環境下啟動,則GetUserName即可;否則,需要採用後面的兩種方法,當然,在使用前需要判斷一下作業系統的類型。
歡迎諸位與大家分享其他方法。