【问题标题】:How to launch detached process through cmd.exe with no console?如何在没有控制台的情况下通过 cmd.exe 启动分离的进程?
【发布时间】:2015-08-03 07:17:00
【问题描述】:

我想从 C# 启动一个外部程序以完全分离。 我通过 pinvoke 使用 CreateProcess,因为 Process.Start 不允许我使用 DETACHED_PROCESS。我也希望这个应用程序将它的输出重定向到某个文件。

示例代码如下:

            var processInformation = new ProcessUtility.PROCESS_INFORMATION();
            var securityInfo = new ProcessUtility.STARTUPINFO();
            var sa = new ProcessUtility.SECURITY_ATTRIBUTES();
            sa.Length = Marshal.SizeOf(sa); 

            // Create process with no window and totally detached
            var result = ProcessUtility.CreateProcess(Path.Combine(Environment.SystemDirectory, "cmd.exe"), commandLineArguments, ref sa, ref sa, false,
                ProcessUtility.DETACHED_PROCESS, IntPtr.Zero, null, ref securityInfo, out processInformation);
  1. CommandLineArguments 是这样的:“/c Foo.bat > Foo.log 2>&1” 一切正常,Foo.log 由 Foo.bat 填充。没有其他控制台窗口可见。完美。

  2. CommandLineArguments 是这样的:“/c Foo.exe > Foo.log 2>&1” Foo.exe 是 .NET 控制台应用程序。 Foo.log 未填充,Foo.exe 在可见控制台窗口中启动。奇怪的。为什么行为与 1 不同。?

  3. 仅供参考。 CommandLineArguments 是这样的:“/c Foo.exe > Foo.log 2>&1” Foo.exe 是 .NET Windows 应用程序。 一切正常,但是当我仅从命令提示符启动此应用程序时,我看不到任何输出,因为没有分配控制台。

我想要 2. 和 1 一样工作。为什么会有不同?

更新:我不想为自己编写 Foo.log,因为启动应用程序会被杀死。

更新:好的,我编写了一些代码来指定只继承一个句柄,但 CreateProcess 在使用 EXTENDED_STARTUPINFO_PRESENT 调用时给我错误 87(即使它存在且为空)。

你能帮我解释为什么吗?

public class ProcessUtility
{
    // Process creation flags
    const uint ZERO_FLAG = 0x00000000;
    const uint CREATE_BREAKAWAY_FROM_JOB = 0x01000000;
    const uint CREATE_DEFAULT_ERROR_MODE = 0x04000000;
    const uint CREATE_NEW_CONSOLE = 0x00000010;
    const uint CREATE_NEW_PROCESS_GROUP = 0x00000200;
    const uint CREATE_NO_WINDOW = 0x08000000;
    const uint CREATE_PROTECTED_PROCESS = 0x00040000;
    const uint CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000;
    const uint CREATE_SEPARATE_WOW_VDM = 0x00001000;
    const uint CREATE_SHARED_WOW_VDM = 0x00001000;
    const uint CREATE_SUSPENDED = 0x00000004;
    const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
    const uint DEBUG_ONLY_THIS_PROCESS = 0x00000002;
    const uint DEBUG_PROCESS = 0x00000001;
    const uint DETACHED_PROCESS = 0x00000008;
    const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
    const uint INHERIT_PARENT_AFFINITY = 0x00010000;

    // Thread attributes flags
    const uint PROC_THREAD_ATTRIBUTE_HANDLE_LIST = 0x00020002;
    const uint PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;

    // File creation flags
    const uint FILE_ACCESS_WRITE = 0x40000000;

    // StartupInfo flags
    const int STARTF_USESTDHANDLES = 0x00000100;

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

