繁体   English   中英

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

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

我想从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。 没有其他控制台窗口可见。 完善。

  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,因为启动应用程序将被终止。

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

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

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

Chekout这个答案隐藏控制台:

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

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

流程树

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM