2013-11-28 4 views
3

В процессе тестовой среды A должен запустить процесс B под разными учетными данными пользователя (скажем, _limited_user) с использованием API CreateProcessWithLogonW. lpStartupInfo->lpDesktop NULL, поэтому процесс B должен работать на той же рабочей станции и оконной станции, что и процесс A.процесс, запущенный с сервиса CreateProcessWithLogonW, сразу заканчивается

Все работает нормально, когда процесс A запускается вручную (как _glagolig). Но когда процесс A запускается службой тестовых платформ (выполняется под учетной записью пользовательской учетной записи _test_framework), которая не работает. CreateProcessWithLogonW возвращает успех, но процесс B не может выполнять какую-либо работу. Он заканчивается сразу, потому что его conhost.exe не может инициализировать user32.dll и возвращает 0xC0000142 (я получил это из журналов procmon.exe SysInternals). Таким образом, похоже, проблема связана с доступом к настольной/оконной станции.

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

Также я хотел бы найти обходное решение, сохраняя при этом общую схему одинаково (служба тестового фрейма под учетной записью _test_framework должна запустить процесс B с именем _limited_user).

+1

Пользователь, который регистрируется вручную, запускается на интерактивном рабочем столе.Сервисы не запускаются в интерактивном сеансе/рабочем столе и не должны создавать интерактивные процессы в рамках его сеанса, так как пользователь никогда не сможет его увидеть. Если процесс B является интерактивным процессом (который, скорее всего, нужен ему 'user32.dll'), тогда вы должны указывать интерактивный рабочий стол в' lpStartupInfo-> szDesktop' для его запуска, чтобы пользователь мог его увидеть. –

+0

@Remy Lebeau Process B будет работать в контексте обслуживания просто отлично, если служба не представила отдельного пользователя. Процесс B не нужен user32.dll, но conhost.exe делает. Процесс B не разрешен, потому что conhost.exe терпит неудачу. – glagolig

+0

@Harry Это STATUS_DLL_INIT_FAILED. http://stackoverflow.com/questions/13425106/createprocess-succeeds-but-getexitcodeprocess-returns-c0000142 – glagolig

ответ

1

Приложение: в соответствии с документацией должно быть возможно использование CreateProcessAsUser без выполнения этих действий, если вы не хотите, чтобы новый процесс взаимодействовал с пользователем. Я еще не тестировал это, но, полагая, что это правда, это было бы намного проще для многих сценариев.

Оказалось, что Microsoft уже предоставила образец кода для управления правами на использование оконной станции и рабочего стола под заголовком Starting an Interactive Client Process in C++. Начиная с Windows Vista запуск подпроцесса в оконной станции по умолчанию уже недостаточно, чтобы позволить подпроцессу взаимодействовать с пользователем, но он позволяет подпроцессу работать с альтернативными учетными данными пользователя.

Следует отметить, что код Microsoft использует LogonUser и CreateProcessAsUser, а не CreateProcessWithLogonW. Это означает, что услуге потребуется SE_INCREASE_QUOTA_NAME привилегия и, возможно, SE_ASSIGNPRIMARYTOKEN_NAME. Может быть предпочтительнее заменить CreateProcessWithTokenW, который требует только SE_IMPERSONATE_NAME. Я не рекомендую использовать CreateProcessWithLogonW в этом контексте, потому что он не позволяет вам получить доступ к SID входа в систему до запуска подпроцесса.

Я написал минимальный сервис для демонстрации использования образец кода от Microsoft:

/*******************************************************************/ 

#define _WIN32_WINNT 0x0501 

#include <windows.h> 

/*******************************************************************/ 

// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa379608%28v=vs.85%29.aspx 
// "Starting an Interactive Client Process in C++" 

BOOL AddAceToWindowStation(HWINSTA hwinsta, PSID psid); 
BOOL AddAceToDesktop(HDESK hdesk, PSID psid); 
BOOL GetLogonSID (HANDLE hToken, PSID *ppsid); 
VOID FreeLogonSID (PSID *ppsid); 
BOOL StartInteractiveClientProcess (
    LPTSTR lpszUsername, // client to log on 
    LPTSTR lpszDomain,  // domain of client's account 
    LPTSTR lpszPassword, // client's password 
    LPTSTR lpCommandLine // command line to execute 
); 

/*******************************************************************/ 

const wchar_t displayname[] = L"Demo service for CreateProcessWithLogonW"; 
const wchar_t servicename[] = L"demosvc-createprocesswithlogonw"; 

DWORD dwWin32ExitCode = 0, dwServiceSpecificExitCode = 0; 

/*******************************************************************/ 

#define EXCEPTION_USER 0xE0000000 
#define FACILITY_USER_DEMOSVC 0x0001 
#define EXCEPTION_USER_LINENUMBER (EXCEPTION_USER | (FACILITY_USER_DEMOSVC << 16)) 

HANDLE eventloghandle; 

/*******************************************************************/ 

wchar_t subprocess_username[] = L"harry-test1"; 
wchar_t subprocess_domain[] = L"scms"; 
wchar_t subprocess_password[] = L"xyzzy916"; 
wchar_t subprocess_command[] = L"cmd.exe /c dir"; 

void demo(void) 
{ 
    if (!StartInteractiveClientProcess(subprocess_username, subprocess_domain, subprocess_password, subprocess_command)) 
    { 
     const wchar_t * strings[] = {L"Creating subprocess failed."}; 
     DWORD err = GetLastError(); 
     ReportEventW(eventloghandle, 
        EVENTLOG_ERROR_TYPE, 
        0, 
        2, 
        NULL, 
        _countof(strings), 
        sizeof(err), 
        strings, 
        &err); 
     return; 
    } 

    { 
     const wchar_t * strings[] = {L"Creating subprocess succeeded!"}; 
     ReportEventW(eventloghandle, 
        EVENTLOG_INFORMATION_TYPE, 
        0, 
        1, 
        NULL, 
        _countof(strings), 
        0, 
        strings, 
        NULL); 
    } 

    return; 
} 

/*******************************************************************/ 

CRITICAL_SECTION service_section; 

SERVICE_STATUS service_status;      // Protected by service_section 

SERVICE_STATUS_HANDLE service_handle = 0;   // Constant once set, so can be used from any thread 

static DWORD WINAPI ServiceHandlerEx(DWORD control, DWORD eventtype, LPVOID lpEventData, LPVOID lpContext) 
{ 
    if (control == SERVICE_CONTROL_INTERROGATE) 
    { 
     EnterCriticalSection(&service_section); 
     if (service_status.dwCurrentState != SERVICE_STOPPED) 
     { 
     SetServiceStatus(service_handle, &service_status); 
     } 
     LeaveCriticalSection(&service_section); 
     return NO_ERROR; 
    } 

    return ERROR_CALL_NOT_IMPLEMENTED; 
} 

static VOID WINAPI ServiceMain(DWORD argc, LPTSTR * argv) 
{ 
    SERVICE_STATUS status; 

    EnterCriticalSection(&service_section); 

    service_handle = RegisterServiceCtrlHandlerEx(argv[0], ServiceHandlerEx, NULL); 
    if (!service_handle) RaiseException(EXCEPTION_USER_LINENUMBER | __LINE__, EXCEPTION_NONCONTINUABLE, 0, NULL); 

    service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; 
    service_status.dwCurrentState = SERVICE_RUNNING; 
    service_status.dwControlsAccepted = 0; 
    service_status.dwWin32ExitCode = STILL_ACTIVE; 
    service_status.dwServiceSpecificExitCode = 0; 
    service_status.dwCheckPoint = 0; 
    service_status.dwWaitHint = 500; 

    SetServiceStatus(service_handle, &service_status); 

    LeaveCriticalSection(&service_section); 

    /************** service main function **************/ 

    { 
     const wchar_t * strings[] = {L"Service started!"}; 
     ReportEventW(eventloghandle, 
        EVENTLOG_INFORMATION_TYPE, 
        0, 
        2, 
        NULL, 
        _countof(strings), 
        0, 
        strings, 
        NULL); 
    } 

    demo(); 

    /************** service shutdown **************/ 

    EnterCriticalSection(&service_section);  

    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; 
    status.dwCurrentState = service_status.dwCurrentState = SERVICE_STOPPED; 
    status.dwControlsAccepted = 0; 
    status.dwCheckPoint = 0; 
    status.dwWaitHint = 500; 
    status.dwWin32ExitCode = dwWin32ExitCode; 
    status.dwServiceSpecificExitCode = dwServiceSpecificExitCode; 

    LeaveCriticalSection(&service_section); 

    SetServiceStatus(service_handle, &status);  /* NB: SetServiceStatus does not return here if successful, 
                so any code after this point will not normally run. */ 
    return; 
} 

