Как запустить процесс из службы Windows в текущий пользовательский сеанс
Мне нужно запустить программу из Windows Service. Эта программа является приложением пользовательского интерфейса. Кроме того, это приложение должно быть запущено под определенной учетной записью пользователя.
Проблема заключается в том, что Window Services запускаются в сеансе № 0, но вошедшие в систему сеансы пользователей имеют 1,2 и т. Д.
Таким образом, вопрос заключается в следующем: как запустить процесс из оконной службы таким образом, чтобы он выполнялся в текущем сеансе пользователя?
Я бы подчеркнул, что вопрос не в том, как запустить процесс под определенной учетной записью (это очевидно - Process.Start(new ProcessStartInfo("..") { UserName=..,Password=..})). Даже если я установлю свои окна для запуска под текущей учетной записью пользователя, служба все равно будет работать в сеансе #0. Настройка "Разрешить службе взаимодействовать с рабочим столом" не помогает.
Мой Windows-сервис основан на.net.
ОБНОВЛЕНИЕ: во-первых, .NET здесь не имеет ничего общего, на самом деле это чисто Win32. Вот что я делаю. Следующий код находится в моей службе Windows (C# использует функцию 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);
Код переходит в строку "Блокнот был запущен WatchdogService. Exitcode: " + exitCode. Код выхода 3221225794. И новый блокнот не запущен. Где я не прав?
8 ответов
Блог на MSDN описывает решение
Это потрясающий полезный пост о запуске нового процесса в интерактивном сеансе из службы Windows в Vista/7.
Для не-LocalSystem сервисов основная идея:
Перечислите процесс, чтобы получить дескриптор проводника.
OpenProcessToken должен предоставить вам токен доступа. Примечание. Учетная запись, под которой работает ваша служба, должна иметь соответствующие привилегии для вызова этого API и получения токена процесса.
Как только у вас есть токен, вызовите CreateProcessAsUser с этим токеном. Этот токен уже имеет правильный идентификатор сессии.
Проблема с ответом Шрайка в том, что он не работает с пользователем, подключенным через RDP.
Вот мое решение, которое правильно определяет текущий сеанс пользователя перед созданием процесса. Он был протестирован для работы на XP и 7.
https://github.com/murrayju/CreateProcessAsUser
Все, что вам нужно, упаковано в один класс.NET с помощью статического метода:
public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible)
Это плохая идея. Хотя, вероятно, это не является абсолютно невозможным, Microsoft сделала все возможное, чтобы сделать это как можно более сложным, поскольку она позволяет так называемые Shatter Attacks. Посмотрите, что Ларри Остерман написал об этом еще в 2005 году:
Основная причина этой плохой идеи заключается в том, что интерактивные сервисы обеспечивают класс угроз, известных как "разрушающие" атаки (я полагаю, что они "разрушают окна").
Если вы выполните поиск по запросу "разбить атаку", вы сможете увидеть некоторые детали того, как работают эти угрозы безопасности. Microsoft также опубликовала статью KB 327618, которая расширяет документацию об интерактивных сервисах, а Майкл Ховард написал статью об интерактивных сервисах для библиотеки MSDN. Первоначально атаки shatter преследовали компоненты Windows, которые имели фоновые насосы сообщений окна (которые давно исправлены), но они также использовались для атаки на сторонние сервисы, которые открывают пользовательский интерфейс.
Вторая причина плохой идеи заключается в том, что флаг SERVICE_INTERACTIVE_PROCESS просто не работает правильно. Пользовательский интерфейс службы появляется в системном сеансе (обычно сеанс 0). Если, с другой стороны, пользователь работает в другом сеансе, пользователь никогда не увидит пользовательский интерфейс. Существует два основных сценария, в которых пользователь подключается к другому сеансу - службы терминалов и быстрое переключение пользователей. TS не так уж часто встречается, но в домашних сценариях, когда несколько человек используют один компьютер, часто включается FUS (например, у нас на компьютере почти 4 человека постоянно заходят на компьютер).
Третья причина, по которой интерактивные сервисы - это плохая идея, заключается в том, что интерактивные сервисы не гарантированно работают с Windows Vista:). В рамках процесса повышения безопасности, который шел в Windows Vista, интерактивные пользователи входят в сеансы, отличные от системных, - первый интерактивный пользователь запускается в сеансе 1, а не в сеансе 0. Это приводит к полному прекращению атак с разбивкой на колени - пользовательские приложения не могут взаимодействовать с окнами с высокими привилегиями, запущенными в службах.
Предложенный обходной путь будет использовать приложение в системном трее пользователя.
Если вы можете безопасно игнорировать вышеизложенные проблемы и предупреждения, вы можете следовать приведенным здесь инструкциям:
Я нашел решение здесь:
http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite
Я думаю, что это отличная ссылка.
Внедрил код @murrayju в службу Windows на W10. Запуск исполняемого файла из Program Files всегда вызывал ошибку -2 в VS. Я полагаю, что это произошло из-за начального пути Service к System32. Указание workDir не решило проблему, пока я не добавил следующую строку перед CreateProcessAsUser в StartProcessAsCurrentUser:
if (workDir != null)
Directory.SetCurrentDirectory(workDir);
PS: Это должен был быть комментарий, а не ответ, но у меня пока нет необходимой репутации. Отладка заняла у меня некоторое время, надеюсь, это сэкономит кому-то время.
Вот как я это реализовал. Он попытается запустить процесс в качестве текущего пользователя (из службы). Это основано на нескольких источниках, соединенных с чем-то, что работает.
Это действительно ДЕЙСТВИТЕЛЬНО ЧИСТЫЙ WIN32/C++, поэтому не может быть на 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;
}
Я не знаю, как это сделать в.NET, но в целом вам нужно будет использовать Win32 API CreateProcessAsUser()
функция (или ее эквивалент.NET), указывающая маркер доступа нужного пользователя и имя рабочего стола. Это то, что я использую в своих C++ сервисах, и это прекрасно работает.
Принятый ответ не работал в моем случае, так как приложение, которое я запускал, требовало прав администратора. Я создал пакетный файл для запуска приложения. Он содержал следующее:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe "%~dp0MySoft.exe"
Затем я передал расположение этого файла методу StartProcessAsCurrentUser(). Сделал трюк.