1. 程式人生 > >使用WTSGetActiveConsoleSessionId()的VISTA服務與桌面互動

使用WTSGetActiveConsoleSessionId()的VISTA服務與桌面互動

摘自:http://blog.csdn.net/felixz/archive/2006/10/23/1346380.aspx

作者:

Windows Vista 對快速使用者切換,使用者賬戶許可權,以及服務程式所執行的會話空間都作了很大的改動,致使一些原本可以工作的程式不再能夠正常工作了,我們不得不進行一些改進以跟上 Vista 的步伐。

我們的軟體在Windows NT/2000/XP/Vista 系統中安裝了一個系統服務,這個服務負責以 SYSTEM 許可權啟動我們的主程式。我們的主程式啟動後會在系統托盤新增一個圖示,點選此圖示可以彈出控制選單,通過這個選單也可以啟用配置程式首選項的對話方塊。在 Windows NT/2000/XP 下我們的程式都可以正常工作。哦不,當 XP 具備了快速使用者切換功能的時候我們的問題已經出現了。XP 啟動後我們以使用者 A 登入,我們的圖標出現在系統托盤,一切工作都正常,可當我們使用快速使用者切換,切換到使用者B後(使用者A此時也是已登入狀態,並沒有登出),雖然使用者B已經 是本地控制檯會話(Session 屬性為 Console)但我們的圖示已經無法出現了,自然選單和對話方塊更無從談起了。我們的程式是和本機控制檯桌面相關的,這種情況無疑是個缺陷。再來看一下在 Vista 平臺是怎麼樣吧,系統啟動後以使用者A登入,我們的圖示更本就沒有出現,檢視程序管理器中的程序列表發現我們的程式已經啟動了,當我們從遠端檢查我們的服 務,發現已經正常工作,嘗試遠端登入我們的服務,Vista 會在本機控制檯彈出一個訊息框,提示有互動式服務訊息,是否檢視這個訊息,點選立刻檢視發現切換到另外一個桌面去了。 於是開始分析這種情況發生的原因。在 Windows NT/2000 中系統服務程序和本機控制檯互動式登入的使用者都運行於Session0 中,預設使用者桌面運行於 WinSta0 視窗站,所以我們的程式由服務程式啟動時依然是和本機使用者處於同一個Session中,即使在某些情況下出現不能彈出對話方塊或者無法新增系統托盤圖示的情 況也只需要修改一下程序桌面到 WinSta0/Default 就可以了(可以參考 MSDN 中 OpenInputDesktop, SetThreadDesktop 等API的說明)。 XP為我們帶來了快速使用者切換,也讓我們所採用的軟體架構問題浮現出來。當我們快速切 換到使用者B的時候,使用者A仍然在會話中(Session0),而使用者B則處於新啟動的會話中(Session1或者其他),此時服務程式和本機控制檯程式 就不在處於同一會話了,OpenInputDesktop,SetThreadDesktop 等API的工作範圍僅限於本Session,使用者A沒有退出,Session0也依然存在但是已經是 Disconnected 狀態,當程序所處的Session是 Disconnected 狀態的時候呼叫 OpenInputDesktop 會返回錯誤“無效的API”。程序及執行緒所屬的Session 是由他們的Token 結構中的 TokenSessionId 決定的(參見MSDN中SetTokenInformation 和 TOKEN_INFORMATION_CLASS的說明),我嘗試以微軟提供的相關API修改執行中的程序和執行緒的TokenSessionId 資訊從而達到修改桌面環境的目的,到目前還沒有成功過(或許可以嘗試參考RootKit 技術,不過即使修改成功到底能不能實現我們的需求也不確定)。我們的程序無法跨越Session的界限,自然無法與當前活動的另外一個Session中的 桌面互動了,L
。 Vista中又是如何的一番景象呢?處於安全方面及其他因素的考慮,Vista以及將 所有的服務程式置於Session0中,而為本機第一個互動登入的使用者建立了Session1,快速切換到使用者B後則是 Session2,無論是本機登入的使用者,快速切換後的使用者,還是遠端桌面登入的使用者再也沒有誰和服務程序處於同一個Session中了,我們的程式還運 行在Session0中,自然我們的托盤圖示是沒有使用者能看到了。事實上這個圖示還是可以出現的。Session0因為不是一個互動式會話所以沒有象其他 使用者環境初始化的時候一樣啟動Explorer程式,但是我們開始可以手工啟動他,在Session0中啟動 Explorer 後任務欄出現後我們還是看到了我們的圖示(具體啟動Explorer的方法我們不在此文中討論),選單、對話方塊也可以使用。 既然我們的程式必須執行在Session0而我們又沒有辦法把我們的圖示、對話方塊一下 子就拋到隔壁Session的使用者桌面上去,只能想其他的辦法了。微軟也不提倡我們這種服務程式直接提供GUI與使用者直接互動的方式,而他們建議使用 C/S架構,Client/Server之間用Socket/Pipe/RPC等方式通訊,這樣我們只要把Client整個程序放到使用者Session去 和使用者互動,然後將配置資訊等內容通過上述途徑傳遞給Server,服務端在作出相應的響應即可。 把GUI分離出來並不是那麼困難,然後在以前直接呼叫的地方加上一個通過Pipe通訊的介面,這樣GUI(Client)的執行就可以靈活的掌握了。 最初我想把使用者介面程式放到 Startup(啟動)中隨使用者登入自動啟動。這樣當用戶A和B都登入後將有兩個使用者介面程式在執行,而我們的服務只是和當前活動的控制檯登入使用者互動,所以這樣並不符合需求。 接下來我們需要看看如何判定當前的活動Session是哪個,然後如何在這個活動Session中啟動我們的使用者介面程式了。 微軟從XP/2003開始為我們提供了一套Windows Terminal Service 的相關API,這些API都以WTS開頭(請安裝MSDN2005以查閱相關說明),要獲得活動Session也不止一個途徑,最簡單的就是直接使用 DWORD
WTSGetActiveConsoleSessionId(void); 來獲得活動Session Id 。要在程式中使用這些API需要最新的Platform SDK(如果你正在使用Visual Studio 2005那麼它已經具備了相關標頭檔案和庫檔案可以直接使用了),如果你在使用VC++ 6.0 你也沒有或者不打算安裝最新的SDK那麼你可以直接使用LoadLibrary() 裝載wtsapi32.dll然後使用GetProcAddress()獲得相關函式的地址以呼叫它們。我們獲得了活動SessionId後就可以使用 BOOL WTSQueryUserToken( ULONG SessionId,
PHANDLE phToken ); 來獲取當前活動Session中的使用者令牌(Token),有了這個Token我們的就可以在活動Session中建立新程序了, BOOL CreateProcessAsUser( HANDLE hToken, LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); 將 我們獲得的Token作為此API的第一個引數即可,你可以先嚐試一下執行一個notepad.exe看看,怎麼樣?你可以在控制檯桌面上看到新程序了。 再檢視一下程序列表,該程序的使用者名稱是當前控制檯登入的使用者。可是這裡我們又遇到一個問題,我們需要收集當前交本機互式登入使用者的一些資訊,而有些操作需 要很高的許可權才能完成,而Vista下即使是Administraotrs使用者組成員預設也是以Users許可權啟動程序的,所以我們建立的新程序只有 Users許可權,無法完成一些操作,當然我們可以使用Vista所提供的UI來詢問使用者以提升至管理員許可權,可有些操作甚至是管理員Token也無法完成 的,而且需要使用者確認實在在易用性上大打折扣,所以我決定在活動Session中以SYSTEM許可權啟動我們的使用者互動程式。顯然 WTSQueryUserToken() 是不好用了。 之 前,我們提到過程序所屬的Session是由程序Token中的TokenSessionId來決定的,那麼我們是不是可以複製服務程序的Token然後 修改其中的TokenSessionId,從而在使用者桌面上建立一個具有SYSTEM許可權的新程序呢?答案是肯定的。一下是實現這個操作的程式碼,為了縮小 篇幅我刪除了異常處理程式碼
HANDLEhTokenThis = NULL; HANDLEhTokenDup = NULL; HANDLEhThisProcess = GetCurrentProcess(); OpenProcessToken(hThisProcess, TOKEN_ALL_ACCESS, &hTokenThis); DuplicateTokenEx(hTokenThis, MAXIMUM_ALLOWED,NULL, SecurityIdentification, TokenPrimary, &hTokenDup); DWORDdwSessionId = WTSGetActiveConsoleSessionId(); SetTokenInformation(hTokenDup, TokenSessionId, &dwSessionId, sizeof(DWORD)); STARTUPINFOsi; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(STARTUPINFO)); ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); si.cb = sizeof(STARTUPINFO); si.lpDesktop = "WinSta0//Default"; LPVOIDpEnv = NULL; DWORDdwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE; CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE); CreateProcessAsUser( hTokenDup, NULL,               (char *)"notepad", NULL, NULL, FALSE, dwCreationFlag, pEnv, NULL,               &si,               &pi);
到這裡我們的大部分工作已經完成了,我們還需要做的就是監控活動Session的變化,就是使用者的登入、登出、快速切換。WTS系列API以及為我們提供了具備這些能力的API了,大致可以用一下幾種方法實現: 1.設定一個定時器,使用WTSGetActiveConsoleSessionId()輪詢活動桌面id,當檢測到變化的時候讓使用者互動程式的前一個例項退出,在新活動Session中建立新程序。 2.使用WTSRegisterSessionNotification()函式註冊一個視窗來接收WTSSESSION_NOTIFICATION訊息,來判斷Session變化。 3.使用 WTSEnumerateSessions列舉所有Session然後根據返回的WTS_SESSION_INFO結構中的State成員來判斷Session狀態,找到處於 Active狀態的Session. 結合你的其他需求選擇其中之一,然後作出響應就可以了。 本文只是淺顯的描述一下我在向Windows Vista轉移時遇到的問題和我的解決方案,有疏漏及謬誤指出請讀者不吝指正,你有好的想法和實現也請賜教.