You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从SYSTEM账户通过CreateProcessAsUser启动的.NET 8 WinForms程序无UI显示且立即退出问题求助

从SYSTEM账户通过CreateProcessAsUser启动的.NET 8 WinForms程序无UI显示且立即退出问题求助

我最近碰到个棘手的问题,折腾了好半天没搞定,想请各位大佬帮忙分析下:

我写了两个.NET 8的小工具:

  • Launcher.exe:控制台程序,通过PsExec -s在Session 0的SYSTEM账户下运行
  • PopupClient.exe:WinForms程序,本来应该在用户的交互式桌面弹出一个测试用的MessageBox

我的想法是让Launcher借助CreateProcessAsUser把PopupClient启动到当前活跃的控制台会话里。现在看起来调用是成功的——返回码非零,GetLastError返回0,用Process.GetProcessById也能查到新生成的PID,但用户屏幕上啥反应都没有,而且PopupClient几乎是瞬间就终止了,连日志都没留下。

最小复现步骤

  1. 把两个项目都编译成自包含、单文件、win-x64格式的程序
  2. 将两个EXE放到目标机器的同一个文件夹中
  3. 从管理员权限的命令提示符执行:psexec -s "C:\Temp\PopupTest\Launcher.exe"

控制台输出如下:

=== Launcher (Session 0) ===
Current session : 0
Active console : 1
Launched PopupClient.exe PID 3656
Launcher finished.

但 MessageBox 完全没弹出来,PopupClient也没有生成任何预设的日志文件。

我的核心疑问

  • 为什么PopupClient.exe从SYSTEM账户通过CreateProcessAsUser启动时会静默退出?明明双击交互式运行这个EXE是完全正常的啊?
  • 从System上下文切换到活跃用户上下文启动程序时,我是不是漏了什么关键的配置或权限步骤?

下面是两个程序的完整代码:

Launcher.exe 代码

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.AppDomain;

namespace Launcher
{
    internal class Program
    {
        [DllImport("wtsapi32.dll", SetLastError = true)]
        private static extern bool WTSQueryUserToken(uint sessionId, out IntPtr token);

        [DllImport("kernel32.dll")]
        private static extern uint WTSGetActiveConsoleSessionId();

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool CreateProcessAsUser(
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);

        [DllImport("userenv.dll", SetLastError = true)]
        private static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(IntPtr hObject);

        [StructLayout(LayoutKind.Sequential)]
        private struct STARTUPINFO
        {
            public int cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public uint dwX, dwY, dwXSize, dwYSize;
            public uint dwXCountChars, dwYCountChars;
            public uint dwFillAttribute;
            public uint dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }

        private static void Main()
        {
            Console.WriteLine("=== Launcher (Session 0) ===");
            Console.WriteLine($"Current session : {Process.GetCurrentProcess().SessionId}");

            uint activeSession = WTSGetActiveConsoleSessionId();
            if (activeSession == 0xFFFFFFFF)
            {
                Console.WriteLine("No active console session.");
                return;
            }
            Console.WriteLine($"Active console : {activeSession}");

            if (!WTSQueryUserToken(activeSession, out IntPtr hUserToken))
            {
                Console.WriteLine($"WTSQueryUserToken failed: {Marshal.GetLastWin32Error()}");
                return;
            }

            if (!CreateEnvironmentBlock(out IntPtr hEnv, hUserToken, false))
            {
                Console.WriteLine($"CreateEnvironmentBlock failed: {Marshal.GetLastWin32Error()}");
                CloseHandle(hUserToken);
                return;
            }

            string clientPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PopupClient.exe");
            if (!File.Exists(clientPath))
            {
                Console.WriteLine($"PopupClient.exe not found at: {clientPath}");
                CloseHandle(hUserToken);
                return;
            }

            STARTUPINFO si = new STARTUPINFO();
            si.cb = Marshal.SizeOf(typeof(STARTUPINFO));
            si.lpDesktop = "winsta0\\default";

            bool ok = CreateProcessAsUser(
                hUserToken,
                null,
                $"\"{clientPath}\"",
                IntPtr.Zero,
                IntPtr.Zero,
                false,
                0x00000400, // CREATE_UNICODE_ENVIRONMENT
                hEnv,
                null,
                ref si,
                out PROCESS_INFORMATION pi);

            if (ok)
            {
                Console.WriteLine($"Launched PopupClient.exe PID {pi.dwProcessId}");
                CloseHandle(pi.hThread);
                CloseHandle(pi.hProcess);
            }
            else
            {
                Console.WriteLine($"CreateProcessAsUser failed: {Marshal.GetLastWin32Error()}");
            }

            CloseHandle(hEnv);
            CloseHandle(hUserToken);
            Console.WriteLine("Launcher finished.");
        }
    }
}

PopupClient.exe 代码

using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;

namespace PopupClient
{
    internal static class Program
    {
        [STAThread]
        static void Main()
        {
            try
            {
                string log = Path.Combine(Path.GetTempPath(), "popup_debug.txt");
                File.WriteAllText(log, $"PopupClient started at {DateTime.Now:yyyy-MM-dd HH:mm:ss}\nSession ID: {Process.GetCurrentProcess().SessionId}");

                ApplicationConfiguration.Initialize();
                MessageBox.Show("Hello from PopupClient!", "Test Notification", MessageBoxButtons.OK, MessageBoxIcon.Information);

                File.AppendAllText(log, "\nMessageBox closed successfully by user.");
            }
            catch (Exception ex)
            {
                string errorLog = Path.Combine(Path.GetTempPath(), "popup_error.txt");
                File.WriteAllText(errorLog, $"Error occurred at {DateTime.Now:yyyy-MM-dd HH:mm:ss}\nException details:\n{ex.ToString()}");
            }
        }
    }
}

我自己初步的排查方向(供参考)

  • 环境变量问题:虽然调用了CreateEnvironmentBlock,但.NET 8单文件程序会不会对环境变量有特殊依赖?或者是不是应该把CreateProcessAsUser的lpCurrentDirectory设置为用户的常用目录(比如桌面)?
  • Token权限问题:WTSQueryUserToken拿到的token是不是缺少必要权限?比如SE_ASSIGNPRIMARYTOKEN_NAME或SE_INCREASE_QUOTA_NAME?
  • 单文件程序的启动坑:.NET 8单文件自包含程序在跨会话启动时,会不会有解压或初始化的隐藏问题?
  • 桌面会话绑定问题:虽然设置了lpDesktop = "winsta0\\default",但STARTUPINFO里是不是还需要加上STARTF_USESHOWWINDOW并设置wShowWindow = SW_SHOWNORMAL来强制显示窗口?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:00:27