From 4ef8d205785ef2652efbc5a28faec46008da919a Mon Sep 17 00:00:00 2001 From: Justin Paul Date: Fri, 8 May 2026 09:18:59 -0400 Subject: [PATCH] Skip lpDesktop=winsta0\default for SpecificUser launches Setting lpDesktop on STARTUPINFO forces the child to open that desktop; the LogonUser-derived token in SpecificUser mode usually cannot, since winsta0\default's DACL only grants the currently-logged-in user. The result was STATUS_DLL_INIT_FAILED (exit 0xC0000142) with empty stdio. Only InteractiveUser mode needs the explicit interactive desktop - that whole point of the mode is to land in the user's session. For SpecificUser, leaving lpDesktop null lets the child inherit our service desktop, which works for headless batch tasks (AD reads, file ops, etc.). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Execution/Native/InteractiveProcessLauncher.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/WebhookServer.Core/Execution/Native/InteractiveProcessLauncher.cs b/src/WebhookServer.Core/Execution/Native/InteractiveProcessLauncher.cs index 09ce6c5..b9229ba 100644 --- a/src/WebhookServer.Core/Execution/Native/InteractiveProcessLauncher.cs +++ b/src/WebhookServer.Core/Execution/Native/InteractiveProcessLauncher.cs @@ -57,7 +57,7 @@ internal static class InteractiveProcessLauncher if (!WTSQueryUserToken(sessionId, out var userToken)) throw LastError("WTSQueryUserToken (must run as SYSTEM)"); - try { return LaunchWithToken(userToken, opts); } + try { return LaunchWithToken(userToken, opts, useInteractiveDesktop: true); } finally { CloseHandle(userToken); } } @@ -80,7 +80,7 @@ internal static class InteractiveProcessLauncher } } - try { return LaunchWithToken(token, opts); } + try { return LaunchWithToken(token, opts, useInteractiveDesktop: false); } finally { CloseHandle(token); } } @@ -93,7 +93,7 @@ internal static class InteractiveProcessLauncher return domain; } - private static LaunchResult LaunchWithToken(IntPtr sourceToken, LaunchOptions opts) + private static LaunchResult LaunchWithToken(IntPtr sourceToken, LaunchOptions opts, bool useInteractiveDesktop) { IntPtr primaryToken = IntPtr.Zero; IntPtr envBlock = IntPtr.Zero; @@ -127,7 +127,10 @@ internal static class InteractiveProcessLauncher hStdInput = stdinRead, hStdOutput = stdoutWrite, hStdError = stderrWrite, - lpDesktop = @"winsta0\default", + // For InteractiveUser we explicitly target the logged-in user's desktop. + // For SpecificUser the LogonUser-derived token typically can't open that + // DACL; leave lpDesktop null and let the new process inherit ours. + lpDesktop = useInteractiveDesktop ? @"winsta0\default" : null, }; var commandLine = BuildCommandLine(opts.FileName, opts.Arguments);