Windows Vista 交互式服务编程
生活随笔
收集整理的這篇文章主要介紹了
Windows Vista 交互式服务编程
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
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權限的新進程呢?答案是肯定的。一下是實現這個操作的代碼,為了縮小篇幅我刪除了異常處理代碼
到這里我們的大部分工作已經完成了,我們還需要做的就是監控活動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轉移時遇到的問題和我的解決方案,有疏漏及謬誤指出請讀者不吝指正,你有好的想法和實現也請賜教.
我的郵箱是zhong.felix@gmail.com
轉自:http://blog.csdn.net/felixz/article/details/1346380
| 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); |
總結
以上是生活随笔為你收集整理的Windows Vista 交互式服务编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机领域的顶级会议和期刊
- 下一篇: VC(MFC、ATL)中 得到2个SYS