出处:http://blog.csdn.net/donghui6116773/article/details/53467069

  服务(Service)对于大家来说一定不会陌生,它是Windows 操作系统重要的组成部分。我们可以把服务想像成一种特殊的应用程序,它随系统的“开启~关闭”而“开始~停止”其工作内容,在这期间无需任何用户参与。

Windows 服务在后台执行着各种各样任务,支持着我们日常的桌面操作。有时候可能需要服务与用户进行信息或界面交互操作,这种方式在XP 时代是没有问题的,但自从Vista 开始你会发现这种方式似乎已不起作用。

Session 0 隔离实验

下面来做一个名叫AlertService 的服务,它的作用就是向用户发出一个提示对话框,我们看看这个服务在Windows 7 中会发生什么情况。

  1. using System.ServiceProcess;
  2. using System.Windows.Forms;
  3.  
  4. namespace AlertService
  5. {
  6. public partial class Service1 : ServiceBase
  7. {
  8. public Service1()
  9. {
  10. InitializeComponent();
  11. }
  12.  
  13. protected override void OnStart(string[] args)
  14. {
  15. MessageBox.Show("A message from AlertService.");
  16. }
  17.  
  18. protected override void OnStop()
  19. {
  20. }
  21. }
  22. }

程序编译后通过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 类,并在类中加入如下代码:

  1. public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
  2.  
  3. public static void ShowMessageBox(string message, string title)
  4. {
  5. int resp = 0;
  6. WTSSendMessage(
  7. WTS_CURRENT_SERVER_HANDLE,
  8. WTSGetActiveConsoleSessionId(),
  9. title, title.Length,
  10. message, message.Length,
  11. 0, 0, out resp, false);
  12. }
  13.  
  14. [DllImport("kernel32.dll", SetLastError = true)]
  15. public static extern int WTSGetActiveConsoleSessionId();
  16.  
  17. [DllImport("wtsapi32.dll", SetLastError = true)]
  18. public static extern bool WTSSendMessage(
  19. IntPtr hServer,
  20. int SessionId,
  21. String pTitle,
  22. int TitleLength,
  23. String pMessage,
  24. int MessageLength,
  25. int Style,
  26. int Timeout,
  27. out int pResponse,
  28. bool bWait);

在ShowMessageBox 函数中调用了WTSSendMessage 来发送信息窗口,这样我们就可以在Service 的OnStart 函数中使用,打开Service1.cs 加入下面代码:

  1. protected override void OnStart(string[] args)
  2. {
  3. Interop.ShowMessageBox("This a message from AlertService.",
    "AlertService Message");
  4. }

编译程序后在服务管理器中重新启动AlertService 服务,从下图中可以看到消息窗口是在当前用户桌面显示的,而不是Session 0 中。

CreateProcessAsUser 函数

