Ru-Board.club
← Вернуться в раздел «Прикладное программирование»

» запуск процесса из под сервиса

Автор: ezdun
Дата сообщения: 05.09.2008 12:30
запускаемое приложение интерактивное
использую CreateProcessAsUser, запускаю приложение под нужным юзером, при этом
si.lpDesktop :=pchar('WinSta0\Default');
Проблема в следующем
Для локальных юзеров все ок, оно запускается, окно появляется, но когда заходишь удаленно, через терминал, то после запуска окно не появляется, процесс висит и даже работает - т.е. оно запускается под каким-то другим десктопом и(или) Winsta.

Простое приложение с таким кодом

Station:=GetProcessWindowStation;
Desk:=OpenInputDesktop(0, FALSE, GENERIC_ALL);
GetUserObjectInformation (Station, UOI_NAME, p, lb, Len);
memo1.Lines.Add(string(p));
GetUserObjectInformation (Desk, UOI_NAME, p, lb, Len);
memo1.Lines.Add(string(p));

при запуске через терминал выдает
WinSta0
Default

т.е. втоде все правильно, но где-же тогда окно???

Поскажите, как можно с этим справиться?
Автор: Qraizer
Дата сообщения: 05.09.2008 18:48
Дело в том, что оконная станция ещё не самый высокий уровень иерархии. Есть ещё клиентские сессии. Сильно подозреваю, но могу ошибаться, что если локальный пользователь ещё не сделал логона, то и он не увидит окна программы, когда логонётся.
Вообще, могу посоветовать создавать из сервиса отдельную нитку, имперсонировать её под нужного клиента, сменить SessionID под того же нужного клиента и только потом делать CreateProcess. Но это будет работать, только если нужный клиент уже выполнил логон. Иначе не имеет смысла ИМХО. Кстати, просто CreateProcess() будет достаточно, CreateProcessAsUser() уже не будет нужен.
Автор: ezdun
Дата сообщения: 05.09.2008 19:40
В том то и дело, что при локальных коннектах winsta0 едиственная и если запускать под default desktop, то окно приложения гарантированно отображается первому вошедшему локально (если было дапущено до логона), а вот при терминале...
А можно пример
"имперсонировать её под нужного клиента, сменить SessionID под того же нужного клиента и только потом делать CreateProcess"
а то уже весь инет перерыл, убил два дня на изучение msdn, вроде все понятно, но вот чего-то не выходит...
Автор: Qraizer
Дата сообщения: 06.09.2008 16:24
Ну дык понятно, терминальные сессии - они ж не локальные, а удалённые, хоть и интерактивные.
Хороший я как-то пример написал. Уже не первый раз пригождается. Это полный пример сервиса. Вырезать из него нужное труда составить не должно.[more]
Код:
/****************************************************************************************\
|* *|
|* Пример для интерактивного сервиса *|
|* ВНИМАНИЕ! Интерактивные сервисы не рекомендуются. *|
|* *|
\****************************************************************************************/

#define _WIN32_WINNT 0x0501
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>
#include <process.h>
#include <vector>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <string>

SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;
std::vector<HANDLE> events;
HANDLE hStart=NULL, hStop=NULL, hThread=NULL;
TOKEN_PRIVILEGES newToken, oldToken={0};
HANDLE tokenProcess=NULL;

namespace std
{

typedef std::basic_string <TCHAR> tstring;
typedef std::basic_ostringstream<TCHAR> tostringstream;
typedef std::basic_ifstream <TCHAR> tifstream;

}

// Как-то проявляемся на десктопе
int startGUI()
{
OPENFILENAME fileName;
std::vector<TCHAR> buffer(256);

fileName.lStructSize = sizeof(fileName);
fileName.pvReserved = fileName.lpstrCustomFilter = fileName.lpstrFileTitle = NULL;
fileName.hwndOwner = NULL;
fileName.lpstrInitialDir= fileName.lpstrDefExt = NULL;
fileName.lpstrFilter = _T("Log files (*.log)\0*.log\0Text files (*.txt)\0*.txt\0All files (*.*)\0*.*\0\0");
fileName.nFilterIndex = fileName.dwReserved = fileName.FlagsEx = 0;
fileName.lpstrFile =&buffer[0];
fileName.nMaxFile = buffer.size();
fileName.lpstrTitle = _T("NAV DB log file results");
fileName.Flags = OFN_DONTADDTORECENT | OFN_FORCESHOWHIDDEN;

return GetSaveFileName((fileName.lpstrTitle=static_cast<std::tostringstream&>
(std::tostringstream() << WTSGetActiveConsoleSessionId()
<< _T(" NAV DB log file results")
).str().c_str(), &fileName
));
}

