C# 如何在没有控制台的情况下通过cmd.exe启动分离的进程?
我想从C#启动一个完全独立的外部程序。 我通过pinvoke使用CreateProcess,因为Process.Start不允许我使用分离的\u进程。我还希望这个应用程序将其输出重定向到某个文件 以下是示例代码:C# 如何在没有控制台的情况下通过cmd.exe启动分离的进程?,c#,windows,batch-file,cmd,console-application,C#,Windows,Batch File,Cmd,Console Application,我想从C#启动一个完全独立的外部程序。 我通过pinvoke使用CreateProcess,因为Process.Start不允许我使用分离的\u进程。我还希望这个应用程序将其输出重定向到某个文件 以下是示例代码: var processInformation = new ProcessUtility.PROCESS_INFORMATION(); var securityInfo = new ProcessUtility.STARTUPINFO();
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);
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\u PROCESS
标志,Windows会像往常一样创建一个新的控制台
在案例3中,子进程不是控制台应用程序,因此Windows不会为其创建控制台,即使未指定分离的\u进程
通常的解决方案是自己打开foo.log
文件,直接启动控制台应用程序(而不是通过cmd.exe
),并使用STARTUP\u INFO
结构将日志文件句柄作为新进程的标准输出和标准错误传递。CreateProcess
返回后,可以关闭文件句柄。当进程关闭时,子进程中的复制句柄将不受影响
然而,我不确定您将如何在.NET中正确地实现这一点。在最好的情况下,这有点棘手,因为您必须让子进程继承日志文件句柄,而不让它不适当地继承其他句柄-这可能是
进程的原因。Start
会给您带来问题。建议使用进程/线程属性列表()和PROC\u thread\u attribute\u HANDLE\u list
条目。(但是日志句柄仍然需要可继承。)创建一个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来确认proceses Foo.exe已分离-Win7 如果终止启动应用程序,为什么不使用
进程。启动?因为AFAIK(如果我没记错的话)启动的进程将在“父进程”退出后立即分离…使用“/cfoo.bat>Foo.log 2>&1”
方法并将Foo.exe
放入Foo.bat
中这并不能回答问题,但解决了问题<代码>;-)代码>使用Process.Start时有一个非常奇怪的行为。进程已被终止,但其TCP连接保持活动状态,直到最后一个子进程与进程一起生成。开始结束。这不是一个理想的行为。当使用分离的\u进程标志时没有问题。请尝试使用“创建\u否\u”窗口,而不是根据Eryksun对我的答案的评论使用分离的\u进程。抱歉,我的问题不是关于进程。开始,因为它不允许我创建真正分离的进程。请参阅DETACHED_进程标志。我不理解DETACHED_进程
如何影响套接字句柄的继承。否则它可以使用CREATE\u NO\u WINDOW
,这样子进程从没有窗口的cmd.exe继承控制台。@eryksun:我不认为分离的\u进程
会影响继承。我的猜测是,OP观察到使用CreateProcess
不会导致套接字问题,并假设这是因为DETACHED\u进程
标志,而实际上是因为他正在将bInheritHandles
设置为FALSE
。但是,我没有尝试复制问题来验证这一点,所以这只是猜测。@eryksun:CREATE\u NO\u WINDOW
选项没有创建隐藏的控制台,因此没有帮助。再说一次,我自己也没试过。这个解释不太正确CREATE_NO_WINDOW
启动连接到控制台主机进程的程序,该进程实际上不创建窗口(它不仅仅是隐藏的)。您可以看到conhost.exe的新实例,但也可以将调试器附加到控制台客户端,以检查其进程参数的控制台句柄字段。此外,标准句柄(StandardInput
等)仍将设置为有效的控制台句柄。OTOH,使用分离的\u进程
时,控制台句柄
和标准句柄都将为NULL
@eryksun:在这种情况下,使用创建\u NO \u窗口
而不是分离的\u进程
应该可以解决OPs问题。也许你应该发帖