C# 如何在没有控制台的情况下通过cmd.exe启动分离的进程?

C# 如何在没有控制台的情况下通过cmd.exe启动分离的进程?,c#,windows,batch-file,cmd,console-application,C#,Windows,Batch File,Cmd,Console Application,我想从C#启动一个完全独立的外部程序。 我通过pinvoke使用CreateProcess,因为Process.Start不允许我使用分离的\u进程。我还希望这个应用程序将其输出重定向到某个文件 以下是示例代码: var processInformation = new ProcessUtility.PROCESS_INFORMATION(); var securityInfo = new ProcessUtility.STARTUPINFO();

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

以下是示例代码:

            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);
  • CommandLineArguments是这样的:“/cfoo.bat>Foo.log 2>&1” 一切正常,Foo.log由Foo.bat填充。没有其他控制台窗口可见。太好了

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

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

  • 我想要两个。工作方式与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);
                }
            }
        }
    }
    

    将此答案设置为隐藏控制台:

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


    在案例1中,您正在启动的
    cmd.exe
    实例可以运行批处理文件本身。未创建任何子进程

    在案例2中,您要启动的
    cmd.exe
    实例必须作为子进程运行控制台应用程序。它无法知道您不想给应用程序一个控制台窗口,因此当它调用CreateProcess时,它不会使用
    DETACHED\u PROCESS
    标志,Windows会像往常一样创建一个新的控制台

    在案例3中,子进程不是控制台应用程序,因此Windows不会为其创建控制台,即使未指定
    分离的\u进程

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


    然而,我不确定您将如何在.NET中正确地实现这一点。在最好的情况下,这有点棘手,因为您必须让子进程继承日志文件句柄,而不让它不适当地继承其他句柄-这可能是
    进程的原因。Start
    会给您带来问题。建议使用进程/线程属性列表()和
    PROC\u thread\u attribute\u HANDLE\u list
    条目。(但是日志句柄仍然需要可继承。)

    创建一个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来确认proceses Foo.exe已分离-Win7

    如果终止启动应用程序,为什么不使用
    进程。启动
    ?因为AFAIK(如果我没记错的话)启动的进程将在“父进程”退出后立即分离…使用
    “/cfoo.bat>Foo.log 2>&1”
    方法并将
    Foo.exe
    放入
    Foo.bat
    中这并不能回答问题,但解决了问题<代码>;-)使用Process.Start时有一个非常奇怪的行为。进程已被终止,但其TCP连接保持活动状态,直到最后一个子进程与进程一起生成。开始结束。这不是一个理想的行为。当使用分离的\u进程标志时没有问题。请尝试使用“创建\u否\u”窗口,而不是根据Eryksun对我的答案的评论使用分离的\u进程。抱歉,我的问题不是关于进程。开始,因为它不允许我创建真正分离的进程。请参阅DETACHED_进程标志。我不理解
    DETACHED_进程
    如何影响套接字句柄的继承。否则它可以使用
    CREATE\u NO\u WINDOW
    ,这样子进程从没有窗口的cmd.exe继承控制台。@eryksun:我不认为
    分离的\u进程
    会影响继承。我的猜测是,OP观察到使用
    CreateProcess
    不会导致套接字问题,并假设这是因为
    DETACHED\u进程
    标志,而实际上是因为他正在将
    bInheritHandles
    设置为
    FALSE
    。但是,我没有尝试复制问题来验证这一点,所以这只是猜测。@eryksun:
    CREATE\u NO\u WINDOW
    选项没有创建隐藏的控制台,因此没有帮助。再说一次,我自己也没试过。这个解释不太正确
    CREATE_NO_WINDOW
    启动连接到控制台主机进程的程序,该进程实际上不创建窗口(它不仅仅是隐藏的)。您可以看到conhost.exe的新实例,但也可以将调试器附加到控制台客户端,以检查其
    进程参数的
    控制台句柄
    字段。此外,标准句柄(
    StandardInput
    等)仍将设置为有效的控制台句柄。OTOH,使用
    分离的\u进程
    时,
    控制台句柄
    和标准句柄都将为
    NULL
    @eryksun:在这种情况下,使用
    创建\u NO \u窗口
    而不是
    分离的\u进程
    应该可以解决OPs问题。也许你应该发帖