Подтвердить что ты не робот

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

Мне нужно запустить программу из Windows Service. Эта программа является пользовательским пользовательским интерфейсом. Кроме того, это приложение должно запускаться под определенной учетной записью пользователя.

Проблема заключается в том, что службы Window работают в сеансе # 0, но зарегистрированные сеансы пользователя равны 1,2 и т.д.

Итак, возникает вопрос: как запустить процесс из окна службы таким образом, чтобы он выполнялся в текущем сеансе пользователя?

Я бы сделал акцент на том, что вопрос заключается не в том, как начать процесс под определенной учетной записью (это очевидно - Process.Start(новый ProcessStartInfo ( ".." ) {UserName =.., Password =..})). Даже если я установлю свои окна для запуска под текущей учетной записью пользователя, служба будет работать в сеансе # 0 в любом случае. Установка "Разрешить обслуживание для взаимодействия с рабочим столом" не помогает.

Служба моих окон основана на .net.

UPDATE: в первую очередь,.NET здесь нечего делать, это действительно чистая вещь Win32. Вот что я делаю. Следующий код находится в моей службе Windows (С#, используя функцию win32 через P/Inkove, я пропустил импортные подписи, они все здесь - http://www.pinvoke.net/default.aspx/advapi32/CreateProcessWithLogonW.html):

    var startupInfo = new StartupInfo()
        {
            lpDesktop = "WinSta0\\Default",
            cb = Marshal.SizeOf(typeof(StartupInfo)),
        };
    var processInfo = new ProcessInformation();
    string command = @"c:\windows\Notepad.exe";
    string user = "Administrator";
    string password = "password";
    string currentDirectory = System.IO.Directory.GetCurrentDirectory();
    try
    {
        bool bRes = CreateProcessWithLogonW(user, null, password, 0,
            command, command, 0,
            Convert.ToUInt32(0),
            currentDirectory, ref startupInfo, out processInfo);
        if (!bRes)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }
    catch (Exception ex)
    {
        writeToEventLog(ex);
        return;
    }
    WaitForSingleObject(processInfo.hProcess, Convert.ToUInt32(0xFFFFFFF));
    UInt32 exitCode = Convert.ToUInt32(123456);
    GetExitCodeProcess(processInfo.hProcess, ref exitCode);
    writeToEventLog("Notepad has been started by WatchdogService. Exitcode: " + exitCode);

    CloseHandle(processInfo.hProcess);
    CloseHandle(processInfo.hThread);

Код переходит к строке "Notepad был запущен WatchdogService. Exitcode:" + exitCode. Exitcode - 3221225794. И не было никакого нового блокнота. Где я ошибаюсь?

4b9b3361

Ответ 1

Блог в MSDN описывает решение

Это потрясающая полезная статья о запуске нового процесса в интерактивном сеансе из Windows-сервиса в Vista/7.

Для служб, отличных от LocalSystem, основная идея:

  • Перечислите процесс, чтобы получить дескриптор проводника.

  • OpenProcessToken должен предоставить вам токен доступа. Примечание. Учетная запись, на которой выполняется ваша служба, должна иметь соответствующие права для вызова этого API и получения маркера процесса.

  • Как только у вас есть токен, вызовите CreateProcessAsUser с этим токеном. Этот токен уже имеет правильный идентификатор сеанса.

Ответ 2

Проблема с ответом Shrike заключается в том, что он не работает с пользователем, подключенным через RDP.
Вот мое решение, которое правильно определяет текущий сеанс пользователя перед созданием процесса. Он был протестирован для работы с XP и 7.

https://github.com/murrayju/CreateProcessAsUser

Все, что вам нужно, обернуто в один класс .NET со статическим методом:

public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible)

Ответ 3

Это плохая идея. Хотя, возможно, это и не совсем невозможно, Microsoft сделала все возможное, чтобы сделать это как можно труднее, так как это позволяет так называемые Shatter Attacks. Смотрите, о чем писал Ларри Остерман в 2005 году:

Основная причина плохой идеи заключается в том, что интерактивные службы включают класс угроз, известных как атаки "Shatter" (потому что они "разбивают окна", я считаю).

Если вы выполните поиск "shatter attack", вы можете увидеть некоторые детали того, как работают эти угрозы безопасности. Microsoft также опубликовала статью KB 327618, которая расширяет документацию об интерактивных сервисах, а Майкл Ховард написал статью об интерактивных службах для библиотеки MSDN. Изначально атаки с разбивкой выполнялись после компонентов Windows, в которых были установлены потоковые сообщения (они уже давно исправлены), но они также использовались для атаки сторонних служб, которые вызывают пользовательский интерфейс.

