You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#/.NET 4.6.1如何在指定父进程下创建子进程并捕获输出

问题描述

我正在编写一个Windows应用程序,需要在非自身的父进程下创建子进程并捕获其输出,目标系统为Windows 7及以上,以普通用户权限运行。我选择同用户上下文的explorer.exe作为父进程,避免引发Windows安全问题。

当前遇到的问题:指定父进程时,子进程并未成功创建;但以当前应用程序为父进程时可以正常工作,不清楚问题出在哪里。


代码实现
using System.Runtime.InteropServices;
using System;
using NLog;
using System.Diagnostics;
using System.Threading;
using System.Security.Permissions;
using System.Security.AccessControl;
using System.IO;
using NPOI.SS.Formula.Functions;
using Microsoft.Win32.SafeHandles;

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    [MarshalAs(UnmanagedType.Bool)]
    public bool bInheritHandle;
}

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

public enum HANDLE_FLAGS : uint
{
    None = 0,
    INHERIT = 1,
    PROTECT_FROM_CLOSE = 2
}

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

[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public int dwProcessId;
    public int dwThreadId;
}

[StructLayout(LayoutKind.Sequential)]
public struct SIZE_T
{
    public UIntPtr _value;
}

namespace LaunchProcess
{
    public static class LaunchProcess
    {
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        const uint MAXIMUM_ALLOWED = 0x02000000;
        const int ProcessParentPid = 0x00000002;
        public const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
        public const int STARTF_USESTDHANDLES = 0x00000100;
        public const int STARTF_USESHOWWINDOW = 0x00000001;
        public const ushort SW_HIDE = 0x0000;
        public const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
        public const uint CREATE_NO_WINDOW = 0x08000000;
        public const uint CreateSuspended = 0x00000004;

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        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", SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool CreateProcess(
            string lpApplicationName,
            string lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFOEX lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);

        [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 IntPtr CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);

        public static string CreateProcess(string processName, string command, string parentProcess = null)
        {
            Log.Trace($"Spawning {processName} with command {command}");

            const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
            const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;

            // Create pipe for output capture
            IntPtr hReadPipe = IntPtr.Zero, hWritePipe = IntPtr.Zero;
            SECURITY_ATTRIBUTES securityAttributes = new SECURITY_ATTRIBUTES();
            securityAttributes.nLength = Marshal.SizeOf(securityAttributes);
            securityAttributes.bInheritHandle = true;
            CreatePipe(out hReadPipe, out hWritePipe, ref securityAttributes, 0);
            SafeFileHandle safeReadPipe = new SafeFileHandle(hReadPipe, ownsHandle: false);
            SetHandleInformation(hReadPipe, 1, 0); // Prevent inheritance of the read pipe

            var pInfo = new PROCESS_INFORMATION();
            var sInfoEx = new STARTUPINFOEX();
            sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
            sInfoEx.StartupInfo.hStdOutput = hWritePipe;
            sInfoEx.StartupInfo.dwFlags |= 0x00000100; // Set the STARTF_USESTDHANDLES flag
            IntPtr lpValue = IntPtr.Zero;

            bool processCreated = false; // Flag to indicate if the process was created successfully
            string commandLine = $"{processName} {command}";
            var lpApplicationName = Path.Combine(Environment.SystemDirectory, "cmd.exe");

            try
            {
                if (string.IsNullOrEmpty(parentProcess))
                {
                    Log.Trace($"Creating process with command: {commandLine}");

                    // Create the process
                    //processCreated = CreateProcess(null, commandLine, ref pSec, ref tSec, false, EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref sInfoEx, out pInfo);
                    processCreated = CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, false, 0, IntPtr.Zero, null, ref sInfoEx, out pInfo);

                }
                else
                {
                    int PPID = GetParentProcessId(parentProcess);

                    if (PPID != -1)
                    {
                        var lpSize = IntPtr.Zero;
                        var success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize);

                        if (success || lpSize == IntPtr.Zero)
                        {
                            //Log.Error($"Failed to open parent process handle. Error: {Marshal.GetLastWin32Error()}");
                            return "";
                        }

                        sInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize);
                        success = InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref lpSize);
                        if (!success)
                        {
                            return "";
                        }

                        var parentHandle = Process.GetProcessById(PPID).Handle;
                        // This value should persist until the attribute list is destroyed using the DeleteProcThreadAttributeList function
                        lpValue = Marshal.AllocHGlobal(IntPtr.Size);
                        Marshal.WriteIntPtr(lpValue, parentHandle);

