對於簡單的互動,服務可以通過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 系統中服務與案頭使用者的互動操作。
參考資料
1. WTSSendMessage Function
http://msdn.microsoft.com/en-us/library/aa383842(VS.85).aspx
2. CreateProcessAsUser Function
http://msdn.microsoft.com/en-us/library/ms682429(v=VS.85).aspx
3. WTSSendMessage (wtsapi32)
http://www.pinvoke.net/default.aspx/wtsapi32/WTSSendMessage.html
4. WTSQueryUserToken Function
http://msdn.microsoft.com/en-us/library/aa383840(VS.85).aspx
5. http://www.pinvoke.net/
代碼下載 AlertService2_jb51.rar
作者:李敬然(Gnie)
出處:{GnieTech} (http://www.cnblogs.com/gnielee/)