1. 程式人生 > >以Windows系統服務得到活動使用者的使用者名稱、UserProfile與環境變數

以Windows系統服務得到活動使用者的使用者名稱、UserProfile與環境變數

在之前的一篇博文中(http://blog.csdn.net/nirendao/article/details/51194003),介紹瞭如何使用QT寫一個Windows下的Service. 這篇文章試圖在此基礎上,解決一個問題:如何在這個系統級別的Windows service裡面獲得活動使用者的一些資訊,比如使用者名稱,環境變數。本篇文章並未完全解決這些問題,因為很可能是基本沒有什麼好的方法的。

首先,要明瞭Windows系統的一些特性,比如Fast User Switching(https://msdn.microsoft.com/en-us/library/windows/desktop/bb776893(v=vs.85).aspx

),它指的是在Windows XP之前版本的Windows OS,一個使用者要登入,就必須等待其他使用者登出(Log off);但是自從Windows XP開始,就可以做到:一個使用者在使用的時候,並不需要登出(Log off),而只是切換使用者,就可以允許其他使用者登入了。這就是Fast User Switching. 另外,也允許真正的多個遠端使用者登入了(以前只是Windows 2003 Server版才可以). 這裡要注意的是:雖然不需要使用者登出(Log off),但是能看到桌面的在同一時刻只會有一個使用者。比如遠端登入桌面,遠端者如果看到桌面,需要現有的桌面擁有者允許並退出桌面(不是Log off,只是類似切換使用者)。

雖然有Fast User Switching這樣的特性,允許多個使用者同時登入,但是很可能依然有活動使用者的概念(Active User),即看到桌面的那個使用者。–這僅僅是筆者的猜測,並未在網上找到相應的文件。那麼如何在一個系統級別的程序(如system level service)中得到active user的使用者名稱和環境變數呢?本文分步驟來探討這個問題。

第一步是如何得到active user的使用者名稱。筆者做了以下的嘗試,最終能夠實現目標。

嘗試-1:
使用QT中的qgetenv()方法獲取環境變數%UserName%

qInfo() << "User Name = "
<< qgetenv("UserName");

可惜這樣得到的結果是: hostname$, 如”FINIX-LAPTOP$”. 很明顯,這樣得到的是和系統相關的資訊,而不是特定使用者的使用者名稱。

嘗試-2:
使用Windows API GetUserName()來得到環境變數%UserName%

wchar_t acUserName[200];
DWORD nUserName = sizeof(acUserName);
if (GetUserName(acUserName, &nUserName))
{
    // Translate wchar_t * to QString
    qInfo() << "Windows User Name = " << QString::fromWCharArray(acUserName);
}

得到的結果是”SYSTEM”. 很顯然,仍然認為自己是系統程序,而沒有去取活動使用者的環境變數%UserName%.

嘗試-3:
先去獲得活動控制檯的session id,然後再去獲得相關資訊,如使用者名稱。

DWORD sessionId = WTSGetActiveConsoleSessionId();
qInfo() << "session id = " << sessionId;

wchar_t* ppBuffer[100];
DWORD bufferSize;
WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE, sessionId, WTSUserName, ppBuffer, &bufferSize);

這次嘗試成功了。成功地獲取到了Active User的使用者名稱。

在第一步成功獲得active user的使用者名稱之後,第二步就是:
我們要試圖來獲得active使用者的Home Location,或者也稱為%UserProfile%,即一般為C:\Users\

嘗試-4:

QStringList homePath = QStandardPaths::standardLocations(QStandardPaths::HomeLocation);
qInfo() << homePath.first().split(QDir::separator()).last();

這裡得到的結果是: “C:/Windows/system32/config/systemprofile”,這其實是系統級別的%UserProfile%.

嘗試-5:

wchar_t envVar[] = L"UserProfile";
wchar_t result[100];
GetEnvironmentVariable(envVar, result, sizeof(result)/sizeof(wchar_t));
qInfo() << "USER PROFILE = " << QString::fromWCharArray(result);

得到的依然是: “C:/Windows/system32/config/systemprofile”.

嘗試-6:
同嘗試-3. 但是對WTSQuerySessionInformation()使用不同的引數,比如WTSWorkingDirectory或WTSClientDirectory. 然而,得到都是空字串。

其實我最終想做到的是在此係統級別service中得到某個指定使用者或active使用者的環境變數,而這裡的嘗試-5正是呼叫了Windows獲取環境變數的API,可惜獲取的仍是系統級別的環境變數,而不是當前活動使用者的環境變數。所以這一部分嘗試是失敗了。

最終的解決方法是這樣的:
先用WTSQueryUserToken()獲得給定session的登入使用者的token,然後將token傳給CreateEnvironmentBlock()以獲取該使用者的環境資訊。最後,就可以根據CreateEnvironmentBlock()的輸出引數lpEnvironment來獲取環境變量了;也可以呼叫CreateProcessAsUser()來以該使用者的身份啟動某程序。

最後,我做了一個實驗,給這個Windows service添加了一個新功能,就是監控使用者的登入、登出、lock、unlock的事件,當有登入或unlock事件發生的時候,在log裡打印出當前active user的user name. 實驗顯示,列印的使用者名稱是正確的。
本文的實驗程式碼如下:
https://github.com/FinixLei/QtProjects/tree/master/TestWindowsService