public async override Task WaitForDisconnectAsync(CancellationToken cancellationToken) { if (!(Stream is PipeStream pipeStream)) { return; } // We have to poll for disconnection by reading, PipeStream.IsConnected isn't reliable unless you // actually do a read - which will cause it to update its state. while (!cancellationToken.IsCancellationRequested && pipeStream.IsConnected) { await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); try { ServerLogger.Log($"Before poking pipe {Identifier}."); await Stream.ReadAsync(Array.Empty <byte>(), 0, 0, cancellationToken); ServerLogger.Log($"After poking pipe {Identifier}."); } catch (OperationCanceledException) { } catch (Exception e) { // It is okay for this call to fail. Errors will be reflected in the // IsConnected property which will be read on the next iteration. ServerLogger.LogException(e, $"Error poking pipe {Identifier}."); } } }
public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken) { if (!TryParseArguments(request, out var parsed)) { return(new RejectedServerResponse()); } var exitCode = 0; var commandArgs = parsed.args.ToArray(); var outputWriter = new StringWriter(); var errorWriter = new StringWriter(); var checker = new DefaultExtensionDependencyChecker(Loader, outputWriter, errorWriter); var app = new Application(cancellationToken, Loader, checker, AssemblyReferenceProvider, outputWriter, errorWriter); exitCode = app.Execute(commandArgs); var output = outputWriter.ToString(); var error = errorWriter.ToString(); outputWriter.Dispose(); errorWriter.Dispose(); // This will no-op if server logging is not enabled. ServerLogger.Log(output); ServerLogger.Log(error); return(new CompletedServerResponse(exitCode, utf8output: false, output, error)); }
public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken) { using (var memoryStream = new MemoryStream()) using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode)) { // Format the response ServerLogger.Log("Formatting Response"); writer.Write((int)Type); AddResponseBody(writer); writer.Flush(); cancellationToken.ThrowIfCancellationRequested(); // Send the response to the client // Write the length of the response var length = checked ((int)memoryStream.Length); ServerLogger.Log("Writing response length"); // There is no way to know the number of bytes written to // the pipe stream. We just have to assume all of them are written. await outStream .WriteAsync(BitConverter.GetBytes(length), 0, 4, cancellationToken) .ConfigureAwait(false); // Write the response ServerLogger.Log("Writing response of size {0}", length); memoryStream.Position = 0; await memoryStream .CopyToAsync(outStream, bufferSize : length, cancellationToken : cancellationToken) .ConfigureAwait(false); } }
/// <summary> /// Check to ensure that the named pipe server we connected to is owned by the same /// user. /// </summary> private static bool CheckPipeConnectionOwnership(NamedPipeClientStream pipeStream) { try { if (PlatformInformation.IsWindows) { using (var currentIdentity = WindowsIdentity.GetCurrent()) { var currentOwner = currentIdentity.Owner; var remotePipeSecurity = GetPipeSecurity(pipeStream); var remoteOwner = remotePipeSecurity.GetOwner(typeof(SecurityIdentifier)); return(currentOwner.Equals(remoteOwner)); } } // We don't need to verify on non-windows as that will be taken care of by the // PipeOptions.CurrentUserOnly flag. return(false); } catch (Exception ex) { ServerLogger.LogException(ex, "Checking pipe connection"); return(false); } }
/// <summary> /// This task does not complete until we are completely done reading. /// </summary> internal static async Task ReadAllAsync( Stream stream, byte[] buffer, int count, CancellationToken cancellationToken) { var totalBytesRead = 0; do { ServerLogger.Log("Attempting to read {0} bytes from the stream", count - totalBytesRead); var bytesRead = await stream.ReadAsync( buffer, totalBytesRead, count - totalBytesRead, cancellationToken) .ConfigureAwait(false); if (bytesRead == 0) { ServerLogger.Log("Unexpected -- read 0 bytes from the stream."); throw new EndOfStreamException("Reached end of stream before end of read."); } ServerLogger.Log("Read {0} bytes", bytesRead); totalBytesRead += bytesRead; } while (totalBytesRead < count); ServerLogger.Log("Finished read"); }
public async override Task <Connection> WaitForConnectionAsync(CancellationToken cancellationToken) { // Create the pipe and begin waiting for a connection. This doesn't block, but could fail // in certain circumstances, such as the OS refusing to create the pipe for some reason // or the pipe was disconnected before we starting listening. var pipeStream = new NamedPipeServerStream( PipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, // Maximum connections. PipeTransmissionMode.Byte, _pipeOptions, PipeBufferSize, // Default input buffer PipeBufferSize); // Default output buffer ServerLogger.Log("Waiting for new connection"); await pipeStream.WaitForConnectionAsync(cancellationToken); ServerLogger.Log("Pipe connection detected."); if (Environment.Is64BitProcess || Memory.IsMemoryAvailable()) { ServerLogger.Log("Memory available - accepting connection"); return(new NamedPipeConnection(pipeStream, GetNextIdentifier())); } pipeStream.Close(); throw new Exception("Insufficient resources to process new connection."); }
public static ServerRequest Create( string workingDirectory, string tempDirectory, IList <string> args, string keepAlive = null) { ServerLogger.Log("Creating ServerRequest"); ServerLogger.Log($"Working directory: {workingDirectory}"); ServerLogger.Log($"Temp directory: {tempDirectory}"); var requestLength = args.Count + 1; var requestArgs = new List <RequestArgument>(requestLength) { new RequestArgument(RequestArgument.ArgumentId.CurrentDirectory, 0, workingDirectory), new RequestArgument(RequestArgument.ArgumentId.TempDirectory, 0, tempDirectory) }; if (keepAlive != null) { requestArgs.Add(new RequestArgument(RequestArgument.ArgumentId.KeepAlive, 0, keepAlive)); } for (var i = 0; i < args.Count; ++i) { var arg = args[i]; ServerLogger.Log($"argument[{i}] = {arg}"); requestArgs.Add(new RequestArgument(RequestArgument.ArgumentId.CommandLineArgument, i, arg)); } return(new ServerRequest(ServerProtocol.ProtocolVersion, requestArgs)); }
/// <summary> /// Try to process the request using the server. Returns a null-containing Task if a response /// from the server cannot be retrieved. /// </summary> private static async Task <ServerResponse> TryProcessRequest( Client client, ServerRequest request, CancellationToken cancellationToken) { ServerResponse response; using (client) { // Write the request try { ServerLogger.Log("Begin writing request"); await request.WriteAsync(client.Stream, cancellationToken).ConfigureAwait(false); ServerLogger.Log("End writing request"); } catch (Exception e) { ServerLogger.LogException(e, "Error writing build request."); return(new RejectedServerResponse()); } // Wait for the compilation and a monitor to detect if the server disconnects var serverCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); ServerLogger.Log("Begin reading response"); var responseTask = ServerResponse.ReadAsync(client.Stream, serverCts.Token); var monitorTask = client.WaitForDisconnectAsync(serverCts.Token); await Task.WhenAny(new[] { responseTask, monitorTask }).ConfigureAwait(false); ServerLogger.Log("End reading response"); if (responseTask.IsCompleted) { // await the task to log any exceptions try { response = await responseTask.ConfigureAwait(false); } catch (Exception e) { ServerLogger.LogException(e, "Error reading response"); response = new RejectedServerResponse(); } } else { ServerLogger.Log("Server disconnect"); response = new RejectedServerResponse(); } // Cancel whatever task is still around serverCts.Cancel(); Debug.Assert(response != null); return(response); } }
// Based on: https://github.com/dotnet/roslyn/blob/14aed138a01c448143b9acf0fe77a662e3dfe2f4/src/Compilers/Shared/BuildServerConnection.cs#L290 public static async Task <Client> ConnectAsync(string pipeName, TimeSpan?timeout, CancellationToken cancellationToken) { var timeoutMilliseconds = timeout == null ? Timeout.Infinite : (int)timeout.Value.TotalMilliseconds; try { // Machine-local named pipes are named "\\.\pipe\<pipename>". // We use the SHA1 of the directory the compiler exes live in as the pipe name. // The NamedPipeClientStream class handles the "\\.\pipe\" part for us. ServerLogger.Log("Attempt to open named pipe '{0}'", pipeName); var stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, _pipeOptions); cancellationToken.ThrowIfCancellationRequested(); ServerLogger.Log("Attempt to connect named pipe '{0}'", pipeName); try { await stream.ConnectAsync(timeoutMilliseconds, cancellationToken); } catch (Exception e) when(e is IOException || e is TimeoutException) { // Note: IOException can also indicate timeout. // From docs: // - TimeoutException: Could not connect to the server within the specified timeout period. // - IOException: The server is connected to another client and the time-out period has expired. ServerLogger.Log($"Connecting to server timed out after {timeoutMilliseconds} ms"); return(null); } ServerLogger.Log("Named pipe '{0}' connected", pipeName); cancellationToken.ThrowIfCancellationRequested(); #if NETFRAMEWORK // Verify that we own the pipe. if (!CheckPipeConnectionOwnership(stream)) { ServerLogger.Log("Owner of named pipe is incorrect"); return(null); } #endif return(new NamedPipeClient(stream, GetNextIdentifier())); } catch (Exception e) when(!(e is TaskCanceledException || e is OperationCanceledException)) { ServerLogger.LogException(e, "Exception while connecting to process"); return(null); } }
private Task <ServerResponse> ExecuteRequestAsync(ServerRequest buildRequest, CancellationToken cancellationToken) { Func <ServerResponse> func = () => { ServerLogger.Log("Begin processing request"); var response = _compilerHost.Execute(buildRequest, cancellationToken); ServerLogger.Log("End processing request"); return(response); }; var task = new Task <ServerResponse>(func, cancellationToken, TaskCreationOptions.LongRunning); task.Start(); return(task); }
protected override void Dispose(bool disposing) { ServerLogger.Log($"Pipe {Identifier}: Closing."); try { Stream.Dispose(); } catch (Exception ex) { // The client connection failing to close isn't fatal to the server process. It is simply a client // for which we can no longer communicate and that's okay because the Close method indicates we are // done with the client already. var message = $"Pipe {Identifier}: Error closing pipe."; ServerLogger.LogException(ex, message); } }
private static DateTime?GetFileTimeStamp(string fullPath) { try { Debug.Assert(Path.IsPathRooted(fullPath)); return(File.GetLastWriteTimeUtc(fullPath)); } catch (Exception e) { // There are several exceptions that can occur here: NotSupportedException or PathTooLongException // for a bad path, UnauthorizedAccessException for access denied, etc. Rather than listing them all, // just catch all exceptions and log. ServerLogger.LogException(e, $"Error getting timestamp of file {fullPath}."); return(null); } }
/// <summary> /// Read a Request from the given stream. /// /// The total request size must be less than 1MB. /// </summary> /// <returns>null if the Request was too large, the Request otherwise.</returns> public static async Task <ServerRequest> ReadAsync(Stream inStream, CancellationToken cancellationToken) { // Read the length of the request var lengthBuffer = new byte[4]; ServerLogger.Log("Reading length of request"); await ServerProtocol.ReadAllAsync(inStream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false); var length = BitConverter.ToInt32(lengthBuffer, 0); // Back out if the request is > 1MB if (length > 0x100000) { ServerLogger.Log("Request is over 1MB in length, cancelling read."); return(null); } cancellationToken.ThrowIfCancellationRequested(); // Read the full request var requestBuffer = new byte[length]; await ServerProtocol.ReadAllAsync(inStream, requestBuffer, length, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); ServerLogger.Log("Parsing request"); // Parse the request into the Request data structure. using (var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode)) { var protocolVersion = reader.ReadUInt32(); var argumentCount = reader.ReadUInt32(); var argumentsBuilder = new List <RequestArgument>((int)argumentCount); for (var i = 0; i < argumentCount; i++) { cancellationToken.ThrowIfCancellationRequested(); argumentsBuilder.Add(RequestArgument.ReadFromBinaryReader(reader)); } return(new ServerRequest(protocolVersion, argumentsBuilder)); } }
/// <summary> /// May throw exceptions if there are pipe problems. /// </summary> /// <param name="stream"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public static async Task <ServerResponse> ReadAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken)) { ServerLogger.Log("Reading response length"); // Read the response length var lengthBuffer = new byte[4]; await ServerProtocol.ReadAllAsync(stream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false); var length = BitConverter.ToUInt32(lengthBuffer, 0); // Read the response ServerLogger.Log("Reading response of length {0}", length); var responseBuffer = new byte[length]; await ServerProtocol.ReadAllAsync( stream, responseBuffer, responseBuffer.Length, cancellationToken) .ConfigureAwait(false); using (var reader = new BinaryReader(new MemoryStream(responseBuffer), Encoding.Unicode)) { var responseType = (ResponseType)reader.ReadInt32(); switch (responseType) { case ResponseType.Completed: return(CompletedServerResponse.Create(reader)); case ResponseType.MismatchedVersion: return(new MismatchedVersionServerResponse()); case ResponseType.Shutdown: return(ShutdownServerResponse.Create(reader)); case ResponseType.Rejected: return(new RejectedServerResponse()); default: throw new InvalidOperationException("Received invalid response type from server."); } } }
/// <summary> /// Write a Request to the stream. /// </summary> public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken = default(CancellationToken)) { using (var memoryStream = new MemoryStream()) using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode)) { // Format the request. ServerLogger.Log("Formatting request"); writer.Write(ProtocolVersion); writer.Write(Arguments.Count); foreach (var arg in Arguments) { cancellationToken.ThrowIfCancellationRequested(); arg.WriteToBinaryWriter(writer); } writer.Flush(); cancellationToken.ThrowIfCancellationRequested(); // Write the length of the request var length = checked ((int)memoryStream.Length); // Back out if the request is > 1 MB if (memoryStream.Length > 0x100000) { ServerLogger.Log("Request is over 1MB in length, cancelling write"); throw new ArgumentOutOfRangeException(); } // Send the request to the server ServerLogger.Log("Writing length of request."); await outStream .WriteAsync(BitConverter.GetBytes(length), 0, 4, cancellationToken) .ConfigureAwait(false); ServerLogger.Log("Writing request of size {0}", length); // Write the request memoryStream.Position = 0; await memoryStream .CopyToAsync(outStream, bufferSize : length, cancellationToken : cancellationToken) .ConfigureAwait(false); } }
private static int RunApplication(string[] args) { DebugMode.HandleDebugSwitch(ref args); var cancel = new CancellationTokenSource(); Console.CancelKeyPress += (sender, e) => { cancel.Cancel(); }; var outputWriter = new StringWriter(); var errorWriter = new StringWriter(); // Prevent shadow copying. var loader = new DefaultExtensionAssemblyLoader(baseDirectory: null); var checker = new DefaultExtensionDependencyChecker(loader, outputWriter, errorWriter); var application = new Application( cancel.Token, loader, checker, (path, properties) => MetadataReference.CreateFromFile(path, properties), outputWriter, errorWriter); var result = application.Execute(args); var output = outputWriter.ToString(); var error = errorWriter.ToString(); outputWriter.Dispose(); errorWriter.Dispose(); Console.Write(output); Console.Error.Write(error); // This will no-op if server logging is not enabled. ServerLogger.Log(output); ServerLogger.Log(error); return(result); }
internal async Task <ConnectionResult> AcceptConnection(Task <Connection> task, bool accept, CancellationToken cancellationToken) { Connection connection; try { connection = await task; } catch (Exception ex) { // Unable to establish a connection with the client. The client is responsible for // handling this case. Nothing else for us to do here. ServerLogger.LogException(ex, "Error creating client named pipe"); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } try { using (connection) { ServerRequest request; try { ServerLogger.Log("Begin reading request."); request = await ServerRequest.ReadAsync(connection.Stream, cancellationToken).ConfigureAwait(false); ServerLogger.Log("End reading request."); } catch (Exception e) { ServerLogger.LogException(e, "Error reading build request."); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } if (request.IsShutdownRequest()) { // Reply with the PID of this process so that the client can wait for it to exit. var response = new ShutdownServerResponse(Environment.ProcessId); await response.WriteAsync(connection.Stream, cancellationToken); // We can safely disconnect the client, then when this connection gets cleaned up by the event loop // the server will go to a shutdown state. return(new ConnectionResult(ConnectionResult.Reason.ClientShutdownRequest)); } else if (!accept) { // We're already in shutdown mode, respond gracefully so the client can run in-process. var response = new RejectedServerResponse(); await response.WriteAsync(connection.Stream, cancellationToken).ConfigureAwait(false); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } else { // If we get here then this is a real request that we will accept and process. // // Kick off both the compilation and a task to monitor the pipe for closing. var buildCancelled = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var watcher = connection.WaitForDisconnectAsync(buildCancelled.Token); var worker = ExecuteRequestAsync(request, buildCancelled.Token); // await will end when either the work is complete or the connection is closed. await Task.WhenAny(worker, watcher); // Do an 'await' on the completed task, preference being compilation, to force // any exceptions to be realized in this method for logging. ConnectionResult.Reason reason; if (worker.IsCompleted) { var response = await worker; try { ServerLogger.Log("Begin writing response."); await response.WriteAsync(connection.Stream, cancellationToken); ServerLogger.Log("End writing response."); reason = ConnectionResult.Reason.CompilationCompleted; _eventBus.CompilationCompleted(); } catch { reason = ConnectionResult.Reason.ClientDisconnect; } } else { await watcher; reason = ConnectionResult.Reason.ClientDisconnect; } // Begin the tear down of the Task which didn't complete. buildCancelled.Cancel(); return(new ConnectionResult(reason, request.KeepAlive)); } } } catch (Exception ex) { ServerLogger.LogException(ex, "Error handling connection"); return(new ConnectionResult(ConnectionResult.Reason.ClientException)); } }
private static async Task <ServerResponse> RunOnServerCore( IList <string> arguments, ServerPaths serverPaths, string pipeName, string keepAlive, int?timeoutOverride, TryCreateServerCoreDelegate <string, string, int?, bool, bool> tryCreateServerFunc, CancellationToken cancellationToken, bool debug) { if (pipeName == null) { return(new RejectedServerResponse()); } if (serverPaths.TempDirectory == null) { return(new RejectedServerResponse()); } var clientDir = serverPaths.ClientDirectory; var timeoutNewProcess = timeoutOverride ?? TimeOutMsNewProcess; var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess; var clientMutexName = MutexName.GetClientMutexName(pipeName); Task <Client> pipeTask = null; Mutex clientMutex = null; var holdsMutex = false; try { try { clientMutex = new Mutex(initiallyOwned: true, name: clientMutexName, createdNew: out holdsMutex); } catch (Exception ex) { // The Mutex constructor can throw in certain cases. One specific example is docker containers // where the /tmp directory is restricted. In those cases there is no reliable way to execute // the server and we need to fall back to the command line. // Example: https://github.com/dotnet/roslyn/issues/24124 ServerLogger.LogException(ex, "Client mutex creation failed."); return(new RejectedServerResponse()); } if (!holdsMutex) { try { holdsMutex = clientMutex.WaitOne(timeoutNewProcess); if (!holdsMutex) { return(new RejectedServerResponse()); } } catch (AbandonedMutexException) { holdsMutex = true; } } // Check for an already running server var serverMutexName = MutexName.GetServerMutexName(pipeName); var wasServerRunning = WasServerMutexOpen(serverMutexName); var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess; if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName, out var _, debug)) { pipeTask = Client.ConnectAsync(pipeName, TimeSpan.FromMilliseconds(timeout), cancellationToken); } } finally { if (holdsMutex) { clientMutex?.ReleaseMutex(); } clientMutex?.Dispose(); } if (pipeTask != null) { var client = await pipeTask.ConfigureAwait(false); if (client != null) { var request = ServerRequest.Create( serverPaths.WorkingDirectory, serverPaths.TempDirectory, arguments, keepAlive); return(await TryProcessRequest(client, request, cancellationToken).ConfigureAwait(false)); } } return(new RejectedServerResponse()); }
protected override Task <int> ExecuteCoreAsync() { // Make sure there's only one server with the same identity at a time. var serverMutexName = MutexName.GetServerMutexName(Pipe.Value()); Mutex serverMutex = null; var holdsMutex = false; try { serverMutex = new Mutex(initiallyOwned: true, name: serverMutexName, createdNew: out holdsMutex); } catch (Exception ex) { // The Mutex constructor can throw in certain cases. One specific example is docker containers // where the /tmp directory is restricted. In those cases there is no reliable way to execute // the server and we need to fall back to the command line. // Example: https://github.com/dotnet/roslyn/issues/24124 Error.Write($"Server mutex creation failed. {ex.Message}"); return(Task.FromResult(-1)); } if (!holdsMutex) { // Another server is running, just exit. Error.Write("Another server already running..."); return(Task.FromResult(1)); } FileStream pidFileStream = null; try { try { // Write the process and pipe information to a file in a well-known location. pidFileStream = WritePidFile(); } catch (Exception ex) { // Something happened when trying to write to the pid file. Log and move on. ServerLogger.LogException(ex, "Failed to create PID file."); } TimeSpan?keepAlive = null; if (KeepAlive.HasValue() && int.TryParse(KeepAlive.Value(), out var result)) { // Keep alive times are specified in seconds keepAlive = TimeSpan.FromSeconds(result); } var host = ConnectionHost.Create(Pipe.Value()); var compilerHost = CompilerHost.Create(); ExecuteServerCore(host, compilerHost, Cancelled, eventBus: null, keepAlive: keepAlive); } finally { serverMutex.ReleaseMutex(); serverMutex.Dispose(); pidFileStream?.Close(); } return(Task.FromResult(0)); }
// Internal for testing. internal static bool TryCreateServerCore(string clientDir, string pipeName, out int?processId, bool debug = false) { processId = null; // The server should be in the same directory as the client var expectedCompilerPath = Path.Combine(clientDir, ServerName); var expectedPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet"; var argumentList = new string[] { expectedCompilerPath, debug ? "--debug" : "", "server", "-p", pipeName }; var processArguments = ArgumentEscaper.EscapeAndConcatenate(argumentList); if (!File.Exists(expectedCompilerPath)) { return(false); } if (PlatformInformation.IsWindows) { // Currently, there isn't a way to use the Process class to create a process without // inheriting handles(stdin/stdout/stderr) from its parent. This might cause the parent process // to block on those handles. So we use P/Invoke. This code was taken from MSBuild task starting code. // The work to customize this behavior is being tracked by https://github.com/dotnet/corefx/issues/306. var startInfo = new STARTUPINFO(); startInfo.cb = Marshal.SizeOf(startInfo); startInfo.hStdError = NativeMethods.InvalidIntPtr; startInfo.hStdInput = NativeMethods.InvalidIntPtr; startInfo.hStdOutput = NativeMethods.InvalidIntPtr; startInfo.dwFlags = NativeMethods.STARTF_USESTDHANDLES; var dwCreationFlags = NativeMethods.NORMAL_PRIORITY_CLASS | NativeMethods.CREATE_NO_WINDOW; ServerLogger.Log("Attempting to create process '{0}'", expectedPath); var builder = new StringBuilder($@"""{expectedPath}"" {processArguments}"); var success = NativeMethods.CreateProcess( lpApplicationName: null, lpCommandLine: builder, lpProcessAttributes: NativeMethods.NullPtr, lpThreadAttributes: NativeMethods.NullPtr, bInheritHandles: false, dwCreationFlags: dwCreationFlags, lpEnvironment: NativeMethods.NullPtr, // Inherit environment lpCurrentDirectory: clientDir, lpStartupInfo: ref startInfo, lpProcessInformation: out var processInfo); if (success) { ServerLogger.Log("Successfully created process with process id {0}", processInfo.dwProcessId); NativeMethods.CloseHandle(processInfo.hProcess); NativeMethods.CloseHandle(processInfo.hThread); processId = processInfo.dwProcessId; } else { ServerLogger.Log("Failed to create process. GetLastError={0}", Marshal.GetLastWin32Error()); } return(success); } else { try { var startInfo = new ProcessStartInfo() { FileName = expectedPath, Arguments = processArguments, UseShellExecute = false, WorkingDirectory = clientDir, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; var process = Process.Start(startInfo); processId = process.Id; return(true); } catch { return(false); } } }