出處:http://blog.csdn.net/donghui6116773/article/details/53467069
服務(Service)對於大家來說一定不會陌生,它是Windows 操作系統重要的組成部分。我們可以把服務想像成一種特殊的應用程序,它隨系統的“開啟~關閉”而“開始~停止”其工作內容,在這期間無需任何用戶參與。
Windows 服務在后台執行着各種各樣任務,支持着我們日常的桌面操作。有時候可能需要服務與用戶進行信息或界面交互操作,這種方式在XP 時代是沒有問題的,但自從Vista 開始你會發現這種方式似乎已不起作用。
Session 0 隔離實驗
下面來做一個名叫AlertService 的服務,它的作用就是向用戶發出一個提示對話框,我們看看這個服務在Windows 7 中會發生什么情況。
using System.ServiceProcess;
using System.Windows.Forms; namespace AlertService { public partial class Service1 : ServiceBase { public Service1() { InitializeComponent(); } protected override void OnStart(string[] args) { MessageBox.Show("A message from AlertService."); } protected override void OnStop() { } } }
程序編譯后通過Installutil 將其加載到系統服務中:
在服務屬性中勾選“Allow service to interact with desktop” ,這樣可以使AlertService 與桌面用戶進行交互。
在服務管理器中將AlertService 服務“啟動”,這時任務欄中會閃動一個圖標:
點擊該圖標會顯示下面窗口,提示有個程序(AlertService)正在試圖顯示信息,是否需要瀏覽該信息:
嘗試點擊“View the message”,便會顯示下圖界面(其實這個界面我已經不能從當前桌面操作截圖了,是通過Virtual PC 截屏的,其原因請繼續閱讀)。注意觀察可以發現下圖的桌面背景已經不是Windows 7 默認的桌面背景了,說明AlertService 與桌面系統的Session 並不相同,這就是Session 0 隔離作用的結果。
Session 0 隔離原理
在Windows XP、Windows Server 2003 或早期Windows 系統時代,當第一個用戶登錄系統后服務和應用程序是在同一個Session 中運行的。這就是Session 0 如下圖所示:
但是這種運行方式提高了系統安全風險,因為服務是通過提升了用戶權限運行的,而應用程序往往是那些不具備管理員身份的普通用戶運行的,其中的危險顯而易見。
從Vista 開始Session 0 中只包含系統服務,其他應用程序則通過分離的Session 運行,將服務與應用程序隔離提高系統的安全性。如下圖所示:
這樣使得Session 0 與其他Session 之間無法進行交互,不能通過服務向桌面用戶彈出信息窗口、UI 窗口等信息。這也就是為什么剛才我說那個圖已經不能通過當前桌面進行截圖了。
Session 檢查
在實際開發過程中,可以通過Process Explorer 檢查服務或程序處於哪個Session,會不會遇到Session 0 隔離問題。我們在Services 中找到之前加載的AlertService 服務,右鍵屬性查看其Session 狀態。
可看到AlertService 處於Session 0 中:
再來看看Outlook 應用程序:
很明顯在Windows 7 中服務和應用程序是處於不同的Session,它們之間加隔了一個保護牆,在下篇文章中將介紹如何穿過這堵保護牆使服務與桌面用戶進行交互操作。
如果在開發過程中確實需要服務與桌面用戶進行交互,可以通過遠程桌面服務的API 繞過Session 0 的隔離完成交互操作。
對於簡單的交互,服務可以通過WTSSendMessage 函數,在用戶Session 上顯示消息窗口。對於一些復雜的UI 交互,必須調用CreateProcessAsUser 或其他方法(WCF、.NET遠程處理等)進行跨Session 通信,在桌面用戶上創建一個應用程序界面。
WTSSendMessage 函數
如果服務只是簡單的向桌面用戶Session 發送消息窗口,則可以使用WTSSendMessage 函數實現。首先,在上一篇下載的代碼中加入一個Interop.cs 類,並在類中加入如下代碼:
public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; public static void ShowMessageBox(string message, string title) { int resp = 0; WTSSendMessage( WTS_CURRENT_SERVER_HANDLE, WTSGetActiveConsoleSessionId(), title, title.Length, message, message.Length, 0, 0, out resp, false); } [DllImport("kernel32.dll", SetLastError = true)] public static extern int WTSGetActiveConsoleSessionId(); [DllImport("wtsapi32.dll", SetLastError = true)] public static extern bool WTSSendMessage( IntPtr hServer, int SessionId, String pTitle, int TitleLength, String pMessage, int MessageLength, int Style, int Timeout, out int pResponse, bool bWait);
在ShowMessageBox 函數中調用了WTSSendMessage 來發送信息窗口,這樣我們就可以在Service 的OnStart 函數中使用,打開Service1.cs 加入下面代碼:
protected override void OnStart(string[] args) { Interop.ShowMessageBox("This a message from AlertService.",
"AlertService Message"); }
編譯程序后在服務管理器中重新啟動AlertService 服務,從下圖中可以看到消息窗口是在當前用戶桌面顯示的,而不是Session 0 中。
CreateProcessAsUser 函數
如果想通過服務向桌面用戶Session 創建一個復雜UI 程序界面,則需要使用CreateProcessAsUser 函數為用戶創建一個新進程用來運行相應的程序。打開Interop 類繼續添加下面代碼:
public static void CreateProcess(string app, string path) { bool result; IntPtr hToken = WindowsIdentity.GetCurrent().Token; IntPtr hDupedToken = IntPtr.Zero; PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES(); sa.Length = Marshal.SizeOf(sa); STARTUPINFO si = new STARTUPINFO(); si.cb = Marshal.SizeOf(si); int dwSessionID = WTSGetActiveConsoleSessionId(); result = WTSQueryUserToken(dwSessionID, out hToken); if (!result) { ShowMessageBox("WTSQueryUserToken failed", "AlertService Message"); } result = DuplicateTokenEx( hToken, GENERIC_ALL_ACCESS, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hDupedToken ); if (!result) { ShowMessageBox("DuplicateTokenEx failed" ,"AlertService Message"); } IntPtr lpEnvironment = IntPtr.Zero; result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false); if (!result) { ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message"); } result = CreateProcessAsUser( hDupedToken, app, String.Empty, ref sa, ref sa, false, 0, IntPtr.Zero, path, ref si, ref pi); if (!result) { int error = Marshal.GetLastWin32Error(); string message = String.Format("CreateProcessAsUser Error: {0}", error); ShowMessageBox(message, "AlertService Message"); } if (pi.hProcess != IntPtr.Zero) CloseHandle(pi.hProcess); if (pi.hThread != IntPtr.Zero) CloseHandle(pi.hThread); if (hDupedToken != IntPtr.Zero) CloseHandle(hDupedToken); } [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public Int32 cb; public string lpReserved; public string lpDesktop; public string lpTitle; public Int32 dwX; public Int32 dwY; public Int32 dwXSize; public Int32 dwXCountChars; public Int32 dwYCountChars; public Int32 dwFillAttribute; public Int32 dwFlags; public Int16 wShowWindow; public Int16 cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public Int32 dwProcessID; public Int32 dwThreadID; } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public Int32 Length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } public enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous, SecurityIdentification, SecurityImpersonation, SecurityDelegation } public enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation } public const int GENERIC_ALL_ACCESS = 0x10000000; [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern bool CloseHandle(IntPtr handle); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern bool CreateProcessAsUser( IntPtr hToken, string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", SetLastError = true)] public static extern bool DuplicateTokenEx( IntPtr hExistingToken, Int32 dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, Int32 ImpersonationLevel, Int32 dwTokenType, ref IntPtr phNewToken); [DllImport("wtsapi32.dll", SetLastError=true)] public static extern bool WTSQueryUserToken( Int32 sessionId, out IntPtr Token); [DllImport("userenv.dll", SetLastError = true)] static extern bool CreateEnvironmentBlock( out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
在CreateProcess 函數中同時也涉及到DuplicateTokenEx、WTSQueryUserToken、CreateEnvironmentBlock 函數的使用,有興趣的朋友可通過MSDN 進行學習。完成CreateProcess 函數創建后,就可以真正的通過它來調用應用程序了,回到Service1.cs 修改一下OnStart 我們來打開一個CMD 窗口。如下代碼:
protected override void OnStart(string[] args) { Interop.CreateProcess("cmd.exe",@"C:\Windows\System32\"); }
重新編譯程序,啟動AlertService 服務便可看到下圖界面。至此,我們已經可以通過一些簡單的方法對Session 0 隔離問題進行解決。大家也可以通過WCF 等技術完成一些更復雜的跨Session 通信方式,實現在Windows 7 及Vista 系統中服務與桌面用戶的交互操作。