使用WTSGetActiveConsoleSessionId()的VISTA服务与桌面交互

针对Windows Vista引入的会话隔离机制导致服务程序无法正常显示托盘图标等问题,本文介绍了如何通过修改进程Token中的SessionID,使服务程序能在当前活动会话中以SYSTEM权限启动用户界面程序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘自:http://blog.youkuaiyun.com/felixz/archive/2006/10/23/1346380.aspx

作者:

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转移时遇到的问题和我的解决方案,有疏漏及谬误指出请读者不吝指正,你有好的想法和实现也请赐教.
<think>好的,我现在需要帮助用户解决在Windows 11中配置C++服务程序以允许桌面交互的问题。首先,用户的问题涉及到系统级编程,特别是服务程序和用户交互的结合,这在Windows环境下可能会比较复杂,尤其是考虑到安全机制如Session 0隔离。 根据用户提供的引用内容,虽然引用了Docker、3D GS配置和Visual Studio安装,但这些似乎当前问题关联不大,可能用户之前的问题涉及这些内容,但现在的问题集中在服务配置上。因此,我需要专注于用户当前的需求:在Win11中配置服务以允许桌面交互,并使用C++编写交互程序。 首先,我需要回忆Windows服务的基本知识。Windows服务默认运行在Session 0,而用户界面程序运行在用户会话(如Session 1),因此直接交互是不可能的。用户提到的“允许桌面交互”在旧版本Windows中可能通过服务属性设置,但Win10/Win11由于Session 0隔离,已经禁用了这种方式。因此,解决方案可能需要绕过Session 0隔离,或者使用其他IPC方法。 接下来,我需要参考用户之前的回答,其中提到了几种方法:命名管道、WTSSendMessage API、COM组件。用户可能需要具体的实现步骤和配置方法。此外,用户可能需要了解如何注册服务、配置权限以及调试服务程序。 然后,我需要考虑用户可能的实际应用场景。例如,用户可能希望服务程序弹出对话框,或者用户应用程序交换数据。这涉及到不同会话间的通信,需要选择合适的IPC机制。同时,安全性也是一个重要因素,需要确保服务以最小权限运行,避免被恶意利用。 另外,用户可能遇到的常见问题包括权限不足、服务无法启动、IPC连接失败等。需要提供调试建议,比如查看事件查看器日志,使用sc命令管理服务等。 最后,我需要组织回答的结构,分步骤说明如何创建服务、配置权限、实现IPC,并提供代码示例和配置示例,确保用户能够逐步跟随操作。同时,需要注意使用中文,保持回答清晰,并引用之前提到的参考文献中的相关内容,如权限配置和工具使用。</think>在Windows 11中实现**服务程序桌面用户交互**需绕过Session 0隔离限制。以下是基于C++的具体配置和实现方案: --- ### 一、服务配置关键步骤 #### 1. 修改服务属性(旧版兼容方式) 虽然Windows 10/11默认禁用服务桌面直接交互,但可通过注册表强制启用(需谨慎): ```powershell # 以管理员身份运行PowerShell sc config "YourServiceName" type= interact type= own ``` - **限制**:仅适用于旧版应用,且需服务账户具有**交互式登录权限**(需组策略配置)[^3]。 #### 2. 配置服务账户权限 - **步骤1**:打开服务管理器(`services.msc`)→ 找到目标服务 → 右键属性 → "登录"选项卡 - **步骤2**:选择**"此账户"** → 输入本地管理员账户或`Local System`(高风险,不推荐) - **步骤3**:勾选**"允许服务桌面交互"**(Windows 11下此选项已失效,但需保留配置) --- ### 二、C++服务端实现跨会话交互 #### 方案1:通过WTSSendMessage API(推荐) 需链接`WtsApi32.lib`并包含头文件`<WtsApi32.h>`: ```cpp #include <WtsApi32.h> // 获取当前活动会话ID DWORD sessionId = WTSGetActiveConsoleSessionId(); // 创建用户令牌 HANDLE hToken; if (!WTSQueryUserToken(sessionId, &hToken)) { // 错误处理: GetLastError() } // 在用户会话中启动交互程序 STARTUPINFO si = { sizeof(STARTUPINFO) }; PROCESS_INFORMATION pi; si.lpDesktop = L"winsta0\\default"; // 关键:指定桌面环境 WCHAR cmdLine[] = L"C:\\MyInteractiveApp.exe"; if (!CreateProcessAsUser( hToken, // 用户令牌 NULL, // 应用程序路径(已在cmdLine中) cmdLine, // 命令行参数 NULL, // 进程安全性 NULL, // 线程安全性 FALSE, // 不继承句柄 CREATE_NEW_CONSOLE, // 创建新控制台 NULL, // 环境变量 NULL, // 当前目录 &si, // 启动信息 &pi)) // 进程信息 { // 错误处理: GetLastError() } CloseHandle(hToken); ``` #### 方案2:使用命名管道(需用户程序配合) **服务端代码**(服务程序中): ```cpp // 创建命名管道(管理员权限) HANDLE hPipe = CreateNamedPipe( L"\\\\.\\pipe\\MyServicePipe", PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 4096, // 输出缓冲区 4096, // 输入缓冲区 0, // 默认超时 NULL); // 默认安全性 // 等待用户程序连接 ConnectNamedPipe(hPipe, NULL); // 发送/接收数据 char buffer[1024]; DWORD bytesRead; ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL); ``` **客户端代码**(用户交互程序中): ```cpp HANDLE hPipe = CreateFile( L"\\\\.\\pipe\\MyServicePipe", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); // 发送数据到服务 char data[] = "User input"; DWORD bytesWritten; WriteFile(hPipe, data, strlen(data), &bytesWritten, NULL); ``` --- ### 三、完整开发流程 #### 1. 创建服务程序框架 ```cpp #include <Windows.h> #include <WtsApi32.h> SERVICE_STATUS_HANDLE g_StatusHandle; VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv) { // 注册服务控制处理器 g_StatusHandle = RegisterServiceCtrlHandler(L"MyInteractiveService", ServiceCtrlHandler); // 启动跨会话交互线程 CreateThread(NULL, 0, InteractionThread, NULL, 0, NULL); } DWORD WINAPI InteractionThread(LPVOID lpParam) { // 在此调用WTSSendMessage或管道通信代码 return 0; } ``` #### 2. 注册服务(管理员CMD) ```powershell sc create MyInteractiveService binPath= "C:\MyService.exe" start= auto sc failure MyInteractiveService reset= 86400 actions= restart/60000 ``` --- ### 四、安全增强措施 1. **降低权限** - 在服务属性中配置专用低权限账户(非SYSTEM) - 清单文件声明权限需求: ```xml <!-- 服务程序清单文件 --> <requestedPrivileges> <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/> </requestedPrivileges> ``` 2. **防火墙规则** ```powershell New-NetFirewallRule -DisplayName "MyService IPC" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow ``` --- ### 五、调试技巧 1. **服务日志输出** ```cpp // 写入Windows事件日志 ReportEvent(hEventLog, EVENTLOG_INFORMATION_TYPE, 0, 0, NULL, 1, 0, L"Service started", NULL); ``` 2. **实时调试** ```powershell sc start MyInteractiveService Get-WinEvent -LogName System | Where-Object {$_.ProviderName -eq "MyInteractiveService"} ``` --- ### 典型错误解决方案 | 错误现象 | 原因 | 解决方案 | |--------------------------|--------------------------|-----------------------------------| | 错误1053:服务未响应 | 未正确处理服务控制请求 | 在`ServiceCtrlHandler`中实现状态上报 | | 访问被拒绝(ERROR_ACCESS_DENIED) | 令牌权限不足 | 使用`AdjustTokenPrivileges`提升权限 | | 管道连接超时 | 用户程序未启动 | 通过任务计划程序触发用户程序启动 | ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值