I want to launch an external program from C# to be completely detached. I use CreateProcess through pinvoke because Process.Start doesn't allow me to use DETACHED_PROCESS. Also I want this application to redirect it's output to some file.
Here's the sample code:
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 are something like this : "/c Foo.bat > Foo.log 2>&1" Everything works fine and Foo.log is populated by Foo.bat. No additional console window is visible. PERFECT.
CommandLineArguments are something like this : "/c Foo.exe > Foo.log 2>&1" Foo.exe is .NET Console Application. Foo.log is not populated and Foo.exe is launched in visible console window. STRANGE. Why behavior is different from 1. ?
Just for your information. CommandLineArguments are something like this : "/c Foo.exe > Foo.log 2>&1" Foo.exe is .NET Windows Application. Everything works fine but when I launch this application simply from command prompt I see no output because no console is allocated.
I want 2. work the same as 1. Why there's a difference ?
UPDATE: I don't want to write the Foo.log for myself because launching application will be killed.
UPDATE: Ok, I wrote some code to specify that only one handle is inherited but CreateProcess gives me error 87 when calling with EXTENDED_STARTUPINFO_PRESENT ( even if it's present and empty ).
Can you please help me why?
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);
}
}
}
}