从服务启动用户会话中的进程
Posted
技术标签:
【中文标题】从服务启动用户会话中的进程【英文标题】:Launching a process in user’s session from a service 【发布时间】:2010-06-27 16:40:25 【问题描述】:在 Windows Vista/7/2008/2008R2 中,是否可以?具体来说,本地会话将是最有用的。
我一直在阅读的所有内容似乎都在说这是不可能的,但我想在完全放弃之前我会在这里问一下。
我正在使用 VB.NET 进行编码,但会接受任何建议。
【问题讨论】:
本地交互用户是我关注的重点。我主要对从服务中锁定工作站感兴趣,但也需要执行其他程序,具体取决于某些条件。 【参考方案1】:真的有可能。您遇到的主要问题是 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?,其中我描述了如何强制该进程在用户桌面的前台启动。
【讨论】:
我刚刚解决了执行此操作的服务时遇到的问题,并在这里找到了您的答案...非常有帮助。就我而言,当我从生成的进程运行文件打开对话框时,我遇到了一个恼人的“拒绝访问”错误(否则对话框工作正常)。调用CreateEnvironmentBlock
并将结果传递给CreateProcessAsUser
似乎已经修复了它。我真的需要设置我的STARTUPINFO
结构的lpDesktop
值吗?我只是将其保留为 NULL。此外,它无需设置 SE_TCB_NAME 权限即可工作。
@paddy:抱歉,如果您有一些常见情况,所有步骤都是必需的。如果服务在某个用户帐户下运行而不是作为系统运行,则您确实必须设置TokenSessionId
以使交互式用户能够看到已启动进程的窗口。为了能够将SetTokenInformation
与TokenSessionId
一起使用,服务帐户确实需要具有SE_TCB_NAME
权限并且必须启用它。如果屏幕保护程序在进程启动时工作,则应将lpDesktop
设置为指定屏幕保护程序的桌面或默认用户桌面。【参考方案2】:
如果有帮助,我也遇到过类似的问题,但想要一个纯粹的 powershell 解决方案。
我从其他网站拼凑了一些内容并想出了这个:
function Invoke-CommandInSession
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[ScriptBlock] $expression
)
$commandLine = “powershell“
## Convert the command into an encoded command for PowerShell
$commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)
$encodedCommand = [Convert]::ToBase64String($commandBytes)
$args = “-Output XML -EncodedCommand $encodedCommand”
$action = New-ScheduledTaskAction -Execute $commandLine -Argument $args
$setting = New-ScheduledTaskSettingsSet -DeleteExpiredTaskAfter ([Timespan]::Zero)
$trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date) + ([Timespan]::FromSeconds(5)))
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $setting
Register-ScheduledTask "Invoke-CommandInSession - $([Guid]::NewGuid())" -InputObject $task
是的,它有点奇怪,但它可以工作 - 最重要的是你可以从 ps remoting 中调用它
【讨论】:
【参考方案3】:这是可以做到的,但服务直接与用户会话交互并不被认为是好的做法,因为它会造成严重的安全漏洞。
http://support.microsoft.com/kb/327618
更好的方法是创建 2 个程序,一个后端服务和一个前端客户端 UI 程序。服务后端始终运行并使用 WCF(例如)公开其操作。客户端程序可以在用户会话启动时运行。
【讨论】:
我不相信您链接的文章内容对于 2008(r2) 仍然有效。会话空隔离等... 这不是一回事。 Brad 询问是否使用用户凭据创建 /new/ 进程,而不是在用户会话中作为服务进程执行交互式操作。 在用户会话中启动进程仍在与用户桌面交互。让我们不要分裂头发它仍然不是一个好习惯。我同意链接不是最好的:) 是的,我的程序实际上就是这样设计的。有一个客户端应用程序使用远程处理通过命名管道与服务进行通信。出于安全原因,如果有人停止客户端进程,我需要找到一种锁定工作站的方法。我完全理解与用户会话的交互通常很差......这略有不同。谢谢你的回复!【参考方案4】:是的,使用CreateProcessAsUser 你可以做到这一点。 MSDN 有示例文章,但有一些注意事项。
【讨论】:
请不要发布带有版本号的 MSDN 链接,除非有理由始终使用该版本。 谢谢,我现在正在处理 .NET 的 API 声明。【参考方案5】:Subverting Vista UAC in Both 32 and 64 bit Architectures
提供的代码针对 Vista,但也适用于 Win7 和 Win10
【讨论】:
以上是关于从服务启动用户会话中的进程的主要内容,如果未能解决你的问题,请参考以下文章