// Рабочая нитка
unsigned __stdcall threadFunc(void*)
{
DWORD res;
HANDLE hpToken, htToken;

// Получить токен процесса...
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_IMPERSONATE | TOKEN_DUPLICATE, &hpToken))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - OpenProcessToken() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
return -res;
}
// ... полутить из него вторичный токен для имперсонации...
if(!DuplicateToken(hpToken, SecurityImpersonation, &htToken))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - DuplicateToken() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
CloseHandle(hpToken);
return -res;
}
// ...и имперсонировать им токен нитки
if(!SetThreadToken(NULL, htToken))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - SetThreadToken() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
CloseHandle(htToken);
CloseHandle(hpToken);
return -res;
}
// Зачистка
CloseHandle(hpToken);
CloseHandle(htToken);

// Ждём сигнала и циклим, пока не произойдёт ошибка
while((res=WaitForMultipleObjects(events.size(), &events[0], FALSE, INFINITE)) == WAIT_OBJECT_0+1)
{
// Подстроиться под текущий логин

// Получить текущий сеанс.
// Это для WinXP с её Fast User Switch. В реальной терминалсерверной среде лучше оперировать
// иными критериями, например, можно перечислить все сеансы функцией WTSEnumerateSessions()
DWORD sessionID=WTSGetActiveConsoleSessionId();
HANDLE ppToken;

// Взять имперсонированный токен
if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | TOKEN_IMPERSONATE | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, FALSE, &hpToken))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - OpenThreadToken() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
SetEvent(hStart);
continue;
}
// Подменить в нём ID сеанса...
if(!SetTokenInformation(hpToken, TokenSessionId, &sessionID, sizeof(sessionID)))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - SetTokenInformation() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
CloseHandle(hpToken);
SetEvent(hStart);
continue;
}
// ...получить первичный токен
if(!DuplicateTokenEx(hpToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &ppToken))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - DuplicateTokenEx() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
if(!ImpersonateSelf(SecurityImpersonation))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - ImpersonateSelf() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
}
CloseHandle(hpToken);
SetEvent(hStart);
continue;
}

// Подготовка к старту процесса
STARTUPINFO startInfo = {sizeof(startInfo), NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0,
NULL, NULL, NULL, NULL};
PROCESS_INFORMATION processInfo;
std::tstring commandLine(GetCommandLine());

commandLine+=_T(" startGUI\0");
commandLine+=_T('\0');

// Строковые операции кончились. К сожалению, WinAPI хочет неконстантный указатель,
// поэтому придётся строку заменить на вектор.
std::vector<std::tstring::value_type> buffer(commandLine.begin(), commandLine.end());

if(!CreateProcessAsUser(ppToken, NULL, &buffer[0], NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL,
&startInfo, &processInfo))
{
DWORD res=GetLastError();

// Старт себя с параметром
MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - CreateProcessAsUser() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
CloseHandle(ppToken);
if(!ImpersonateSelf(SecurityImpersonation))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - ImpersonateSelf() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
}
CloseHandle(hpToken);
SetEvent(hStart);
continue;
}
// Подчищаем за собой
CloseHandle(hpToken);
CloseHandle(ppToken);
CloseHandle(processInfo.hProcess);
// Вернуться к имперсонированный токену
if(!ImpersonateSelf(SecurityImpersonation))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - ImpersonateSelf() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
}
// Ждём завершения запущенного себя
WaitForSingleObject(processInfo.hThread, INFINITE);
CloseHandle(processInfo.hThread);
// Сигналим главной нити
SetEvent(hStart);
}
// Вернуть токен нитки к токену процесса.
// Т.к. нитка всё равно уже завершается, то это не обязательно. Но знать об этом полезно.
RevertToSelf();
return res==WAIT_OBJECT_0;
}

// Control handler function
VOID WINAPI ControlHandler(DWORD request)
{
switch(request)
{
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
SetEvent(hStop);

AdjustTokenPrivileges(tokenProcess, FALSE, &oldToken, 0, NULL, NULL);
CloseHandle(tokenProcess);
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (hStatus, &ServiceStatus);
return;

default:
break;
}
// Report current status
SetServiceStatus (hStatus, &ServiceStatus);
}

// Service initialization
bool InitService()
{
unsigned threadID;

hStart = CreateEvent(NULL, FALSE, FALSE, NULL);
hStop = CreateEvent(NULL, FALSE, FALSE, NULL);

events.push_back(hStop);
events.push_back(hStart);

hThread=(HANDLE)_beginthreadex(NULL, 0, threadFunc, NULL, 0, &threadID);

return hStart!=NULL && hStop!=NULL && hThread!=NULL;
}

