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

时间:2015-05-21 14:32:15

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

我想从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.bat填充了Foo.log。没有其他控制台窗口可见。 PERFECT。

  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应用程序。 一切正常,但是当我从命令提示符启动此应用程序时,我看不到输出,因为没有分配控制台。

  4. 我希望2.工作与1.相同。为什么会有差异?

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

    更新:好的,我编写了一些代码来指定只有一个句柄被继承但是当使用EXTENDED_STARTUPINFO_PRESENT调用时,CreateProcess会给出错误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);
                }
            }
        }
    }
    

3 个答案:

答案 0 :(得分:3)

在案例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)。 (但是日志句柄仍然需要是可继承的。)

答案 1 :(得分:1)

创建一个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。

答案 2 :(得分:0)

Chekout这个答案隐藏控制台:

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

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

Process Tree