在上一篇博文中,我说了如何编写一个系统服务,SERVICE_STATUS结构中有一个SdwServiceType成员,ERVICE_INTERACTIVE_PROCESS表示服务是一个交互式服务,可以与用户交互,MSDN上虽然是这么说的,其实经过测试,在XP上这一个完全不影响服务显示UI,只要安装服务时允许服务与桌面交互即可(见下图)。而在WinVista和Win7以及更高的平台,天生就有Session 0隔离,这一个加不加就显得更鸡肋了。。
MSDN关于Session 0隔离的兼容性白皮书:(话说MSDN上有篇中文的文档可真不容易,,)https://msdn.microsoft.com/zh-cn/library/ee663077.aspx
简单说,Win32子系统中有会话这一概念,Vista以后,服务被隔离到了Session 0会话,用户会话从1开始,因此他们之间不能直接共享UI。
我们知道,会话之中是窗口站,窗口站里面有桌面,其中用户会话的交互式窗口站是固定的,也就是winsta0,其中的默认桌面也是固定的default,也就是说我们只需要在这个桌面窗口一个进程即可。
当然,直接使用CreateProcess肯定是不行的,因为进程的令牌会继承,进程令牌里有会话ID。那么我们就使用CreateProcessAsUser即可。
如何在编写服务,如何在服务使用下面的代码请参见:[Win32] 服务程序开发(1)基本概念和服务程序的框架
void ShowMessage(LPWSTR lpszMessage, LPWSTR lpszTitle) { // 获得当前Session ID DWORD dwSession = WTSGetActiveConsoleSessionId(); DWORD dwResponse = 0; // 显示消息对话框 WTSSendMessageW(WTS_CURRENT_SERVER_HANDLE, dwSession, lpszTitle, static_cast<DWORD>((wcslen(lpszTitle) + 1) * sizeof(wchar_t)), lpszMessage, static_cast<DWORD>((wcslen(lpszMessage) + 1) * sizeof(wchar_t)), 0, 0, &dwResponse, FALSE); } void CreateUserProcess(LPCTSTR Filename) { STARTUPINFO si; PROCESS_INFORMATION pi; RtlZeroMemory(&si, sizeof(STARTUPINFO)); RtlZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); si.cb = sizeof(STARTUPINFO); //si.lpDesktop = TEXT("Winsta0\\default"); HANDLE hToken, hDuplicatedToken = NULL; WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken);//获取用户令牌 DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken);//复制令牌 LPVOID lpEnvironment = NULL; CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE);//创建当前用户环境 //在当前用户创建进程(CREATE_UNICODE_ENVIRONMENT表示用户环境是Unicode字符串) if (CreateProcessAsUser(hDuplicatedToken, Filename, NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, NULL, &si, &pi) == 0){ ShowMessage(L"在用户界面创建进程失败", L"Error"); } //释放资源 CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(hToken); CloseHandle(hDuplicatedToken); DestroyEnvironmentBlock(lpEnvironment); }
CreateUserProcess这个函数就是创建一个当前用户的进程,不过如果开启了UAC这个进程是没有管理员权限的,因为开启UAC时WTSQueryUserToken获取的是低权限令牌。
在Win8.1测试:运行正常。
在Win10测试:新创建的进程窗口不能直接创建在前面,即使新进程调用SetWindowPos
在Win7测试:和win10效果一样,新创建的进程窗口不能直接创建在前面,即使新进程调用SetWindowPos
在XP测试:和win8.1效果一样,运行正常。
进一步测试:运行的程序创建一个有总在最前风格的程序窗口
Win8.1正常、Win7正常、Win10正常、XP正常