using Microsoft.Win32.SafeHandles; using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; namespace TrustedUninstaller.Shared { [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; } [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessId; public int dwThreadId; } // This also works with CharSet.Ansi as long as the calling function uses the same character set. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public 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 dwYSize; 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, CharSet = CharSet.Unicode)] public struct STARTUPINFOEX { public STARTUPINFO StartupInfo; public IntPtr lpAttributeList; } public class NativeProcess { [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private 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)] [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)] private static extern bool CloseHandle(IntPtr hObject); private const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000; private const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000; private const int CREATE_NO_WINDOW = 0x08000000; private const int CREATE_NEW_CONSOLE = 0x00000010; [DllImport("kernel32.dll", SetLastError = true)] static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); const UInt32 INFINITE = 0xFFFFFFFF; const UInt32 WAIT_ABANDONED = 0x00000080; const UInt32 WAIT_OBJECT_0 = 0x00000000; const UInt32 WAIT_TIMEOUT = 0x00000102; public static Process? Process; public static bool StartProcess(string path, int parent, string playbookDir) { Process = null; // Create a new process with a different parent process // https://stackoverflow.com/questions/10554913/how-to-call-createprocess-with-startupinfoex-from-c-sharp-and-re-parent-the-ch var pInfo = new PROCESS_INFORMATION(); var sInfoEx = new STARTUPINFOEX(); sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx); var lpValue = IntPtr.Zero; try { var lpSize = IntPtr.Zero; var success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize); if (success || lpSize == IntPtr.Zero) { return false; } sInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize); success = InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref lpSize); if (!success) { return false; } var parentHandle = Process.GetProcessById(parent).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 false; } var pSec = new SECURITY_ATTRIBUTES(); var tSec = new SECURITY_ATTRIBUTES(); pSec.nLength = Marshal.SizeOf(pSec); tSec.nLength = Marshal.SizeOf(tSec); CreateProcess(null, $"\"{path}\" \"{playbookDir}\"", ref pSec, ref tSec, false, EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW, IntPtr.Zero, null, ref sInfoEx, out pInfo); //WaitForSingleObject(pInfo.hProcess, INFINITE); Process = Process.GetProcessById(pInfo.dwProcessId); return true; } 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); } } } } }