1. 程式人生 > >(轉)C# Windows服務 彈出訊息提醒框

(轉)C# Windows服務 彈出訊息提醒框

出處: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)正在試圖顯示資訊,是否需要瀏覽該資訊:

flashmeg

     嘗試點選“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 系統中服務與桌面使用者的互動操作。