真的有可能。您遇到的主要问题是 Windows 应被视为终端服务器,而用户会话应被视为远程会话。您的服务应该能够启动在远程会话中运行的属于用户的进程。
顺便说一下,如果你编写了一个在 Windows XP 下运行的服务,但它没有添加到域中,并且激活了快速用户切换,那么在第二个(第三个和等等)登录的用户桌面。
我希望你有一个用户令牌,例如你收到的关于模拟的用户令牌,或者你有一个dwSessionId 的会话。如果您没有它,您可以尝试使用一些 WTS 功能(远程桌面服务 API http://msdn.microsoft.com/en-us/library/aa383464.aspx,例如 WTSEnumerateProcesses 或 WTSGetActiveConsoleSessionId)或 LSA-API 来找出相应的用户会话(LsaEnumerateLogonSessions 请参阅http://msdn.microsoft.com/en-us/library/aa378275.aspx 和 LsaGetLogonSessionData 见http://msdn.microsoft.com/en-us/library/aa378290.aspx)或ProcessIdToSessionId(见http://msdn.microsoft.com/en-us/library/aa382990.aspx)。
如果您知道用户令牌hClient,则可以使用GetTokenInformation 函数和参数TokenSessionId(请参阅http://msdn.microsoft.com/en-us/library/aa446671.aspx)来接收用户会话的会话ID dwSessionId。
BOOL bSuccess;
HANDLE hProcessToken = NULL, hNewProcessToken = NULL;
DWORD dwSessionId, cbReturnLength;
bSuccess = GetTokenInformation (hClient, TokenSessionId, &dwSessionId,
sizeof(DWORD), &cbReturnLength);
bSuccess = OpenProcessToken (GetCurrentProcess(), MAXIMUM_ALLOWED, &hProcessToken);
bSuccess = DuplicateTokenEx (hProcessToken, MAXIMUM_ALLOWED, NULL,
SecurityImpersonation,
TokenPrimary, &hNewProcessToken);
EnablePrivilege (SE_TCB_NAME);
bSuccess = SetTokenInformation (hNewProcessToken, TokenSessionId, &dwSessionId,
sizeof(DWORD));
bSuccess = CreateProcessAsUser (hNewProcessToken, NULL, szCommandToExecute, ...);
这段代码只是一个模式。 EnablePrivilege 是一个简单的函数,使用AdjustTokenPrivileges 来启用SE_TCB_NAME 权限(参见http://msdn.microsoft.com/en-us/library/aa446619.aspx 作为模板)。重要的是,您从中启动的进程具有 TCB 权限,但如果您的服务在本地系统下运行,则您有足够的权限。顺便说一句,以下代码片段不仅适用于本地系统帐户,而且该帐户必须具有SE_TCB_NAME 权限才能切换当前终端服务器会话。
再说一句。在上面的代码中,我们使用与当前进程相同的帐户启动新进程(例如本地系统)。您更改代码以使用另一个帐户,例如用户令牌hClient。拥有primary token 很重要。如果您有一个模拟令牌,您可以将其转换为与上述代码完全相同的主令牌。
在CreateProcessAsUser 中使用的STARTUPINFO 结构中,您应该使用lpDesktop = WinSta0\Default"。
根据您的要求,可能还需要使用CreateEnvironmentBlock 来创建您将传递给新进程的新环境块。
我建议您也阅读How to ensure process window launched by Process.Start(ProcessStartInfo) has focus of all Forms?,其中我描述了如何强制该进程在用户桌面的前台启动。