public static ShellResult ExecuteCommand(ShellArgs shellArgs) { shellArgs.Command = shellArgs.Command ?? string.Empty; shellArgs.Args = shellArgs.Args ?? string.Empty; shellArgs.WorkingDirectory = shellArgs.WorkingDirectory ?? string.Empty; ShellResult result = new ShellResult(); result.Command = shellArgs.Command; result.Args = shellArgs.Args; result.Output = string.Empty; result.Error = string.Empty; if (shellArgs.Monitor != null) { if (shellArgs.Monitor.AbortRequested) { result.Error = USER_ABORTED_LOG; return(result); } shellArgs.Monitor.AppendCommand(shellArgs.Command, shellArgs.Args); } ProcessStartInfo processStartInfo = new ProcessStartInfo(shellArgs.Command, shellArgs.Args); processStartInfo.RedirectStandardOutput = true; processStartInfo.RedirectStandardError = true; processStartInfo.CreateNoWindow = true; processStartInfo.WindowStyle = ProcessWindowStyle.Hidden; processStartInfo.UseShellExecute = false; processStartInfo.WorkingDirectory = shellArgs.WorkingDirectory; Process process; try { process = Process.Start(processStartInfo); } catch (System.Exception ex) { // Most probably file not found. result.Error = ex.ToString(); if (shellArgs.Monitor != null) { shellArgs.Monitor.AppendErrorLine(result.Error); } return(result); } // Run and forget... // TODO: No Process.Dispose() called - leak?! if (!shellArgs.WaitForOutput) { result.Output = string.Empty; result.Error = string.Empty; return(result); } // // Handle aborting. // ShellRequestAbortEventHandler abortHandler = null; if (shellArgs.Monitor != null) { abortHandler = (bool kill) => { shellArgs.Monitor?.AppendErrorLine(USER_ABORTED_LOG); // TODO: Is this thread safe? if (kill) { process.Kill(); } else { process.CloseMainWindow(); } }; shellArgs.Monitor.RequestAbort += abortHandler; } // // Subscribe for standard output. // DataReceivedEventHandler outputReadLineHandler = null; outputReadLineHandler = (sender, args) => { if (args.Data != null) { result.Output += args.Data + "\n"; if (shellArgs.Monitor != null) { shellArgs.Monitor.AppendOutputLine(args.Data); } } }; process.OutputDataReceived += outputReadLineHandler; process.BeginOutputReadLine(); // // Subscribe for error output. // DataReceivedEventHandler errorReadLineHandler = null; errorReadLineHandler = (sender, args) => { if (args.Data != null) { result.Error += args.Data + "\n"; if (shellArgs.Monitor != null) { shellArgs.Monitor.AppendErrorLine(args.Data); } } }; process.ErrorDataReceived += errorReadLineHandler; process.BeginErrorReadLine(); if (shellArgs.WaitTimeout < 0) { process.WaitForExit(); } else { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (!process.HasExited && stopwatch.ElapsedMilliseconds < shellArgs.WaitTimeout) { Thread.Sleep(20); } stopwatch.Stop(); // If process is still running, the timeout kicked in. if (!process.HasExited) { result.Error += $"Command [{shellArgs.Command} {shellArgs.Args}] timed out."; } } process.OutputDataReceived -= outputReadLineHandler; process.ErrorDataReceived -= errorReadLineHandler; if (shellArgs.Monitor != null) { shellArgs.Monitor.RequestAbort -= abortHandler; } process.Dispose(); return(result); }
public static ShellResult ExecuteCommand(ShellArgs shellArgs) { shellArgs.Command = shellArgs.Command ?? string.Empty; shellArgs.Args = shellArgs.Args ?? string.Empty; shellArgs.WorkingDirectory = shellArgs.WorkingDirectory ?? string.Empty; ShellResult result = new ShellResult(); result.Command = shellArgs.Command; result.Args = shellArgs.Args; result.Output = string.Empty; result.Error = string.Empty; if (shellArgs.Monitor != null) { if (shellArgs.Monitor.AbortRequested) { result.Error = USER_ABORTED_LOG; return(result); } shellArgs.Monitor.AppendCommand(shellArgs.Command, shellArgs.Args); } ProcessStartInfo processStartInfo = new ProcessStartInfo(shellArgs.Command, shellArgs.Args); processStartInfo.RedirectStandardOutput = true; processStartInfo.RedirectStandardError = true; processStartInfo.RedirectStandardInput = true; processStartInfo.CreateNoWindow = true; processStartInfo.WindowStyle = ProcessWindowStyle.Hidden; processStartInfo.UseShellExecute = false; processStartInfo.WorkingDirectory = shellArgs.WorkingDirectory; Process process; try { process = Process.Start(processStartInfo); // Input is not supported so close it directly. Prevents hangs when CLI prompts the user for something (authentication for example). process.StandardInput.Close(); } catch (System.Exception ex) { // Most probably file not found. result.Error = ex.ToString(); if (shellArgs.Monitor != null) { shellArgs.Monitor.AppendErrorLine(result.Error); } return(result); } // Run and forget... // TODO: No Process.Dispose() called - leak?! if (!shellArgs.WaitForOutput) { result.Output = string.Empty; result.Error = string.Empty; return(result); } // // Handle aborting. // ShellRequestAbortEventHandler abortHandler = null; if (shellArgs.Monitor != null) { abortHandler = (bool kill) => { shellArgs.Monitor?.AppendErrorLine(USER_ABORTED_LOG); // TODO: Is this thread safe? if (kill) { process.Kill(); } else { process.CloseMainWindow(); } }; shellArgs.Monitor.RequestAbort += abortHandler; } // // Subscribe for standard output. // StringBuilder outputBuilder = new StringBuilder(); DataReceivedEventHandler outputReadLineHandler = null; outputReadLineHandler = (sender, args) => { // Lock - check the Builder usage at the end. lock (outputBuilder) { if (args.Data != null) { outputBuilder.AppendLine(args.Data); if (shellArgs.Monitor != null) { shellArgs.Monitor.AppendOutputLine(args.Data); } } } }; process.OutputDataReceived += outputReadLineHandler; process.BeginOutputReadLine(); // // Subscribe for error output. // StringBuilder errorBuilder = new StringBuilder(); DataReceivedEventHandler errorReadLineHandler = null; errorReadLineHandler = (sender, args) => { // Lock - check the Builder usage at the end. lock (errorBuilder) { if (args.Data != null) { errorBuilder.AppendLine(args.Data); if (shellArgs.Monitor != null) { shellArgs.Monitor.AppendErrorLine(args.Data); } } } }; process.ErrorDataReceived += errorReadLineHandler; process.BeginErrorReadLine(); if (shellArgs.WaitTimeout < 0) { process.WaitForExit(); } else { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); while (!process.HasExited && stopwatch.ElapsedMilliseconds < shellArgs.WaitTimeout) { Thread.Sleep(20); } stopwatch.Stop(); // If process is still running, the timeout kicked in. if (!process.HasExited) { result.Error += $"Command [{shellArgs.Command} {shellArgs.Args}] timed out."; } } process.OutputDataReceived -= outputReadLineHandler; lock (outputBuilder) { // When the main thread gets here, the process will not be running (unless it timed out), // but the OutputDataReceived thread might still be appending the final strings. Lock it! result.Output = outputBuilder.ToString(); } process.ErrorDataReceived -= errorReadLineHandler; lock (errorBuilder) { // Same as above. Concat if error was present. result.Error += errorBuilder.ToString(); } if (shellArgs.Monitor != null) { shellArgs.Monitor.RequestAbort -= abortHandler; } // Dispose() (invoking Close()) will wait for the process to finish. // If process is stuck, this will hang Unity on recompile / exit. // Not calling Dispose() in that regard will leak some resources / processes, but that shouldn't be the normal case anyway. if (process.HasExited) { process.Dispose(); } return(result); }