Windows Vista 对快速用户切换,用户账户权限,以及服务程序所运行的会话空间都作了很大的改动,致使一些原本可以工作的程序不再能够正常工作了,我们不得不进行一些改进以跟上 Vista 的步伐。

我们的软件在Windows NT/2000/XP/Vista 系统中安装了一个系统服务,这个服务负责以 SYSTEM 权限启动我们的主程序。我们的主程序启动后会在系统托盘添加一个图标,点击此图标可以弹出控制菜单,通过这个菜单也可以激活配置程序首选项的对话框。在 Windows NT/2000/XP 下我们的程序都可以正常工作。哦不,当 XP 具备了快速用户切换功能的时候我们的问题已经出现了。XP 启动后我们以用户 A 登录,我们的图标出现在系统托盘,一切工作都正常,可当我们使用快速用户切换,切换到用户B后(用户A此时也是已登录状态,并没有注销),虽然用户B已经 是本地控制台会话(Session 属性为 Console)但我们的图标已经无法出现了,自然菜单和对话框更无从谈起了。我们的程序是和本机控制台桌面相关的,这种情况无疑是个缺陷。再来看一下在 Vista 平台是怎么样吧,系统启动后以用户A登录,我们的图标更本就没有出现,查看进程管理器中的进程列表发现我们的程序已经启动了,当我们从远端检查我们的服 务,发现已经正常工作,尝试远程登录我们的服务,Vista 会在本机控制台弹出一个消息框,提示有交互式服务消息,是否查看这个消息,点击立刻查看发现切换到另外一个桌面去了。
于是开始分析这种情况发生的原因。在 Windows NT/2000 中系统服务进程和本机控制台交互式登录的用户都运行于Session0 中,默认用户桌面运行于 WinSta0 窗口站,所以我们的程序由服务程序启动时依然是和本机用户处于同一个Session中,即使在某些情况下出现不能弹出对话框或者无法添加系统托盘图标的情 况也只需要修改一下进程桌面到 WinSta0/Default 就可以了(可以参考 MSDN 中 OpenInputDesktop, SetThreadDesktop 等API的说明)。
XP为我们带来了快速用户切换,也让我们所采用的软件架构问题浮现出来。当我们快速切 换到用户B的时候,用户A仍然在会话中(Session0),而用户B则处于新启动的会话中(Session1或者其他),此时服务程序和本机控制台程序 就不在处于同一会话了,OpenInputDesktop,SetThreadDesktop 等API的工作范围仅限于本Session,用户A没有退出,Session0也依然存在但是已经是 Disconnected 状态,当进程所处的Session是 Disconnected 状态的时候调用 OpenInputDesktop 会返回错误“无效的API”。进程及线程所属的Session 是由他们的Token 结构中的 TokenSessionId 决定的(参见MSDN中SetTokenInformation 和 TOKEN_INFORMATION_CLASS的说明),我尝试以微软提供的相关API修改运行中的进程和线程的TokenSessionId 信息从而达到修改桌面环境的目的,到目前还没有成功过(或许可以尝试参考RootKit 技术,不过即使修改成功到底能不能实现我们的需求也不确定)。我们的进程无法跨越Session的界限,自然无法与当前活动的另外一个Session中的 桌面交互了, L 。
Vista中又是如何的一番景象呢?处于安全方面及其他因素的考虑,Vista以及将 所有的服务程序置于Session0中,而为本机第一个交互登录的用户创建了Session1,快速切换到用户B后则是 Session2,无论是本机登录的用户,快速切换后的用户,还是远程桌面登录的用户再也没有谁和服务进程处于同一个Session中了,我们的程序还运 行在Session0中,自然我们的托盘图标是没有用户能看到了。事实上这个图标还是可以出现的。Session0因为不是一个交互式会话所以没有象其他 用户环境初始化的时候一样启动Explorer程序,但是我们开始可以手工启动他,在Session0中启动 Explorer 后任务栏出现后我们还是看到了我们的图标(具体启动Explorer的方法我们不在此文中讨论),菜单、对话框也可以使用。
既然我们的程序必须运行在Session0而我们又没有办法把我们的图标、对话框一下 子就抛到隔壁Session的用户桌面上去,只能想其他的办法了。微软也不提倡我们这种服务程序直接提供GUI与用户直接交互的方式,而他们建议使用 C/S架构,Client/Server之间用Socket/Pipe/RPC等方式通讯,这样我们只要把Client整个进程放到用户Session去 和用户交互,然后将配置信息等内容通过上述途径传递给Server,服务端在作出相应的响应即可。
把GUI分离出来并不是那么困难,然后在以前直接调用的地方加上一个通过Pipe通讯的接口,这样GUI(Client)的运行就可以灵活的掌握了。
最初我想把用户界面程序放到 Startup(启动)中随用户登录自动启动。这样当用户A和B都登录后将有两个用户界面程序在运行,而我们的服务只是和当前活动的控制台登录用户交互,所以这样并不符合需求。
接下来我们需要看看如何判定当前的活动Session是哪个,然后如何在这个活动Session中启动我们的用户界面程序了。
微软从XP/2003开始为我们提供了一套Windows Terminal Service 的相关API,这些API都以WTS开头(请安装MSDN2005以查阅相关说明),要获得活动Session也不止一个途径,最简单的就是直接使用
DWORD WTSGetActiveConsoleSessionId(void);
来获得活动Session Id 。要在程序中使用这些API需要最新的Platform SDK(如果你正在使用Visual Studio 2005那么它已经具备了相关头文件和库文件可以直接使用了),如果你在使用VC++ 6.0 你也没有或者不打算安装最新的SDK那么你可以直接使用LoadLibrary() 装载wtsapi32.dll然后使用GetProcAddress()获得相关函数的地址以调用它们。我们获得了活动SessionId后就可以使用
BOOL WTSQueryUserToken(
ULONG SessionId,
PHANDLE phToken
);
来获取当前活动Session中的用户令牌(Token),有了这个Token我们的就可以在活动Session中创建新进程了,
BOOL CreateProcessAsUser(
HANDLE hToken,
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
将 我们获得的Token作为此API的第一个参数即可,你可以先尝试一下运行一个notepad.exe看看,怎么样?你可以在控制台桌面上看到新进程了。 再查看一下进程列表,该进程的用户名是当前控制台登录的用户。可是这里我们又遇到一个问题,我们需要收集当前交本机互式登录用户的一些信息,而有些操作需 要很高的权限才能完成,而Vista下即使是Administraotrs用户组成员默认也是以Users权限启动进程的,所以我们创建的新进程只有 Users权限,无法完成一些操作,当然我们可以使用Vista所提供的UI来询问用户以提升至管理员权限,可有些操作甚至是管理员Token也无法完成 的,而且需要用户确认实在在易用性上大打折扣,所以我决定在活动Session中以SYSTEM权限启动我们的用户交互程序。显然 WTSQueryUserToken() 是不好用了。
之 前,我们提到过进程所属的Session是由进程Token中的TokenSessionId来决定的,那么我们是不是可以复制服务进程的Token然后 修改其中的TokenSessionId,从而在用户桌面上创建一个具有SYSTEM权限的新进程呢?答案是肯定的。一下是实现这个操作的代码,为了缩小 篇幅我删除了异常处理代码
HANDLE hTokenThis = NULL ;
HANDLE hTokenDup = NULL ;
HANDLE hThisProcess = GetCurrentProcess ();
OpenProcessToken ( hThisProcess , TOKEN_ALL_ACCESS , & hTokenThis );
DuplicateTokenEx ( hTokenThis , MAXIMUM_ALLOWED , NULL , SecurityIdentification, TokenPrimary, & hTokenDup );
DWORD dwSessionId = WTSGetActiveConsoleSessionId();
SetTokenInformation ( hTokenDup , TokenSessionId, & dwSessionId , sizeof ( DWORD ));
STARTUPINFO si ;
PROCESS_INFORMATION pi ;
ZeroMemory (& si , sizeof ( STARTUPINFO ));
ZeroMemory (& pi , sizeof (PROCESS_INFORMATION));
si . cb = sizeof ( STARTUPINFO );
si . lpDesktop = "WinSta0//Default";
LPVOID pEnv = NULL ;
DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE ;
CreateEnvironmentBlock(& pEnv , hTokenDup , FALSE );
CreateProcessAsUser (
              hTokenDup ,
              NULL ,
              ( char *)"notepad",
              NULL ,
              NULL ,
              FALSE ,
              dwCreationFlag ,
              pEnv ,
               NULL ,
              & si ,
              & pi );
到这里我们的大部分工作已经完成了,我们还需要做的就是监控活动Session的变化,就是用户的登录、注销、快速切换。WTS系列API以及为我们提供了具备这些能力的API了,大致可以用一下几种方法实现:
1.              设置一个定时器,使用WTSGetActiveConsoleSessionId()轮询活动桌面id,当检测到变化的时候让用户交互程序的前一个实例退出,在新活动Session中创建新进程。
2.              使用WTSRegisterSessionNotification()函数注册一个窗口来接收WTSSESSION_NOTIFICATION消息,来判断Session变化。
3.              使用 WTSEnumerateSessions枚举所有Session然后根据返回的WTS_SESSION_INFO结构中的State成员来判断Session状态,找到处于 Active状态的Session.
结合你的其他需求选择其中之一,然后作出响应就可以了。
本文只是浅显的描述一下我在向Windows Vista转移时遇到的问题和我的解决方案,有疏漏及谬误指出请读者不吝指正,你有好的想法和实现也请赐教.
using System; using System.Runtime.InteropServices; using System.Security.Principal; namespace BlueVision.HxyProject.FTPMonitorDaemonServices { class Interop { 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); /// <summary> /// 穿透Session 0 /// </summary> /// <param name="app">应用程序全路径名称</param> /// <param name="path">应用程序所在路径</param> 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); } }

使用WTSGetActiveConsoleSessionId()的VISTA服务与桌面交互相关推荐

  1. 用gd扩展调用imagegrabscreen截图,完全黑屏!允许服务与桌面交互没用!

    用gd扩展调用imagegrabscreen截图,完全黑屏!!!流传的方法"允许服务与桌面交互"根本没有用!!而且最诡异的是我尝试用system调用一个C写的exe去截屏也是黑屏, ...

  2. 设置c#windows服务描述及允许服务与桌面交互的几种方法

    方法一: 在ProjectInstaller.cs重写 install() ,Uninstall()方法 public override void Install(IDictionary stateS ...

  3. 允许服务与桌面交互_在后全面屏时代 手机需要什么样的人机交互?

    在智能手机进入全面屏时代之后,智能手机的屏占比已经越来越高,智能手机上的各种实体按键也已经越来越少,手机屏幕已经在客观上成为了用户进行交互操作的主要部件,而触控也全面取代了按键成为手机交互中最主要的方 ...

  4. 允许服务与桌面交互_vivo 正式推出 Origin OS,融合自然设计与全新交互_搜狐汽车...

    点击右上角关注我们,每天给您带来最新最潮的科技资讯,让您足不出户也知道科技圈大事! 今天下午,vivo 推出了全新 Origin OS 手机系统.它采用了源于自然界的设计理念,同时加入了全新并且允许用 ...

  5. 允许服务与桌面交互_vivo 正式推出 Origin OS,融合自然设计与全新交互

    点击右上角关注我们,每天给您带来最新最潮的科技资讯,让您足不出户也知道科技圈大事! 今天下午,vivo 推出了全新 Origin OS 手机系统.它采用了源于自然界的设计理念,同时加入了全新并且允许用 ...

  6. Windows 服务程序、窗口界面、桌面交互、与远程桌面

    昨天用c写了一个windows服务(服务内部带一个gui窗口+系统托盘),在windows xp sp3上测试,启动服务后,系统托盘显示正常. 但在另一台windows 2003 sp2 上测试(通过 ...

  7. C#穿透session隔离———Windows服务启动UI交互程序

    在Windows服务里面启动其他具有界面的应用程序,需要穿透session隔离,尝试了很多种方法,都可行,现在一一列举下来,并写下几个需要注意的地方. 需要注意的地方 首先要将服务的Account属性 ...

  8. windows服务与前台交互

    1.问题描述 最近遇上一个难题,我写了一个windows服务,想要与桌面可以进行交互,设置了属性在运行一点反应也没有. 2.解决方案    查找出来的解决方案有以下这些(一个代码块一个): 1 [Ru ...

  9. 微服务架构 接口交互问题_架构师的故事:设计微服务架构

    架构师在软件项目中的作用是提供待解决问题的工作模型.架构师的工作是提供脚手架,开发人员将根据这些脚手架构建他们的代码,使应用程序所有部件都组合在一起. 在构建微服务架构时,项目的架构师主要关注以下3个 ...

最新文章

  1. 利用OpenCV实现抖音最强变脸术 | CSDN原力计划
  2. hdu5056(找相同字母不出现k次的子串个数)
  3. 计算机考研【211 计算机专业院校 官方排名】教育部 第4次“计算机专业”学科评估
  4. MiseringThread.java 解析页面线程
  5. LeetCode167 | Two Sum II - Input array is sorted (Easy)
  6. 安全威胁建模综述_如何使用威胁建模分析应用程序的安全性
  7. linux5中文支持,centos安装中文支持
  8. Spark之SparkStreaming理论篇
  9. php mysql time_wait_[PHP]MySQL的wait_timeout与pdo对象
  10. java计算list集合中重复对象的次数及for循环内外创建对象
  11. 20面向对象三特征 之继承 方法重写 super
  12. MASM5.0下载安装与运行第一个HelloWorld
  13. js中给html元素追加属性,用JS(JavaScript )给HTML元素增加id属性
  14. c语言高级成分,高级语言的基本成分数据成分,运算成分,控制成分,传输成分,怎么看它们的类型区别的?比如其中对处理对象的类型说明属于高级语...
  15. 高斯公式积分matlab,三用MATLAB实现定积分计算.PPT
  16. 求函数:x的n次方(函数调用)
  17. 忆阻器交叉开关阵列中的长短期记忆(LSTM)神经网络
  18. gym101908 F. Music Festival(状压dp)
  19. Java练习题 类 编写一个程序,使用复数类Complex验证两个复数 1+2i 和3+4i 相加产生一个新的复数 4+6i 。
  20. BVT BAT (版本验证测试和版本验收测试)

热门文章

  1. Flv.js全面解析
  2. 通过网页查看服务器算法,利用17ce自动化监测网站
  3. Android之闪动的、增长的数字(仿余额宝)
  4. 电子琴仿真c语言程序,基于单片机简易电子琴仿真和源程序
  5. ScriptManager 脚本报错
  6. WebKit(WKScriptMessageHandler)
  7. 布尔代数(基本概念,运算规则,符号/数字代表的含义)
  8. underscore.js源码整体框架解析
  9. 符得海与王岱老师合影20121109
  10. wallpaper使用教程,从注册到安装