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