                        success = UpdateProcThreadAttribute(
                            sInfoEx.lpAttributeList,
                            0,
                            (IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
                            lpValue,
                            (IntPtr)IntPtr.Size,
                            IntPtr.Zero,
                            IntPtr.Zero);

                        if (!success)
                        {
                            return "";
                        }

                        var pSec = new SECURITY_ATTRIBUTES();
                        var tSec = new SECURITY_ATTRIBUTES();
                        pSec.nLength = Marshal.SizeOf(pSec);
                        tSec.nLength = Marshal.SizeOf(tSec);
                        //var lpApplicationName = Path.Combine(Environment.SystemDirectory, "notepad.exe");
                        processCreated = CreateProcess(lpApplicationName, null, ref pSec, ref tSec, false, EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref sInfoEx, out pInfo);
                    }
                }

                // Check if process was created successfully
                if (!processCreated)
                {
                    Log.Error($"CreateProcess failed. Error: {Marshal.GetLastWin32Error()}");
                    return "";
                }

                Log.Trace($"Created process with PID: {pInfo.dwProcessId}");

                Process proc = Process.GetProcessById(pInfo.dwProcessId);
                proc.WaitForExit();

                // Read process output using a StreamReader
                string output = "";

                using (var outputStream = new FileStream(safeReadPipe, FileAccess.Read, 4096, false))
                using (var reader = new StreamReader(outputStream))
                {
                    char[] buffer = new char[4096];
                    int read;

                    while ((read = reader.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        output += new string(buffer, 0, read);
                    }
                }

                return output; // Return the captured output
            }
            finally 
            {
                // Free the attribute list
                if (sInfoEx.lpAttributeList != IntPtr.Zero)
                {
                    DeleteProcThreadAttributeList(sInfoEx.lpAttributeList);
                    Marshal.FreeHGlobal(sInfoEx.lpAttributeList);
                }
                Marshal.FreeHGlobal(lpValue);

                // Close process and thread handles
                if (pInfo.hProcess != IntPtr.Zero)
                {
                    CloseHandle(pInfo.hProcess);
                }

                if (pInfo.hThread != IntPtr.Zero)
                {
                    CloseHandle(pInfo.hThread);
                }

                if (hReadPipe != IntPtr.Zero) 
                { 
                    CloseHandle(hReadPipe);
                }

                if (hWritePipe != IntPtr.Zero)
                { 
                    CloseHandle(hWritePipe);
                }
            }
        }

        private static int GetParentProcessId(string processName)
        {
            // Find the process by the specified name
            Process[] processes = Process.GetProcessesByName(processName);

            if (processes.Length > 0)
            {
                Log.Trace($"Found {processName}.exe pid: {processes[0].Id}");
                return processes[0].Id;
            }
            else
            {
                int currentProcessId = Process.GetCurrentProcess().Id;
                Log.Trace($"Failed to find {processName}.exe");
                Log.Trace($"Using current process pid: {currentProcessId}");
                return currentProcessId;
            }
        }
    }
}

调用方式
var results = LaunchProcess.CreateProcess("cmd.exe", "/c " + command, "explorer");

解决建议

1. 修正CreateProcess参数错误

在指定父进程的分支中,你当前调用CreateProcess时传入了lpApplicationName = cmd.exe但lpCommandLine = null,这会导致进程无法正确启动。应该将构造好的commandLine传入,同时设置bInheritHandles = true(需要继承管道句柄来捕获输出),并添加CREATE_NO_WINDOW标志避免弹出窗口:

processCreated = CreateProcess(null, commandLine, ref pSec, ref tSec, true, EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW, IntPtr.Zero, null, ref sInfoEx, out pInfo);

2. 获取父进程的正确权限句柄

Process.GetProcessById(PPID).Handle默认返回的句柄没有PROCESS_CREATE_PROCESS权限,导致无法设置为父进程。需要添加OpenProcess调用获取足够权限的句柄:

// 添加DllImport声明
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);

const uint PROCESS_CREATE_PROCESS = 0x0080;

// 替换原parentHandle获取代码
var parentHandle = OpenProcess(PROCESS_CREATE_PROCESS, false, PPID);
if (parentHandle == IntPtr.Zero)
{
    Log.Error($"OpenProcess failed. Error: {Marshal.GetLastWin32Error()}");
    return "";
}

记得在finally块中关闭这个句柄:

if (parentHandle != IntPtr.Zero)
{
    CloseHandle(parentHandle);
}

3. 修复管道继承的魔法数字

SetHandleInformation中的1应该替换为HANDLE_FLAGS.INHERIT枚举值,提升代码可读性:

SetHandleInformation(hReadPipe, (uint)HANDLE_FLAGS.INHERIT, 0);

4. 完善错误日志输出

在InitializeProcThreadAttributeList、UpdateProcThreadAttribute等API调用失败时,不要直接返回空字符串,先记录错误码,方便排查问题:

if (!success)
{
    Log.Error($"InitializeProcThreadAttributeList failed. Error: {Marshal.GetLastWin32Error()}");
    return "";
}

5. 补充STARTUPINFO窗口配置

为了避免子进程弹出窗口,需要设置STARTUPINFO的wShowWindow和对应的标志:

sInfoEx.StartupInfo.wShowWindow = SW_HIDE;
sInfoEx.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;

内容的提问来源于stack exchange,提问作者RedWiz666

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 06:42:03