Addendum: according to the documentation, it should be possible to use CreateProcessAsUser without going through these steps, provided you don't want the new process to interact with the user. I haven't tested this yet, but assuming it is true, that would be a much simpler solution for many scenarios.
It turns out that Microsoft has already provided sample code to manipulate window station and desktop access rights, under the title Starting an Interactive Client Process in C++. As of Windows Vista, starting a subprocess in the default window station is no longer enough to allow the subprocess to interact with the user, but it does allow the subprocess to run with alternate user credentials.
I should note that Microsoft's code uses LogonUser
and CreateProcessAsUser
rather than CreateProcessWithLogonW
. This does mean that the service will need SE_INCREASE_QUOTA_NAME
privilege and possibly SE_ASSIGNPRIMARYTOKEN_NAME
. It may be preferable to substitute CreateProcessWithTokenW
which only requires SE_IMPERSONATE_NAME
. I don't recommend the use of CreateProcessWithLogonW
in this context because it doesn't allow you to access the logon SID prior to launching the subprocess.
I wrote a minimal service to demonstrate using Microsoft's sample code:
/*******************************************************************/
#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();
}
This must be linked with Microsoft's sample code. You can then install the service using the sc
command:
sc create demosvc-createprocesswithlogonw binPath= c:pathdemosvc.exe DisplayName= "Demo service for CreateProcessWithLogonW"