Вторая причина - плохая идея, что флаг SERVICE_INTERACTIVE_PROCESS просто не работает. Пользовательский интерфейс службы всплывает в системном сеансе (обычно сеанс 0). Если, с другой стороны, пользователь работает в другом сеансе, пользователь никогда не видит пользовательский интерфейс. Существует два основных сценария, в которых пользователь подключается в другом сеансе - Terminal Services и Fast User Switching. TS не так распространен, но в домашних сценариях, где есть несколько человек, использующих один компьютер, FUS часто включается (например, на нашей кухне, например, 4 человека, в течение долгого времени постоянно работали на компьютере).

Третья причина, по которой интерактивные службы - плохая идея, заключается в том, что интерактивные службы не гарантируют работу с Windows Vista:). В рамках процесса упрощения безопасности, который входит в Windows Vista, интерактивные пользователи регистрируются на сеансах, отличных от системный сеанс - первый интерактивный пользователь запускается в сеансе 1, а не в сеансе 0. Это приводит к полному удалению ударов с разбивкой на коленях - пользовательские приложения не могут взаимодействовать с окнами с высокими привилегиями, запущенными в службах.

Предлагаемое обходное решение будет заключаться в использовании приложения в системном трее пользователя.

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

Разработка для Windows: Изоляция сеанса 0

Ответ 4

Вот как я его реализовал. Он попытается запустить процесс в качестве текущего зарегистрированного пользователя (из службы). Это основано на нескольких источниках, связанных с чем-то, что работает.

Это действительно ДЕЙСТВИТЕЛЬНО ЧИСТЫЙ WIN32/С++, поэтому на исходные вопросы не может быть полезным на 100%. Но я надеюсь, что это может спасти других людей некоторое время, ища что-то подобное.

Требуется Windows XP/2003 (не работает с Windows 2000). Вы должны связаться с Wtsapi32.lib

#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#include <Windows.h>
#include <WtsApi32.h>

bool StartInteractiveProcess(LPTSTR cmd, LPCTSTR cmdDir) {
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.lpDesktop = TEXT("winsta0\\default");  // Use the default desktop for GUIs
    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));
    HANDLE token;
    DWORD sessionId = ::WTSGetActiveConsoleSessionId();
    if (sessionId==0xffffffff)  // Noone is logged-in
        return false;
    // This only works if the current user is the system-account (we are probably a Windows-Service)
    HANDLE dummy;
    if (::WTSQueryUserToken(sessionId, &dummy)) {
        if (!::DuplicateTokenEx(dummy, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &token)) {
            ::CloseHandle(dummy);
            return false;
        }
        ::CloseHandle(dummy);
        // Create process for user with desktop
        if (!::CreateProcessAsUser(token, NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) {  // The "new console" is necessary. Otherwise the process can hang our main process
            ::CloseHandle(token);
            return false;
        }
        ::CloseHandle(token);
    }
    // Create process for current user
    else if (!::CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi))  // The "new console" is necessary. Otherwise the process can hang our main process
        return false;
    // The following commented lines can be used to wait for the process to exit and terminate it
    //::WaitForSingleObject(pi.hProcess, INFINITE);
    //::TerminateProcess(pi.hProcess, 0);
    ::CloseHandle(pi.hProcess);
    ::CloseHandle(pi.hThread);
    return true;
}

Ответ 6

Я не знаю, как это сделать в .NET, но в целом вам нужно будет использовать функцию Win32 API CreateProcessAsUser() (или что-то еще ее эквивальное .NET), указав желаемый токен доступа пользователя и имя рабочего стола, Это то, что я использую в своих службах на С++, и он отлично работает.

Ответ 7

Внедрен код @murrayju в службу Windows на W10. Запуск исполняемого файла из Program Files всегда вызывал ошибку -2 в VS. Я считаю, что это произошло из-за того, что начальный путь службы установлен на System32. Указание workDir не устранило проблему до тех пор, пока я не добавлю следующую строку до CreateProcessAsUser в StartProcessAsCurrentUser:

if (workDir != null)
   Directory.SetCurrentDirectory(workDir);

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

Ответ 8

Принятый ответ не работал в моем случае как приложение, в котором я запускал требуемые права администратора. Что я сделал, так я создал командный файл для запуска приложения. Он содержал следующее:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe  "%~dp0MySoft.exe"

Затем я передал местоположение этого файла методу StartProcessAsCurrentUser(). Это трюк.