int wmain(int argc, wchar_t * argv[]) 
{ 
    const static SERVICE_TABLE_ENTRY servicetable[2] = { 
     {(wchar_t *)servicename, ServiceMain}, 
     {NULL, NULL} 
    }; 

    InitializeCriticalSection(&service_section); 

    eventloghandle = RegisterEventSource(NULL, displayname); 
    if (!eventloghandle) return GetLastError(); 

    { 
     const wchar_t * strings[] = {L"Executable started!"}; 
     ReportEventW(eventloghandle, 
        EVENTLOG_INFORMATION_TYPE, 
        0, 
        2, 
        NULL, 
        _countof(strings), 
        0, 
        strings, 
        NULL); 
    } 

    if (StartServiceCtrlDispatcher(servicetable)) return 0; 
    return GetLastError(); 
} 

Это должно быть связано с примерами кода Microsoft. После этого вы можете установить службу с помощью команды sc:

sc create demosvc-createprocesswithlogonw binPath= c:\path\demosvc.exe DisplayName= "Demo service for CreateProcessWithLogonW" 
+0

Я уже видел этот образец MSDN. Я не мог заставить его работать на моих целевых машинах. (Я подозреваю, что это не работает, когда никто не регистрируется в интерактивном режиме, так что winsta0 не существует. Я вхожу в систему через удаленный рабочий стол. Не уверен, что мои подозрения правильны.) – glagolig

+0

'winsta0' всегда существует в каждом сеансе независимо от того, зарегистрирован ли кто-либо или нет. Возможно, учетная запись, на которой выполнялась служба, не имела необходимых привилегий для вызова 'CreateProcessAsUser'? Какой код ошибки вы получили? –

+0

Пришло время попробовать еще раз ... Ошибка CreateProcessAsUser, ошибка 1314 (0x522) ERROR_PRIVILEGE_NOT_HELD. Мне придется играть с привилегиями вызывающего. Но даже если я сделаю эту работу, было бы интересно узнать, почему я не мог просто запустить ее на оконной станции службы. (Так как мой обход с отдельной службой работает, интерактивный рабочий стол не требуется.) – glagolig

1

Я закончил с следующим обходным путем. Я настроил другую службу, запущенную как _limited_user, и начал работу по требованию. Затем тестовая среда может запускать и останавливать ограниченную службу пользователя. И ограниченная служба пользователя может запускать процессы, необходимые для моих тестов.

Обходное решение работает. Следовательно, мои процессы не требуют интерактивных рабочих столов (даже если они загружают user32.dll). Очевидно, user32.dll можно загрузить в неинтерактивном контексте. Но есть некоторая неизвестная тонкость, которая не позволяет процессу запускаться при запуске непосредственно из службы тестовой среды с помощью CreateProcessWithLogonW.

+1

Windows создает фиктивные поверхности дисплея по мере необходимости, поэтому вам не нужно быть в интерактивном контексте для использования функций графического интерфейса. Но вам нужны правильные разрешения для оконной станции и рабочего стола, независимо от того, будете ли вы использовать графические функции или нет. –

Смежные вопросы