Windows Vista 互動式服務編程

來源:互聯網
上載者:User
  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中的案頭互動了 。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.
結合你的其他需求選擇其中之一,然後作出響應就可以了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.