在某国外大型汽车公司BI项目中,有一个子项目,需要通过大屏幕展示销售报表,程序需要自动启动和关闭。开发人员在开发过程中,发现在Win7的service中不能直接操作UI进程,调查过程中,发现如下相关资料可供参考。

原文地址:解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离

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

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

Session 0 隔离实验

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

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

程序编译后通过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. public static void ShowMessageBox(string message, string title)
  3. {
  4. int resp = 0;
  5. WTSSendMessage(
  6. WTS_CURRENT_SERVER_HANDLE,
  7. WTSGetActiveConsoleSessionId(),
  8. title, title.Length,
  9. message, message.Length,
  10. 0, 0, out resp, false);
  11. }
  12. [DllImport("kernel32.dll", SetLastError = true)]
  13. public static extern int WTSGetActiveConsoleSessionId();
  14. [DllImport("wtsapi32.dll", SetLastError = true)]
  15. public static extern bool WTSSendMessage(
  16. IntPtr hServer,
  17. int SessionId,
  18. String pTitle,
  19. int TitleLength,
  20. String pMessage,
  21. int MessageLength,
  22. int Style,
  23. int Timeout,
  24. out int pResponse,
  25. bool bWait);
  26. 在ShowMessageBox 函数中调用了WTSSendMessage 来发送信息窗口,这样我们就可以在Service 的OnStart 函数中使用,打开Service1.cs 加入下面代码:
  27. protected override void OnStart(string[] args)
  28. {
  29. Interop.ShowMessageBox("This a message from AlertService.",
  30. "AlertService Message");
  31. }

