在某国外大型汽车公司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查看CPU和内存使用情况

    在系统维护的过程中,随时可能有需要查看 CPU 使用率,并根据相应信息分析系统状况的需要.在 CentOS 中,可以通过 top 命令来查看 CPU 使用状况.运行 top 命令后,CPU 使用状态会 ...

  2. JavaWeb学习笔记——jsp:setproperty和getproperty

  3. Java多线程系列

    一.参考文献 1.:Java多线程系列目录 (一) 基础篇 01. Java多线程系列--“基础篇”01之 基本概念 02. Java多线程系列--“基础篇”02之 常用的实现多线程的两种方式 03. ...

  4. CentOS系统rsync文件同步 安装配置

    rsync是类unix系统下的数据镜像备份工具,从软件的命名上就可以看出来了——remote sync 它的特性如下: 可以镜像保存整个目录树和文件系统. 可以很容易做到保持原来文件的权限.时间.软硬 ...

  5. 在VS中向命令行添加参数的方法

    在VS中向命令行添加参数的方法 在VS中向命令行添加参数,即向main()函数传递参数的方法: 右键单击要 添加参数的工程-->属性-->配置属性-->调试,在右侧“命令参数”栏输入 ...

  6. VC----SDK下对窗口非客户区的操作

    窗口分成两大部分:客户区和非客户区.非客户区再次细分:标题栏,如图片中顶部深蓝色:左边框,如图片中红色部分:上边框,如图片中绿色部分:右边框,如图片中右侧天蓝色部分:底边框,如图片中下面棕色部分. 之 ...

  7. git add

    一.前言git add命令主要用于把我们要提交的文件的信息添加到索引库中.当我们使用git commit时,git将依据索引库中的内容来进行文件的提交. 二.基本git add <path> ...

  8. Java基础笔记 – Annotation注解的介绍和使用 自定义注解

    Java基础笔记 – Annotation注解的介绍和使用 自定义注解 本文由arthinking发表于5年前 | Java基础 | 评论数 7 |  被围观 25,969 views+ 1.Anno ...

  9. Redis 安装,主从配置及Sentinel配置自动Failover

    1.安装redis 首页地址:http://redis.io/ 下载地址:http://download.redis.io/ 下载最新的源码包 tar -zxvf redis-stable.tar.g ...

  10. JavaScript中一些怪异用法的理解

    引言 JavaScript这门语言有些场合的用法还是比较怪异的.这篇文章会尽量将这门语言特有的一些比较特殊的用法收集在一起.就当是平时开发时需要注意的地方吧. 特殊用法收集 1.!!用法 在JavaS ...