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) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 09:18:59 -04:00
parent 8b855ec9b9
commit 4ef8d20578
@@ -57,7 +57,7 @@ internal static class InteractiveProcessLauncher
if (!WTSQueryUserToken(sessionId, out var userToken)) if (!WTSQueryUserToken(sessionId, out var userToken))
throw LastError("WTSQueryUserToken (must run as SYSTEM)"); throw LastError("WTSQueryUserToken (must run as SYSTEM)");
try { return LaunchWithToken(userToken, opts); } try { return LaunchWithToken(userToken, opts, useInteractiveDesktop: true); }
finally { CloseHandle(userToken); } 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); } finally { CloseHandle(token); }
} }
@@ -93,7 +93,7 @@ internal static class InteractiveProcessLauncher
return domain; 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 primaryToken = IntPtr.Zero;
IntPtr envBlock = IntPtr.Zero; IntPtr envBlock = IntPtr.Zero;
@@ -127,7 +127,10 @@ internal static class InteractiveProcessLauncher
hStdInput = stdinRead, hStdInput = stdinRead,
hStdOutput = stdoutWrite, hStdOutput = stdoutWrite,
hStdError = stderrWrite, 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); var commandLine = BuildCommandLine(opts.FileName, opts.Arguments);