编译程序后在服务管理器中重新启动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. PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
  7. SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
  8. sa.Length = Marshal.SizeOf(sa);
  9. STARTUPINFO si = new STARTUPINFO();
  10. si.cb = Marshal.SizeOf(si);
  11. int dwSessionID = WTSGetActiveConsoleSessionId();
  12. result = WTSQueryUserToken(dwSessionID, out hToken);
  13. if (!result)
  14. {
  15. ShowMessageBox("WTSQueryUserToken failed", "AlertService Message");
  16. }
  17. result = DuplicateTokenEx(
  18. hToken,
  19. GENERIC_ALL_ACCESS,
  20. ref sa,
  21. (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
  22. (int)TOKEN_TYPE.TokenPrimary,
  23. ref hDupedToken
  24. );
  25. if (!result)
  26. {
  27. ShowMessageBox("DuplicateTokenEx failed" ,"AlertService Message");
  28. }
  29. IntPtr lpEnvironment = IntPtr.Zero;
  30. result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false);
  31. if (!result)
  32. {
  33. ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message");
  34. }
  35. result = CreateProcessAsUser(
  36. hDupedToken,
  37. app,
  38. String.Empty,
  39. ref sa, ref sa,
  40. false, 0, IntPtr.Zero,
  41. path, ref si, ref pi);
  42. if (!result)
  43. {
  44. int error = Marshal.GetLastWin32Error();
  45. string message = String.Format("CreateProcessAsUser Error: {0}", error);
  46. ShowMessageBox(message, "AlertService Message");
  47. }
  48. if (pi.hProcess != IntPtr.Zero)
  49. CloseHandle(pi.hProcess);
  50. if (pi.hThread != IntPtr.Zero)
  51. CloseHandle(pi.hThread);
  52. if (hDupedToken != IntPtr.Zero)
  53. CloseHandle(hDupedToken);
  54. }
  55. [StructLayout(LayoutKind.Sequential)]
  56. public struct STARTUPINFO
  57. {
  58. public Int32 cb;
  59. public string lpReserved;
  60. public string lpDesktop;
  61. public string lpTitle;
  62. public Int32 dwX;
  63. public Int32 dwY;
  64. public Int32 dwXSize;
  65. public Int32 dwXCountChars;
  66. public Int32 dwYCountChars;
  67. public Int32 dwFillAttribute;
  68. public Int32 dwFlags;
  69. public Int16 wShowWindow;
  70. public Int16 cbReserved2;
  71. public IntPtr lpReserved2;
  72. public IntPtr hStdInput;
  73. public IntPtr hStdOutput;
  74. public IntPtr hStdError;
  75. }
  76. [StructLayout(LayoutKind.Sequential)]
  77. public struct PROCESS_INFORMATION
  78. {
  79. public IntPtr hProcess;
  80. public IntPtr hThread;
  81. public Int32 dwProcessID;
  82. public Int32 dwThreadID;
  83. }
  84. [StructLayout(LayoutKind.Sequential)]
  85. public struct SECURITY_ATTRIBUTES
  86. {
  87. public Int32 Length;
  88. public IntPtr lpSecurityDescriptor;
  89. public bool bInheritHandle;
  90. }
  91. public enum SECURITY_IMPERSONATION_LEVEL
  92. {
  93. SecurityAnonymous,
  94. SecurityIdentification,
  95. SecurityImpersonation,
  96. SecurityDelegation
  97. }
  98. public enum TOKEN_TYPE
  99. {
  100. TokenPrimary = 1,
  101. TokenImpersonation
  102. }
  103. public const int GENERIC_ALL_ACCESS = 0x10000000;
  104. [DllImport("kernel32.dll", SetLastError = true,
  105. CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
  106. public static extern bool CloseHandle(IntPtr handle);
  107. [DllImport("advapi32.dll", SetLastError = true,
  108. CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
  109. public static extern bool CreateProcessAsUser(
  110. IntPtr hToken,
  111. string lpApplicationName,
  112. string lpCommandLine,
  113. ref SECURITY_ATTRIBUTES lpProcessAttributes,
  114. ref SECURITY_ATTRIBUTES lpThreadAttributes,
  115. bool bInheritHandle,
  116. Int32 dwCreationFlags,
  117. IntPtr lpEnvrionment,
  118. string lpCurrentDirectory,
  119. ref STARTUPINFO lpStartupInfo,
  120. ref PROCESS_INFORMATION lpProcessInformation);
  121. [DllImport("advapi32.dll", SetLastError = true)]
  122. public static extern bool DuplicateTokenEx(
  123. IntPtr hExistingToken,
  124. Int32 dwDesiredAccess,
  125. ref SECURITY_ATTRIBUTES lpThreadAttributes,
  126. Int32 ImpersonationLevel,
  127. Int32 dwTokenType,
  128. ref IntPtr phNewToken);
  129. [DllImport("wtsapi32.dll", SetLastError=true)]
  130. public static extern bool WTSQueryUserToken(
  131. Int32 sessionId,
  132. out IntPtr Token);
  133. [DllImport("userenv.dll", SetLastError = true)]
  134. static extern bool CreateEnvironmentBlock(
  135. out IntPtr lpEnvironment,
  136. IntPtr hToken,
  137. 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 系统中服务与桌面用户的交互操作。

解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离的更多相关文章

  1. [转]解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离

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

  2. c# 在windows服务中 使用定时器

    由于最近做自动执行的程序,开始做windows服务程序, 在windows服务中如何使用定时器的时候一直失效, 以前是直接拖入timer控件,但是不能直接运行,后来在网上找了一段程序,好使了. //开 ...

  3. windows 服务中托管asp.net core

    在windows 服务中托管asp.net core SDK 2.1.300 官方示例 1.添加运行标识符 xml <PropertyGroup> <TargetFramework& ...

  4. 将WCF寄宿在托管的Windows服务中

    在我之前的一篇博客中我介绍了如何发布WCF服务并将该服务寄宿于IIS上,今天我再来介绍一种方式,就是将WCF服务寄宿在Windows服务中,这样做有什么好处呢?当然可以省去部署IIS等一系列的问题,能 ...

  5. 添加 MySql 服务、Tomcat服务到windows服务中

    添加 MySql 服务到windows服务中: cmd --> F:\MySql\MySqlServer5.1\bin\mysqld --install 这样用默认的 MySQL 为名称添加一个 ...

  6. 在windows服务中使用定时器

    在windows服务中,利用winform中直接拖动timer控件的方式使用定时器是不可以的,启动服务后会发现定时器并没有执行.那么在windows服务中如何使用定时器呢?  不使用直接拖动控件的方式 ...

  7. 解决windows 服务中定时器timer 定时偶尔失效 问题

    最近做个windows 服务,功能是:定时执行一个任务:自动登录到一个网站后,点击相关网面上的按钮button. 在处理的过程中发现定时器老是不定时的失效,失效时间没有规律. 由于刚开始处于测试阶段, ...

  8. win7 删除Windows服务的方法

    http://www.jb51.net/os/windows/25090.html 一.什么是Windows服务 Windows服务也称为Windows Service,它是Windows操作系统和W ...

  9. InstallUtil在windows服务中的使用(转)+ 服务安装的注意事项

    1.  新建一个Windows Service的方法: 1. 打开Visual Studio 2008新建一个project Solution: 2. 选择Windows->windows Se ...

随机推荐

  1. Linux下显示IP地理位置信息的小工具-nali

    一.简介 nali,名字取自中文“哪里”的拼音.nali包含一组命令行程序,其主要功能就是把一些网络工具的输出的IP字符串,附加上地理位置信息(使用纯真数据库QQWry.Dat).例如74.125.1 ...

  2. Spring解析

    Spring还是蛮有技术含量的,可以自己用代码实践一遍,找了一篇实践的案例: http://qingwengang.iteye.com/blog/621678 先mark下,等后面有时间了实践一遍. ...

  3. cmake 编译 c++ dll 的一个例子(更新1)

    CMakeLists.txt project(xxx) add_library(xxx SHARED xxx.cpp) add_executable(yyy yyy.cpp) target_link_ ...

  4. GoLang之协程

    GoLang之协程 目前,WebServer几种主流的并发模型: 多线程,每个线程一次处理一个请求,在当前请求处理完成之前不会接收其它请求:但在高并发环境下,多线程的开销比较大: 基于回调的异步IO, ...

  5. php实现发送邮件

    smtp.php: <?php class smtp {     /* Public Variables */     var $smtp_port;     var $time_out;    ...

  6. Effective Objective-C 2.0 — 第10条:在既有类中使用关联对象存放自定义数据

    可以通过“关联对象”机制来把两个对象连起来 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系” 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于 ...

  7. yii2 小技巧

    参考地址:http://www.cnblogs.com/sandea/p/5714830.html 1.不通过日志获取AR执行的原生SQL语句和打印变量数据 $query = User::find() ...

  8. PhpStorm 快捷键

    不容易记住的: Ctrl + Shift + F  查找文本,在项目目录或指定的目录 Ctrl + Shift + R  查找文本并替换,在项目目录或指定的目录 Ctrl + E    打开最近关闭的 ...

  9. mysql max_allowed_packet查询和修改

    http://www.2cto.com/database/201303/195830.html mysql根据配置文件会限制server接受的数据包大小. 有时候大的插入和更新会被max_allowe ...

  10. 使用HttpFileServer自建下载服务器

    如今单位办公离不开电脑,使用电脑离不开资料传输,举一个简单的例子吧,很多用户经常在电脑上编辑文件,这些文件往往打印出来给领导审阅,可是你电脑上没有打印机,这时你会想到通过优盘.网络硬盘.邮箱.QQ等方 ...