VOID WINAPI ServiceMain(DWORD, LPTSTR*)
{
DWORD retSize;

ServiceStatus.dwServiceType = SERVICE_WIN32;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;

if ((hStatus=RegisterServiceCtrlHandler(_T("RehostStatus"), ControlHandler)) == 0)
// Registering Control Handler failed
return;
// Подправить свой токен
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &tokenProcess))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - OpenProcessToken() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
else
{
// Найти в системе привелегию работы в режиме операционной системы
if(!LookupPrivilegeValue(NULL, SE_TCB_NAME, &newToken.Privileges[0].Luid))
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - LookupPrivilegeValue() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
else
{
// Запросить разрешение этой привелегии
newToken.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
newToken.PrivilegeCount=1;
SetLastError(ERROR_SUCCESS);
// Применить привелегии к токену
if(!AdjustTokenPrivileges(tokenProcess, FALSE, &newToken, sizeof(oldToken),
&oldToken, &retSize) || GetLastError()==ERROR_NOT_ALL_ASSIGNED)
{
DWORD res=GetLastError();

MessageBox(NULL, static_cast<std::tostringstream&>(std::tostringstream()<<res<<_T(" - AdjustTokenPrivileges() fail")).str().c_str(), _T("RehostStatus"), MB_SERVICE_NOTIFICATION | MB_OK);
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
return;
}
}
}
if (!InitService())
{
// Initialization failed
ServiceStatus.dwCurrentState = SERVICE_STOPPED;
ServiceStatus.dwWin32ExitCode = -1;
SetServiceStatus(hStatus, &ServiceStatus);
}
else
{ // Ok.
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);

// Работаен, пока не выключат
while(WaitForSingleObject(hThread, 1000)==WAIT_TIMEOUT) // Периодически проверяем...
if(std::tifstream("c:\\temp\\check.txt").is_open()) // ...что-то
SetEvent(hStart), // и при случае сигналим нитке
WaitForSingleObject(hStart, INFINITE); // и ждём её реакции
}

std::for_each(events.begin(), events.end(), CloseHandle);
CloseHandle(hThread);
}

int _tmain(int argn, TCHAR *argv[])
{
if(argn==2 && std::tstring(argv[1]) == _T("startGUI")) return startGUI();

SERVICE_TABLE_ENTRY ServiceTable[2];

ServiceTable[0].lpServiceName = _T("RehostStatus");
ServiceTable[0].lpServiceProc = ServiceMain;

ServiceTable[1].lpServiceName = NULL;
ServiceTable[1].lpServiceProc = NULL;
// Start the control dispatcher thread for our service
StartServiceCtrlDispatcher(ServiceTable);
}
Автор: ezdun
Дата сообщения: 08.09.2008 10:45
Спасибо, появилась ясность насчет сессий, но все равно не помогает, вот еще есть короткий пример похожий на ваш, с ним то же самое - если все это делать из под обычного приложения, все работает и запускается с окном, но вот из под сервиса окно все равно не появляется, вдобавок, при запуске из под сервиса, процесс получает привелегии SYSTEM, поскольку Handle := GetCurrentProcess;
Уточнения - не работает на терминале, на локальном компьютере все ок, причем, при одновременном логине нескольких пользователей при переключении между ними, все запускается как надо.

procedure StartClient(SessionId: DWord);
var
Handle: THandle;
Token,UserToken: THandle;
Length: Cardinal;
StartUpInfo: TStartUpInfo;
ProcInfo: TProcessInformation;
CommandLine: WideString;
Desktop: WideString;

const
TokenSessionId = 12;