如果想通过服务向桌面用户Session 创建一个复杂UI 程序界面,则需要使用CreateProcessAsUser 函数为用户创建一个新进程用来运行相应的程序。打开Interop 类继续添加下面代码:

  1. public static void CreateProcess(string app, string path)
  2. {
  3. bool result;
  4. IntPtr hToken = WindowsIdentity.GetCurrent().Token;
  5. IntPtr hDupedToken = IntPtr.Zero;
  6.  
  7. PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
  8. SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
  9. sa.Length = Marshal.SizeOf(sa);
  10.  
  11. STARTUPINFO si = new STARTUPINFO();
  12. si.cb = Marshal.SizeOf(si);
  13.  
  14. int dwSessionID = WTSGetActiveConsoleSessionId();
  15. result = WTSQueryUserToken(dwSessionID, out hToken);
  16.  
  17. if (!result)
  18. {
  19. ShowMessageBox("WTSQueryUserToken failed", "AlertService Message");
  20. }
  21.  
  22. result = DuplicateTokenEx(
  23. hToken,
  24. GENERIC_ALL_ACCESS,
  25. ref sa,
  26. (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
  27. (int)TOKEN_TYPE.TokenPrimary,
  28. ref hDupedToken
  29. );
  30.  
  31. if (!result)
  32. {
  33. ShowMessageBox("DuplicateTokenEx failed" ,"AlertService Message");
  34. }
  35.  
  36. IntPtr lpEnvironment = IntPtr.Zero;
  37. result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false);
  38.  
  39. if (!result)
  40. {
  41. ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message");
  42. }
  43.  
  44. result = CreateProcessAsUser(
  45. hDupedToken,
  46. app,
  47. String.Empty,
  48. ref sa, ref sa,
  49. false, 0, IntPtr.Zero,
  50. path, ref si, ref pi);
  51.  
  52. if (!result)
  53. {
  54. int error = Marshal.GetLastWin32Error();
  55. string message = String.Format("CreateProcessAsUser Error: {0}", error);
  56. ShowMessageBox(message, "AlertService Message");
  57. }
  58.  
  59. if (pi.hProcess != IntPtr.Zero)
  60. CloseHandle(pi.hProcess);
  61. if (pi.hThread != IntPtr.Zero)
  62. CloseHandle(pi.hThread);
  63. if (hDupedToken != IntPtr.Zero)
  64. CloseHandle(hDupedToken);
  65. }
  66.  
  67. [StructLayout(LayoutKind.Sequential)]
  68. public struct STARTUPINFO
  69. {
  70. public Int32 cb;
  71. public string lpReserved;
  72. public string lpDesktop;
  73. public string lpTitle;
  74. public Int32 dwX;
  75. public Int32 dwY;
  76. public Int32 dwXSize;
  77. public Int32 dwXCountChars;
  78. public Int32 dwYCountChars;
  79. public Int32 dwFillAttribute;
  80. public Int32 dwFlags;
  81. public Int16 wShowWindow;
  82. public Int16 cbReserved2;
  83. public IntPtr lpReserved2;
  84. public IntPtr hStdInput;
  85. public IntPtr hStdOutput;
  86. public IntPtr hStdError;
  87. }
  88.  
  89. [StructLayout(LayoutKind.Sequential)]
  90. public struct PROCESS_INFORMATION
  91. {
  92. public IntPtr hProcess;
  93. public IntPtr hThread;
  94. public Int32 dwProcessID;
  95. public Int32 dwThreadID;
  96. }
  97.  
  98. [StructLayout(LayoutKind.Sequential)]
  99. public struct SECURITY_ATTRIBUTES
  100. {
  101. public Int32 Length;
  102. public IntPtr lpSecurityDescriptor;
  103. public bool bInheritHandle;
  104. }
  105.  
  106. public enum SECURITY_IMPERSONATION_LEVEL
  107. {
  108. SecurityAnonymous,
  109. SecurityIdentification,
  110. SecurityImpersonation,
  111. SecurityDelegation
  112. }
  113.  
  114. public enum TOKEN_TYPE
  115. {
  116. TokenPrimary = 1,
  117. TokenImpersonation
  118. }
  119.  
  120. public const int GENERIC_ALL_ACCESS = 0x10000000;
  121.  
  122. [DllImport("kernel32.dll", SetLastError = true,
  123. CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
  124. public static extern bool CloseHandle(IntPtr handle);
  125.  
  126. [DllImport("advapi32.dll", SetLastError = true,
  127. CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
  128. public static extern bool CreateProcessAsUser(
  129. IntPtr hToken,
  130. string lpApplicationName,
  131. string lpCommandLine,
  132. ref SECURITY_ATTRIBUTES lpProcessAttributes,
  133. ref SECURITY_ATTRIBUTES lpThreadAttributes,
  134. bool bInheritHandle,
  135. Int32 dwCreationFlags,
  136. IntPtr lpEnvrionment,
  137. string lpCurrentDirectory,
  138. ref STARTUPINFO lpStartupInfo,
  139. ref PROCESS_INFORMATION lpProcessInformation);
  140.  
  141. [DllImport("advapi32.dll", SetLastError = true)]
  142. public static extern bool DuplicateTokenEx(
  143. IntPtr hExistingToken,
  144. Int32 dwDesiredAccess,
  145. ref SECURITY_ATTRIBUTES lpThreadAttributes,
  146. Int32 ImpersonationLevel,
  147. Int32 dwTokenType,
  148. ref IntPtr phNewToken);
  149.  
  150. [DllImport("wtsapi32.dll", SetLastError=true)]
  151. public static extern bool WTSQueryUserToken(
  152. Int32 sessionId,
  153. out IntPtr Token);
  154.  
  155. [DllImport("userenv.dll", SetLastError = true)]
  156. static extern bool CreateEnvironmentBlock(
  157. out IntPtr lpEnvironment,
  158. IntPtr hToken,
  159. bool bInherit);

在CreateProcess 函数中同时也涉及到DuplicateTokenEx、WTSQueryUserToken、CreateEnvironmentBlock 函数的使用,有兴趣的朋友可通过MSDN 进行学习。完成CreateProcess 函数创建后,就可以真正的通过它来调用应用程序了,回到Service1.cs 修改一下OnStart 我们来打开一个CMD 窗口。如下代码:

  1. protected override void OnStart(string[] args)
  2. {
  3. Interop.CreateProcess("cmd.exe",@"C:\Windows\System32\");
  4. }

重新编译程序,启动AlertService 服务便可看到下图界面。至此,我们已经可以通过一些简单的方法对Session 0 隔离问题进行解决。大家也可以通过WCF 等技术完成一些更复杂的跨Session 通信方式,实现在Windows 7 及Vista 系统中服务与桌面用户的交互操作。

(转)C# Windows服务 弹出消息提醒框的更多相关文章

  1. Windows服务弹出MessageBox对话框

    Windows服务弹出MessageBox对话框 自从Windows升级到Vista版本后,系统服务就不在允许弹出那些惨绝人寰的MessageBox了(至于为什么不让弹出,原理有点小复杂,我也不是很门 ...

  2. Sharepoint 弹出消息提示框 .

    在event receiver中如何弹出一个类似winform中messagebox.show 的框? 那我要对用户显示一些错误信息或者提示信息怎么搞? 1. 如果是在ItemAdding或者其他进行 ...

  3. JS实现复制页面文字弹出消息提醒

    先上效果图: 简洁版: <script type="text/javascript"> document.body.oncopy=function(){ alert(& ...

  4. 用PHP实现弹出消息提示框

    方法一: echo "<script>alert('提示内容')</script>"; 方法二: echo '<script language=&qu ...

  5. bootstrap添加模态窗后,再弹出消息提示框后,原先的滚动条消失

    设置需要滚动的模态框 overflow :scroll

  6. Windows 计划任务之消息提醒

    Windows 计划任务之消息提醒 你肯定也有这种需求.想做一个计划任务,却发现老式消息提醒已经被微软禁止了. 或者就是很单纯的希望给系统弹出一个消息框而并非CMD的echo命令. so...how ...

  7. bat脚本弹出消息示例(msg命令详细解释)

    弹出消息的bat,其实就是通过批处理调用msg命令,msg是系统自在的一个可以发送信息的命令. 示例: @echo off rem 测试MSG msg * "ok" rem 测试M ...

  8. 弹出消息对话框ScriptManager

    //直接调用WebMessageBox方法 #region 弹出消息对话框 /// <summary> /// 弹出消息对话框 /// </summary> /// <p ...

  9. PHP自定义弹出消息类,用于弹出提示信息并返回

    一个用PHP自写的弹出消息类,用于在程序出错时弹出提示,,弹出警告框,或在程序运行到某阶段的快捷提示,需用时只需传入参数即可,函数并不复杂,但觉得挺实用.具体代码: function Alert($a ...

随机推荐

  1. vim编辑Makefile如何使用Tab

    因为用vim编辑代码设置了Tab键为4个空格,但有时候我们需要编写Makefile,必须使用Tab,同时也不想设置set noexpandtab. 其实可以先Ctrl_v组合键,再按Tab键盘,这样我 ...

  2. Java中的接口和抽象类

    接口和抽象类是Java设计中最基本的概念,它们都不能实例化对象,都可以实现多态,也都能用来创建匿名内部类.但实际使用上还有很多的不同. 两者的语法定义不同,对应的设计抽象关系也不同,接口主要是对行为的 ...

  3. ubuntu18关闭系统自动更新

    ubuntu18.04关闭系统自动更新有两个方法:1.修改配置文件 修改配置文件/etc/apt/apt.conf.d/10periodic#0是关闭,1是开启,将所有值改为0vi etc/apt/a ...

  4. No input file specified.

    no input file specified 解决方法 2018年02月23日 14:25:07 tiramisuer8023 阅读数:36607   版权声明:本文为博主原创文章,未经博主允许不得 ...

  5. 给idea添加类注释和方法注释模板

    这是我找到的最好的,最简单明白的一文: https://blog.csdn.net/xiaoliulang0324/article/details/79030752

  6. Python日志模块logging&JSON

    日志模块的用法 json部分 先开一段测试代码:注意  str可以直接处理字典   eval可以直接将字符串转成字典的形式 dic={'key1':'value1','key2':'value2'} ...

  7. 第二次作业——分布式版本控制系统Git的安装与使用

    作业要求来自:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/2097 远程仓库地址是:https://github.com/sheep ...

  8. java内存区域之程序计数器

    程序计数器(program counter register) 作用:字节码解释其工作时,通过这个计数器的值的改变,来选取下一条执行的字节码命令. 由于java虚拟机的都线程是通过线程轮流切换,并分配 ...

  9. ps-如何去背景色(将背景色变透明)

    由于生活或工作的需求,图片的处理是必不可少.其中将图片某一部分变为透明,或者截取图片的某一部分比较常见. 1.首先,打开待处理的图片: 2.复制背景图层,将背景图层设为不可见(左边的眼睛即可),选择左 ...

  10. PHP常用的转义函数

    1. addslashes addslashes对SQL语句中的特殊字符进行转义操作,包括(‘), (“), (), (NUL)四个字符,此函数在DBMS没有自己的转义函数时候使用,但是如果DBMS有 ...