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

How to launch detached process through cmd.exe with no console?

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

这是示例代码:

1
2
3
4
5
6
7
8
            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是这样的:"/ c Foo.bat> Foo.log 2>&1"
    一切正常,Foo.bat填充了Foo.log。没有其他控制台窗口可见。完善。

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

  • 我想2.工作与1.相同为什么有区别?

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

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

    你能帮我为什么吗?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    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_PROCESS标志,并且Windows会正常创建新的控制台。

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

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

    但是,我不确定您如何在.NET中进行适当的处??理。最好的情况下,这有点棘手,因为您必须使子进程继承日志文件句柄,而又不能不恰当地继承其他句柄-这可能是Process.Start导致您出现问题的原因。推荐的做法是将进程/线程属性列表(InitializeProcThreadAttributeList)与PROC_THREAD_ATTRIBUTE_HANDLE_LIST条目一起使用。 (但是日志句柄仍然需要可继承。)


    创建一个vb脚本NoWindow.vbs,可能是动态编程,如下所示

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

    在您的主应用中,只需使用Process.Start调用cscript

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

    vbs脚本本身将分离该过程。没有窗口可见。

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


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

    如何在隐藏控制台的情况下运行C#控制台应用程序

    而这个答案启动了分离过程:

    工艺树