    [StructLayout(LayoutKind.Sequential)]
    struct STARTUPINFOEX
    {
        public STARTUPINFO StartupInfo;
        public IntPtr lpAttributeList;
    };

    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public Int32 dwProcessID;
        public Int32 dwThreadID;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct SECURITY_ATTRIBUTES
    {
        public Int32 Length;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern bool CreateProcess(
        string lpApplicationName,
        string lpCommandLine,
        ref SECURITY_ATTRIBUTES lpProcessAttributes,
        ref SECURITY_ATTRIBUTES lpThreadAttributes,
        bool bInheritHandles,
        uint dwCreationFlags,
        IntPtr lpEnvironment,
        string lpCurrentDirectory,
        [In] ref STARTUPINFO lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern bool CreateProcess(
        string lpApplicationName,
        string lpCommandLine,
        ref SECURITY_ATTRIBUTES lpProcessAttributes,
        ref SECURITY_ATTRIBUTES lpThreadAttributes,
        bool bInheritHandles,
        uint dwCreationFlags,
        IntPtr lpEnvironment,
        string lpCurrentDirectory,
        [In] ref STARTUPINFOEX lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("kernel32.dll")]
    public static extern uint GetLastError();

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool UpdateProcThreadAttribute(
        IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue,
        IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool InitializeProcThreadAttributeList(
        IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList);

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

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern SafeFileHandle CreateFile(
        string lpFileName,
        uint fileAccess,
        [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
        SECURITY_ATTRIBUTES securityAttributes,
        [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
        uint dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    public static bool CreateProcessWithStdHandlesRedirect(string lpApplicationName, string lpCommandLine, string logFilename)
    {
        var startupInfo = new STARTUPINFOEX();
        startupInfo.StartupInfo.cb = Marshal.SizeOf(startupInfo);

        try
        {
            var lpSize = IntPtr.Zero;
            if (InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize) || lpSize == IntPtr.Zero)
                return false;
            startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize);

            // Here startupInfo.lpAttributeList is initialized to hold 1 value
            if (!InitializeProcThreadAttributeList(startupInfo.lpAttributeList, 1, 0, ref lpSize))
                return false;

            var fileSecurityAttributes = new SECURITY_ATTRIBUTES();
            fileSecurityAttributes.Length = Marshal.SizeOf(fileSecurityAttributes);
            // Create inheritable file handle
            fileSecurityAttributes.bInheritHandle = true;

            // Open log file for writing
            using (var handle = CreateFile(logFilename, FILE_ACCESS_WRITE, FileShare.ReadWrite,
                fileSecurityAttributes, FileMode.Create, 0, IntPtr.Zero))
            {
                var fileHandle = handle.DangerousGetHandle();

                // Add filehandle to proc thread attribute list
                if (!UpdateProcThreadAttribute(startupInfo.lpAttributeList, 0, (IntPtr)PROC_THREAD_ATTRIBUTE_HANDLE_LIST, fileHandle,
                    (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero))
                    return false;

                startupInfo.StartupInfo.hStdError = fileHandle;
                startupInfo.StartupInfo.hStdOutput = fileHandle;
                // startupInfo.StartupInfo.hStdInput = ?;
                startupInfo.StartupInfo.dwFlags = STARTF_USESTDHANDLES;

                var processInformation = new PROCESS_INFORMATION();
                var securityAttributes = new SECURITY_ATTRIBUTES();
                securityAttributes.Length = Marshal.SizeOf(securityAttributes);
                securityAttributes.bInheritHandle = true;

                // Create process with no window and totally detached
                return ProcessUtility.CreateProcess(lpApplicationName, lpCommandLine, ref securityAttributes, ref securityAttributes, true,
                    DETACHED_PROCESS | EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref startupInfo, out processInformation);
            }
        }
        finally
        {
            if (startupInfo.lpAttributeList != IntPtr.Zero)
            {
                DeleteProcThreadAttributeList(startupInfo.lpAttributeList);
                Marshal.FreeHGlobal(startupInfo.lpAttributeList);
            }
        }
    }
}

【问题讨论】:

  • 如果你杀死启动应用程序为什么不使用Process.Start?因为 AFAIK(如果我理解正确的话)启动的进程将在“父级”退出后立即分离......
  • 使用"/c Foo.bat > Foo.log 2>&1"方法,把Foo.exe放在Foo.bat里面,这不是回答问题,而是解决问题! ;-)
  • 在使用 Process.Start 时有一个非常奇怪的行为。该进程被终止,但它的 TCP 连接保持活动状态,直到使用 Process.Start 生成的最后一个子进程结束。这不是期望的行为。使用 DETACHED_PROCESS 标志时没有问题
  • 尝试使用 CREATE_NO_WINDOW 而不是 DETACHED_PROCESS 每个 Eryksun 的 cmets 来回答我的问题。

标签: c# windows batch-file cmd console-application


【解决方案1】:

查看此答案以隐藏控制台:

How to Run a C# console application with the console hidden

这是启动分离进程的答案:

Process Tree

【讨论】:

  • 抱歉,我的问题与 Process.Start 无关,因为它不允许我创建真正分离的进程。请参阅 DETACHED_PROCESS 标志。
【解决方案2】:

在情况 1 中,您正在启动的 cmd.exe 实例可以运行批处理文件本身。没有创建子进程。

在情况 2 中,您要启动的 cmd.exe 实例必须将控制台应用程序作为子进程运行。它无法知道您是否希望应用程序没有控制台窗口,因此当它调用 CreateProcess 时它不使用DETACHED_PROCESS 标志,Windows 会照常创建一个新控制台。

在情况 3 中,子进程不是控制台应用程序,因此即使未指定 DETACHED_PROCESS,Windows 也不会为其创建控制台。

通常的解决方案是自己打开foo.log文件,直接启动控制台应用程序(而不是通过cmd.exe)并使用STARTUP_INFO结构将日志文件句柄作为标准输出和标准错误传递给新工艺。一旦CreateProcess 返回,您就可以关闭文件句柄。当您的进程关闭时,子进程中的重复句柄不会受到影响。

但是,我不确定您将如何在 .NET 中正确处理该问题。在最好的情况下,这有点棘手,因为您必须让子进程继承日志文件句柄,而不会不恰当地继承其他句柄——这可能是Process.Start 给您带来问题的原因。推荐的做法是使用带有 PROC_THREAD_ATTRIBUTE_HANDLE_LIST 条目的进程/线程属性列表 (InitializeProcThreadAttributeList)。 (但日志句柄仍然需要可继承。)

【讨论】:

  • 我不明白DETACHED_PROCESS 是如何影响套接字句柄的继承的。否则它可以改用CREATE_NO_WINDOW,因此子进程从没有窗口的 cmd.exe 继承控制台。
  • @eryksun:我不相信DETACHED_PROCESS 会影响继承。我的猜测是,OP 观察到使用CreateProcess 不会导致套接字问题,并假设这是因为DETACHED_PROCESS 标志实际上是因为他将bInheritHandles 设置为FALSE。但是,我没有尝试复制问题来验证这一点,所以这只是一个猜测。
  • @eryksun: According to Hans CREATE_NO_WINDOW 选项不会创建隐藏控制台,所以它没有帮助。同样,我自己没有尝试过。
  • 那个解释不太对。 CREATE_NO_WINDOW 启动附加到控制台主机进程的程序,该进程实际上不创建窗口(它不仅仅是隐藏的)。您可以看到 conhost.exe 的新实例,还可以将调试器附加到控制台客户端以检查其 ProcessParametersConsoleHandle 字段。此外,标准句柄(StandardInput 等)仍将设置为有效的控制台句柄。 OTOH,DETACHED_PROCESSConsoleHandle 和标准手柄都将是 NULL
  • @eryksun:在这种情况下,使用CREATE_NO_WINDOW 而不是DETACHED_PROCESS 应该可以解决OPs 问题。也许您应该将其发布为答案?
【解决方案3】:

创建一个 vb 脚本,NoWindow.vbs,也许是在运行中以编程方式,如下

CreateObject("Wscript.Shell").Run WScript.Arguments(0), 0, False

从您的主应用程序中,只需使用 Process.Start 调用 cscript

Process.Start("cscript", "NoWindow.vbs \"cmd /c Foo.exe > Foo.log 2>&1 \" ");

vbs 脚本本身将分离进程。没有可见的窗口。

我的测试仅限于使用 Procexp 来确认进程 Foo.exe 已分离 - Win7。

【讨论】:

  • 你不觉得这对于这么简单的任务来说太复杂了吗?我认为指定继承的句柄是更容易的解决方案。
  • 我有点喜欢 on-the-fly bat 和 vbs 脚本的想法,我不认为它太复杂。即您可能有理由让应用程序或服务重新启动它自己 - 也许是为了自动更新 - 只需生成一个 bat 文件并将其启动
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-10
  • 2023-03-24
  • 1970-01-01
  • 2023-04-03
  • 2020-10-19
  • 1970-01-01
  • 2019-02-17
相关资源
最近更新 更多