I have a service that is responsible for starting/monitoring an interactive process in user session once user logins and console session connects. Service is set to start automatically so its up and running before user login. All work fine on first login and i get the user process started/restarted correctly.
What happens is that if user signs out and re logins service is no longer able to start a user process correctly. CreateProcessAsUser returns no error but as soon as user process is started its exiting with -1073741502 (0xC0000142) exit code.
If i restart the service then its again able to launch user process without any errors.
I can post full source of how the service creates user process if required.
Edit
try
{
//WE ALREADY HAVE A CLIENT ATTACHED , DONT START A NEW ONE
if (ClientProcessId != null)
return;
var ACTIVE_CONSOLE_SESSION = ListSessions()
.Where(SESSION => SESSION.State == WTS_CONNECTSTATE_CLASS.WTSActive)
.FirstOrDefault();
if (ACTIVE_CONSOLE_SESSION == null)
return;
CONSOLE_SESSION_ID = (uint)ACTIVE_CONSOLE_SESSION.Id;
IntPtr USER_TOKEN = IntPtr.Zero;
IntPtr ENVIRONMENT = IntPtr.Zero;
IntPtr LINKED_TOKEN = IntPtr.Zero;
try
{
try
{
if (!Wtsapi32.WTSQueryUserToken(CONSOLE_SESSION_ID.Value, out USER_TOKEN))
throw new Win32Exception();
}
catch (Win32Exception wex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(Wtsapi32.WTSQueryUserToken)} : console session id {CONSOLE_SESSION_ID} error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
throw;
}
try
{
if (!Userenv.CreateEnvironmentBlock(out ENVIRONMENT, USER_TOKEN, true))
throw new Win32Exception();
}
catch (Win32Exception wex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(Userenv.CreateEnvironmentBlock)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
throw;
}
try
{
LINKED_TOKEN = CoreProcess.GetLinkedTokeIfRequiered(USER_TOKEN);
}
catch (Win32Exception wex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(CoreProcess.GetLinkedTokeIfRequiered)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
throw;
}
//GET PROCESS PATHS
string FILE_NAME = EntryPoint.PROCESS_FULL_FILE_NAME;
string WORKING_DIRECTORY = EntryPoint.PROCESS_DIRECTORY;
//GET CURRENT COMMAND LINE ARGUMENTS
var CMD_ARGS = Environment.GetCommandLineArgs();
//FIRST ARGUMENT WILL ALWAYS HAVE FULL PROCESS PATH,OTHER ARGUMENTS ARE OPTIONAL
var ARGUMENTS_STRING = CMD_ARGS
.Skip(1)
.DefaultIfEmpty()
.Aggregate((first, next) => ' ' + first + ' ' + next);
var ARGUMENTS = new StringBuilder(ARGUMENTS_STRING);
var START_INFO = new STARTUPINFO();
START_INFO.cb = Marshal.SizeOf(START_INFO);
START_INFO.lpDesktop = @"winsta0\default";
var PROCESS_INFO = new PROCESS_INFORMATION();
uint dwCreationFlags = NORMAL_PRIORITY_CLASS | (int)(PROCESS_CREATE_FLAG.CREATE_NEW_CONSOLE | PROCESS_CREATE_FLAG.CREATE_UNICODE_ENVIRONMENT);
try
{
if (!AdvApi32.CreateProcessAsUser(LINKED_TOKEN,
FILE_NAME,
ARGUMENTS,
IntPtr.Zero,
IntPtr.Zero,
true,
dwCreationFlags,
ENVIRONMENT,
WORKING_DIRECTORY,
ref START_INFO,
out PROCESS_INFO))
throw new Win32Exception();
if (PROCESS_INFO.hThread != IntPtr.Zero)
{
ClientProcessId = PROCESS_INFO.dwProcessId;
ClientSessionId = CONSOLE_SESSION_ID;
EntryPoint.TryWriteToCacheLog($"{nameof(AdvApi32.CreateProcessAsUser)} : Created porocess {ClientProcessId} in session {CONSOLE_SESSION_ID}.");
}
}
catch (Win32Exception wex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(AdvApi32.CreateProcessAsUser)} : error {wex.ErrorCode} , native error {wex.NativeErrorCode}");
throw;
}
}
catch (Win32Exception wex)
{
switch (wex.NativeErrorCode)
{
case 5:
case 1008:
tryCount++;
if (tryCount >= START_RETRIES)
throw;
Thread.Sleep(RETRY_WAIT_SPAN);
if (DisableCallBacks)
return;
CreateProcess(tryCount);
break;
default:
throw;
}
}
catch
{
throw;
}
finally
{
Userenv.DestroyEnvironmentBlock(ENVIRONMENT);
Kernel32.CloseHandle(USER_TOKEN);
if (USER_TOKEN != LINKED_TOKEN)
Kernel32.CloseHandle(LINKED_TOKEN);
}
}
catch (Exception ex)
{
EntryPoint.TryWriteToCacheLog($"{nameof(CreateProcess)} failed after {tryCount} retries, console seesion id {(CONSOLE_SESSION_ID != null ? CONSOLE_SESSION_ID.ToString() : "Unobtained")}.", ex.ToString());
}
finally
{
Monitor.Exit(CREATE_LOCK);
}
public static TOKEN_ELEVATION_TYPE GetTokenElevationLevel(IntPtr hToken)
{
int TOKEN_INFO_LENGTH = Marshal.SizeOf(typeof(int));
IntPtr TOKEN_INFO_POINTER = Marshal.AllocHGlobal(TOKEN_INFO_LENGTH);
try
{
if (!AdvApi32.GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevationType, TOKEN_INFO_POINTER, TOKEN_INFO_LENGTH, out TOKEN_INFO_LENGTH))
throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
return (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(TOKEN_INFO_POINTER);
}
catch
{
throw;
}
finally
{
if (TOKEN_INFO_POINTER != IntPtr.Zero)
Marshal.FreeHGlobal(TOKEN_INFO_POINTER);
}
}
public static IntPtr GetLinkedTokeIfRequiered(IntPtr hToken)
{
var TOKEN_ELEVATION = GetTokenElevationLevel(hToken);
if (TOKEN_ELEVATION != TOKEN_ELEVATION_TYPE.TokenElevationTypeLimited)
return hToken;
int TOKEN_INFO_LENGHT = Marshal.SizeOf(typeof(IntPtr));
IntPtr LINKED_TOKEN_INFO = Marshal.AllocHGlobal(TOKEN_INFO_LENGHT);
try
{
if (!AdvApi32.GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenLinkedToken, LINKED_TOKEN_INFO, TOKEN_INFO_LENGHT, out TOKEN_INFO_LENGHT))
throw new Win32Exception();
return Marshal.ReadIntPtr(LINKED_TOKEN_INFO);
}
finally
{
if (LINKED_TOKEN_INFO != IntPtr.Zero)
Marshal.Release(LINKED_TOKEN_INFO);
}
}
Thanks you for posting the rest of the code. I see you are launching the process elevated. I added this to my test service and it still works correctly.
But I think the problem might be this line in GetLinkedTokeIfRequiered()
:
Marshal.Release(LINKED_TOKEN_INFO);
That should obviously be:
Marshal.FreeHGlobal(LINKED_TOKEN_INFO);
Fix that and it might just work. As things are, I'm amazed it's not crashing.
Not easy for me, digging through this. C# interop not my strong suit.
For the OP's benefit, complete source code of my test service, written in C++, which works:
#include <windows.h>
#include <wtsapi32.h>
#include <userenv.h>
#include <tchar.h>
#include <stdio.h>
#pragma comment (lib, "user32.lib")
#pragma comment (lib, "wtsapi32.lib")
#pragma comment (lib, "userenv.lib")
#pragma comment (lib, "advapi32.lib")
DWORD report_error (const char *operation)
{
DWORD err = GetLastError ();
return err;
}
// Launch notepad as currently logged-on user
DWORD LaunchProcess (DWORD SessionId, const char **failed_operation)
{
HANDLE hToken;
BOOL ok = WTSQueryUserToken (SessionId, &hToken);
if (!ok)
return report_error (*failed_operation = "WTSQueryUserToken");
void *environment = NULL;
ok = CreateEnvironmentBlock (&environment, hToken, TRUE);
if (!ok)
{
CloseHandle (hToken);
return report_error (*failed_operation = "CreateEnvironmentBlock");
}
TOKEN_LINKED_TOKEN lto;
DWORD nbytes;
ok = GetTokenInformation (hToken, TokenLinkedToken, <o, sizeof (lto), &nbytes);
if (ok)
{
CloseHandle (hToken);
hToken = lto.LinkedToken;
}
STARTUPINFO si = { sizeof (si) } ;
PROCESS_INFORMATION pi = { } ;
si.lpDesktop = "winsta0\\default";
// Do NOT want to inherit handles here, surely
DWORD dwCreationFlags = NORMAL_PRIORITY_CLASS | /* CREATE_NEW_CONSOLE | */ CREATE_UNICODE_ENVIRONMENT;
ok = CreateProcessAsUser (hToken, "c:\\windows\\system32\\notepad.exe", NULL, NULL, NULL, FALSE,
dwCreationFlags, environment, NULL, &si, &pi);
DestroyEnvironmentBlock (environment);
CloseHandle (hToken);
if (!ok)
return report_error (*failed_operation = "CreateProcessAsUser");
CloseHandle (pi.hThread);
CloseHandle (pi.hProcess);
return 0;
}
// Determine the session ID of the currently logged-on user
DWORD GetCurrentSessionId ()
{
WTS_SESSION_INFO *pSessionInfo;
DWORD n_sessions = 0;
BOOL ok = WTSEnumerateSessions (WTS_CURRENT_SERVER, 0, 1, &pSessionInfo, &n_sessions);
if (!ok)
return 0;
DWORD SessionId = 0;
for (DWORD i = 0; i < n_sessions; ++i)
{
if (pSessionInfo [i].State == WTSActive)
{
SessionId = pSessionInfo [i].SessionId;
break;
}
}
WTSFreeMemory (pSessionInfo);
return SessionId;
}
#define SERVICE_NAME __T ("demo_service")
bool quit;
// CtrlHandler callback
DWORD WINAPI CtrlHandler (DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
if (dwControl == SERVICE_CONTROL_STOP)
quit = true;
return NO_ERROR;
}
// SvcMain callback
VOID WINAPI SvcMain (DWORD dwArgc, LPTSTR *lpszArgv)
{
// Register for callbacks
SERVICE_STATUS_HANDLE sh = RegisterServiceCtrlHandlerEx (SERVICE_NAME, CtrlHandler, NULL);
// Tell the SCM that we are up and running
SERVICE_STATUS ss = { };
ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ss.dwCurrentState = SERVICE_RUNNING;
ss.dwControlsAccepted = SERVICE_ACCEPT_STOP;
SetServiceStatus (sh, &ss);
TCHAR buf [256];
const TCHAR *title = __T ("(c) 2018 Contoso Corporation");
while (!quit)
{
DWORD response = IDOK;
DWORD SessionId = GetCurrentSessionId ();
if (SessionId == 0)
{
Sleep (2000);
continue;
}
// Pop-up a message on the screen of the currently logged-on user (session 1)
_stprintf (buf, __T ("Ready to launch..., SessionId = %d"), SessionId);
WTSSendMessage (WTS_CURRENT_SERVER_HANDLE, SessionId, (TCHAR *) title, _tcslen (title),
buf, _tcslen (buf), MB_OKCANCEL, 0, &response, TRUE);
if (response == IDCANCEL)
break;
const char *failed_operation = "";
DWORD dwResult = LaunchProcess (SessionId, &failed_operation);
// Report results
_stprintf (buf, __T ("LaunchProcess returned %lx from %s"), dwResult, failed_operation);
WTSSendMessage (WTS_CURRENT_SERVER_HANDLE, SessionId, (char *) title, _tcslen (title),
buf, _tcslen (buf), MB_OK, 0, &response, TRUE);
FILE *logfile = fopen ("g:\\temp\\service.log", "at");
if (logfile)
{
fprintf (logfile, "%s\n", buf);
fclose (logfile);
}
}
// Tell the SCM we are going away and exit
ss.dwCurrentState = SERVICE_STOPPED;
SetServiceStatus (sh, &ss);
}
// main
int main (void)
{
SERVICE_TABLE_ENTRY DispatchTable [] =
{
{ SERVICE_NAME, SvcMain },
{ NULL, NULL }
};
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
StartServiceCtrlDispatcher (DispatchTable);
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With