Запуск процесса в сеансе пользователей из службы

В 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).

Вы можете использовать функцию GetTokenInformation с параметром TokenSessionId (см. http://msdn.microsoft.com/en-us/library/aa446671.aspx) для получения идентификатора сеанса dwSessionId сеанса пользователей, если вы знаете токен пользователя hClient.

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, но если ваша служба запускается в локальной системе, у вас достаточно разрешений. Кстати, следующий фрагмент кода работает не только с учетной записью Local System, но и с учетной записью должна иметь привилегию SE_TCB_NAME, чтобы иметь возможность переключать текущий сеанс сервера терминалов.

Еще одно замечание. В приведенном выше коде мы начинаем новый процесс с той же учетной записью, что и текущий процесс (например, Local System). Вы изменяете код изменения для использования другой учетной записи, например токена пользователя hClient. Важно иметь primary token. Если у вас есть маркер олицетворения, вы можете преобразовать его в первичный токен точно так же, как в приведенном выше коде.

В структуре STARTUPINFO, используемой в CreateProcessAsUser, вы должны использовать lpDesktop = WinSta0\Default ".

В зависимости от ваших требований также может понадобиться использовать CreateEnvironmentBlock для создания нового блока среды, который вы передадите новому процессу.

Я рекомендую вам также прочитать Как обеспечить, чтобы окно процесса, запущенное Process.Start(ProcessStartInfo), фокусировалось на всех Формах?, где я описываю, как заставить процесс будет запущен на переднем плане на рабочем столе пользователей.

Ответ 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

Да, используя CreateProcessAsUser, вы можете это сделать. MSDN содержит образцы статей, и есть некоторые оговорки.

Ответ 4

Это можно сделать, но это не считается хорошей практикой для служб напрямую взаимодействовать с сеансами пользователей, поскольку это может создать серьезные дыры в безопасности.

http://support.microsoft.com/kb/327618

Лучшим подходом является создание двух программ, бэкэнд-сервиса и интерфейса пользовательского интерфейса для конечного клиента. Бэкэнд службы работает все время и предоставляет свои операции с использованием WCF (например). Клиентскую программу можно запустить при запуске сеанса пользователя.