Как запустить процесс из службы 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.
И не было никакого нового блокнота.
Где я ошибаюсь?
Ответы
Ответ 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;
}
Ответ 5
Я нашел решение здесь:
http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite
Я думаю, что это отличная ссылка.
Ответ 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(). Это трюк.