begin
CommandLine := '"' + Application.ExeName + '" ';
Handle := GetCurrentProcess;
Length := SizeOf(DWord);
FillChar(StartUpInfo, SizeOf(StartUpInfo), #0);
with StartUpInfo do
begin
cb := SizeOf(StartupInfo);
dwFlags := STARTF_USESHOWWINDOW;
Desktop := 'WinSta0\Default';
lpDesktop:= PWideChar(Desktop);
end;
if OpenProcessToken(Handle,
TOKEN_DUPLICATE,
Token) then
try
if DuplicateTokenEx(Token,
MAXIMUM_ALLOWED,
nil,
SecurityImpersonation,
TokenPrimary,
UserToken) then
try
if SetTokenInformation(UserToken,

TTokenInformationClass(TokenSessionId),
@SessionId,
Length) then
CreateProcessAsUserW(UserToken,
nil,
PWideChar(CommandLine),
nil,
nil,
False,
NORMAL_PRIORITY_CLASS,
nil,
nil,
StartUpInfo,
ProcInfo);
finally
CloseHandle(UserToken);
end;
finally
CloseHandle(Token);
end;
end;
Автор: ezdun
Дата сообщения: 08.09.2008 18:21
Нарыл еще вот. Здесь все работает как надо, процесс запускается от текущего юзера, НО при заходе терминалом на server2003, WTSQueryUserToken возврашает FALSE. И дело дальше не идет... Почему так может быть?
(на локальной машине, где все ок WtsGetActiveConsoleSessionID()=0, на сервере, где не работаетWtsGetActiveConsoleSessionID()=4 )


if WTSQueryUserToken(WtsGetActiveConsoleSessionID, UserToken) then
begin
if DuplicateTokenEx(userToken,
MAXIMUM_ALLOWED,
nil,
SecurityImpersonation,
TokenPrimary,
Token)
then params.Add('duplicate ok')
Else params.Add('duplicate NOT');

if SetTokenInformation(Token,
TTokenInformationClass(TokenSessionId),
@SessionId,
Length) then params.Add('SetTokenInformation ok')

Else params.Add('SetTokenInformation NOT');

if CreateProcessAsUserW(Token...
Автор: Qraizer
Дата сообщения: 08.09.2008 18:53
По поводу первого. Весьма кратко, и потому несколько неверно, но суть будет ясна. Ты не можешь изменить существующий токен процесса, т.к. он, будучи первичным, не может быть имперсонирован. Имперсонироваться могут вторичные токены, которые ссылаются на некий первичный и содержат отличия от него. Вторичные токены могут быть применены к нити. Вот для нити ты сможешь сменить SessionID после того, как имперсонируешь её под клиента. Зато вторичный токен не может быть использован для старта нового процесса, поэтому после имперсонирования и смены SessionID его нужно будет преобразовать в первичный вызовом DuplicateTokenEx(). Поэтому твой укороченный вариант и не работает.
По поводу второго. WtsGetActiveConsoleSessionID() возвращает именно что SessionID активного консольного подключения. Повторяю, терминальные подключения не консольные, а удалённые. Естественно, что при отсутствии консольных подключений ты получишь FALSE, а при наличии - совсем не тот SessionID, который хотел. В терминал-серверном окружении тебе надо как-то иначе определять SessionIDs клиентов. Как-то же ты определяешь, откуда к сервису пришёл запрос на старт процесса? Отсюда и копай.
Автор: ezdun
Дата сообщения: 14.09.2008 10:56
Вопрос решен.
Для локальных юзеров

conSessId:=WtsGetActiveConsoleSessionID();
if WTSQueryUserToken(conSessId, ppToken) then
begin
if DuplicateTokenEx(userToken,
MAXIMUM_ALLOWED,
nil,
SecurityImpersonation,
TokenPrimary,
Token) then CreateProcessAsUser...
end;

Опробовано на сервере и рабочих станциях, приложение запускается имеено под текущим юзером на его рабочем столе с его правами.

SetTokenInformation использовать НЕ надо - начинаются глюки с попаданием на нужный десктоп...

При терминале WTSQueryUserToken не работает, поэтому токен получаем через юзера - делаем простейшее приложение starter, которую юзер запускает и которое передает свой handle службе, ну и далее

handle:= OpenProcess(PROCESS_QUERY_INFORMATION ,False,handleStarter);
if handle>0 then
begin
params.Add('открыт HANDLE start_robot_serv.exe ok');
if OpenProcessToken(Handle,TOKEN_DUPLICATE,ppToken) then
params.Add('add Token OpenProcces');
CloseHandle(handle);
end;
if DuplicateTokenEx(userToken,
MAXIMUM_ALLOWED,
nil,
SecurityImpersonation,
TokenPrimary,
Token) then CreateProcessAsUser...
end;
////////////////////////////////////////////////////////////////////////
Но, как оказалось, приложение, запущенное таким образом, является обычным приложением и гаситься при логоффе, что логично (я то хотел исхитриться и сделать так, чтобы после запуска оно висело как служба, и для локальных юзеров у меня это получалось...), так что, все же, пришлось все, что должно висеть перенести в службу и написать отдельное приложение, визуализирующее работу службы - зато теперь все надежно работает на всех вариантах...

Страницы: 1

Предыдущая тема: Delphi for PHP: MySQL кириллица - знаки вопросов


Форум Ru-Board.club — поднят 15-09-2016 числа. Цель - сохранить наследие старого Ru-Board, истории становления российского интернета. Сделано для людей.