private Task <IPtyConnection> StartPseudoConsoleAsync( PtyOptions options, TraceSource trace, CancellationToken cancellationToken) { // Create the in/out pipes if (!CreatePipe(out SafePipeHandle inPipePseudoConsoleSide, out SafePipeHandle inPipeOurSide, null, 0)) { throw new InvalidOperationException("Could not create an anonymous pipe", new Win32Exception()); } if (!CreatePipe(out SafePipeHandle outPipeOurSide, out SafePipeHandle outPipePseudoConsoleSide, null, 0)) { throw new InvalidOperationException("Could not create an anonymous pipe", new Win32Exception()); } var coord = new Coord(options.Cols, options.Rows); var pseudoConsoleHandle = new SafePseudoConsoleHandle(); int hr; RuntimeHelpers.PrepareConstrainedRegions(); try { // Run CreatePseudoConsole* in a CER to make sure we don't leak handles. // MSDN suggest to put all CER code in a finally block // See http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions(v=vs.110).aspx } finally { // Create the Pseudo Console, using the pipes hr = CreatePseudoConsole(coord, inPipePseudoConsoleSide.Handle, outPipePseudoConsoleSide.Handle, 0, out IntPtr hPC); // Remember the handle inside the CER to prevent leakage if (hPC != IntPtr.Zero && hPC != INVALID_HANDLE_VALUE) { pseudoConsoleHandle.InitialSetHandle(hPC); } } if (hr != S_OK) { Marshal.ThrowExceptionForHR(hr); } // Prepare the StartupInfoEx structure attached to the ConPTY. var startupInfo = default(STARTUPINFOEX); startupInfo.InitAttributeListAttachedToConPTY(pseudoConsoleHandle); IntPtr lpEnvironment = Marshal.StringToHGlobalUni(GetEnvironmentString(options.Environment)); try { string app = GetAppOnPath(options.App, options.Cwd, options.Environment); string arguments = options.VerbatimCommandLine ? WindowsArguments.FormatVerbatim(options.CommandLine) : WindowsArguments.Format(options.CommandLine); var commandLine = new StringBuilder(app.Length + arguments.Length + 4); bool quoteApp = app.Contains(" ") && !app.StartsWith("\"") && !app.EndsWith("\""); if (quoteApp) { commandLine.Append('"').Append(app).Append('"'); } else { commandLine.Append(app); } if (!string.IsNullOrWhiteSpace(arguments)) { commandLine.Append(' '); commandLine.Append(arguments); } bool success; int errorCode = 0; var processInfo = default(PROCESS_INFORMATION); var processHandle = new SafeProcessHandle(); var mainThreadHandle = new SafeThreadHandle(); RuntimeHelpers.PrepareConstrainedRegions(); try { // Run CreateProcess* in a CER to make sure we don't leak handles. } finally { success = CreateProcess( null, // lpApplicationName commandLine.ToString(), null, // lpProcessAttributes null, // lpThreadAttributes false, // bInheritHandles VERY IMPORTANT that this is false EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags lpEnvironment, options.Cwd, ref startupInfo, out processInfo); if (!success) { errorCode = Marshal.GetLastWin32Error(); } // Remember the handles inside the CER to prevent leakage if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != INVALID_HANDLE_VALUE) { processHandle.InitialSetHandle(processInfo.hProcess); } if (processInfo.hThread != IntPtr.Zero && processInfo.hThread != INVALID_HANDLE_VALUE) { mainThreadHandle.InitialSetHandle(processInfo.hThread); } } if (!success) { var exception = new Win32Exception(errorCode); throw new InvalidOperationException($"Could not start terminal process {commandLine.ToString()}: {exception.Message}", exception); } var connectionOptions = new PseudoConsoleConnection.PseudoConsoleConnectionHandles( inPipePseudoConsoleSide, outPipePseudoConsoleSide, inPipeOurSide, outPipeOurSide, pseudoConsoleHandle, processHandle, processInfo.dwProcessId, mainThreadHandle); var result = new PseudoConsoleConnection(connectionOptions); return(Task.FromResult <IPtyConnection>(result)); } finally { startupInfo.FreeAttributeList(); if (lpEnvironment != IntPtr.Zero) { Marshal.FreeHGlobal(lpEnvironment); } } }
private async Task <IPtyConnection> StartWinPtyTerminalAsync( PtyOptions options, TraceSource trace, CancellationToken cancellationToken) { IntPtr error; IntPtr config = winpty_config_new(WINPTY_FLAG_COLOR_ESCAPES, out error); ThrowIfErrorOrNull("Error creating WinPTY config", error, config); winpty_config_set_initial_size(config, options.Cols, options.Rows); IntPtr handle = winpty_open(config, out error); winpty_config_free(config); ThrowIfErrorOrNull("Error launching WinPTY agent", error, handle); string commandLine = options.VerbatimCommandLine ? WindowsArguments.FormatVerbatim(options.CommandLine) : WindowsArguments.Format(options.CommandLine); string env = GetEnvironmentString(options.Environment); string app = GetAppOnPath(options.App, options.Cwd, options.Environment); trace.TraceInformation($"Starting terminal process '{app}' with command line {commandLine}"); IntPtr spawnConfig = winpty_spawn_config_new( WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, app, commandLine, options.Cwd, env, out error); ThrowIfErrorOrNull("Error creating WinPTY spawn config", error, spawnConfig); bool spawnSuccess = winpty_spawn(handle, spawnConfig, out SafeProcessHandle hProcess, out IntPtr thread, out int procError, out error); winpty_spawn_config_free(spawnConfig); if (!spawnSuccess) { if (procError != 0) { if (error != IntPtr.Zero) { winpty_error_free(error); } throw new InvalidOperationException($"Unable to start WinPTY terminal '{app}': {new Win32Exception(procError).Message} ({procError})"); } ThrowIfError("Unable to start WinPTY terminal process", error, alwaysThrow: true); } Stream?writeToStream = null; Stream?readFromStream = null; try { writeToStream = await CreatePipeAsync(winpty_conin_name(handle), PipeDirection.Out, cancellationToken); readFromStream = await CreatePipeAsync(winpty_conout_name(handle), PipeDirection.In, cancellationToken); } catch { writeToStream?.Dispose(); hProcess.Close(); winpty_free(handle); throw; } return(new WinPtyConnection(readFromStream, writeToStream, handle, hProcess)); }