public async Task DeleteAsync() { await BashCommand($@"kind delete cluster --name ""{this.name}""") .WithStandardOutputPipe(PipeTarget.ToDelegate(line => Console.WriteLine(line))) .WithStandardErrorPipe(PipeTarget.ToDelegate(line => Console.WriteLine(line))) .ExecuteAsync(); }
internal static async Task ExecuteInternal(Action <ArgumentsBuilder> configure, CancellationToken cancellationToken) { ThrowIfNull(ToolPath, nameof(SdkManager)); var stdErrBuffer = new StringBuilder(); await Cli.Wrap(ToolPath) .WithArguments(configure) .WithValidation(CommandResultValidation.None) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) .WithStandardOutputPipe(PipeTarget.ToDelegate(l => Logger.WriteLine(l, LogLevel.Detailed))) .WithStandardInputPipe(PipeSource.FromString("y")) .ExecuteAsync(cancellationToken); var stdErr = stdErrBuffer.ToString().Trim(); if (!string.IsNullOrEmpty(stdErr)) { if (stdErr.Split('\n').Select(x => x.Trim()).All(x => x.StartsWith("Warning:"))) { Logger.WriteWarning(stdErr); } else { throw new Exception(stdErr); } } }
private async void StartStream(YoutubeClient youtube, AudioClient audioClient, AudioOnlyStreamInfo streamInfo, IMessage?message) { Stream ytStream = await youtube.Videos.Streams.GetAsync(streamInfo); // Convert yt stream MemoryStream memoryStream = new MemoryStream(); await Cli.Wrap("ffmpeg") .WithArguments(" -hide_banner -loglevel panic -i pipe:0 -ac 2 -f s16le -ar 48000 pipe:1") .WithStandardInputPipe(PipeSource.FromStream(ytStream)) .WithStandardOutputPipe(PipeTarget.ToStream(memoryStream)) .ExecuteAsync(); // Clear stream before beginning if (audioClient.CurrentStream != null) { audioClient.CurrentStream.Dispose(); audioClient.CurrentStream = null; } AudioOutStream discord = audioClient.Client.CreatePCMStream(AudioApplication.Mixed); audioClient.CurrentStream = discord; // Delete calling command if (message != null) { await message.DeleteAsync(); } // Start playing music await this.WriteStreamToVoiceChannel(audioClient, discord, memoryStream); }
public static async Task <bool> InstallPackage(string packageName, CancellationToken cancellationToken) { Console.WriteLine($"npm install -g {packageName}"); var stdErrBuffer = new StringBuilder(); var stdOut = PipeTarget.ToDelegate(l => Console.WriteLine(l)); await Cli.Wrap("npm") .WithArguments($"install -g {packageName}") .WithStandardOutputPipe(stdOut) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) .ExecuteAsync(cancellationToken); if (cancellationToken.IsCancellationRequested) { return(false); } var errorOutput = stdErrBuffer.ToString().Trim(); if (string.IsNullOrEmpty(errorOutput)) { return(true); } throw new Exception(errorOutput); }
private async Task <HashSet <string> > ListFileAsync() { var set = new HashSet <string>(_stringComparer); var result = await Cli.Wrap("git") .WithArguments("ls-files") .WithWorkingDirectory(_workingDirectory) .WithValidation(CommandResultValidation.None) .WithStandardOutputPipe(PipeTarget.ToDelegate(line => { line = _fileSystem.Path.Normalize(line); if (line.Contains(Bin) || line.Contains(Obj)) { set.Add(Path.Combine(_workingDirectory, line)); } })) .ExecuteAsync(); if (result.ExitCode != 0) { set.Clear(); } return(set); }
static async Task Compile(string projectFile, string outputDir) { Regex errorRegex = new Regex(@"^[\w\d.\\-]+\(\d+,\d+\):"); string suffix = $" [{projectFile}]"; var command = Cli.Wrap(await Tester.FindMSBuild()) .WithArguments($"/nologo /v:minimal /restore /p:OutputPath=\"{outputDir}\" \"{projectFile}\"") .WithValidation(CommandResultValidation.None) .WithStandardOutputPipe(PipeTarget.ToDelegate(PrintLine)); Console.WriteLine($"\"{command.TargetFilePath}\" {command.Arguments}"); var result = await command.ExecuteAsync().ConfigureAwait(false); if (result.ExitCode != 0) { throw new CompilationFailedException($"Compilation of {Path.GetFileName(projectFile)} failed"); } void PrintLine(string line) { if (line.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) { line = line.Substring(0, line.Length - suffix.Length); } Match m = errorRegex.Match(line); if (m.Success) { // Make path absolute so that it gets hyperlinked line = Path.GetDirectoryName(projectFile) + Path.DirectorySeparatorChar + line; } Console.WriteLine(line); } }
public async Task Serialize(TestDescription testDescription) { var extension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""; var execPath = Path.Combine(testDescription.Directory, testDescription.Name + extension); var standardOutput = new StringBuilder(); var errorOutput = new StringBuilder(); try { await Cli.Wrap(execPath) .WithArguments("Serialize") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(standardOutput)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(errorOutput)) .ExecuteAsync(); } catch (Exception e) { throw new Exception("Failed to run serialization for version " + testDescription.Name, e); } finally { Console.WriteLine(errorOutput.ToString()); Console.WriteLine(standardOutput.ToString()); } }
internal static async Task <string> ExecuteInternal(Action <ArgumentsBuilder> configure, CancellationToken cancellationToken) { var toolPath = ToolPath; ThrowIfNull(toolPath, nameof(Emulator)); var builder = new ArgumentsBuilder(); configure(builder); var args = builder.Build(); Logger.WriteLine($"{toolPath} {args}", LogLevel.Normal); var stdErrBuffer = new StringBuilder(); var stdOutBuffer = new StringBuilder(); var stdOut = PipeTarget.Merge(PipeTarget.ToStringBuilder(stdOutBuffer), PipeTarget.ToDelegate(l => Logger.WriteLine(l, LogLevel.Verbose))); await Cli.Wrap(toolPath) .WithArguments(args) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) .WithStandardOutputPipe(stdOut) .ExecuteAsync(cancellationToken); var stdErr = stdErrBuffer.ToString().Trim(); if (!string.IsNullOrEmpty(stdErr)) { throw new Exception(stdErr); } return(stdOutBuffer.ToString().Trim()); }
private static async Task <bool> IsValidVideoFile(FileSystemInfo file, FrameFinderOptions frameFinderOptions) { var standardOutput = new StringBuilder(); var standardErrorOutput = new StringBuilder(); var result = await Cli.Wrap(frameFinderOptions.FFprobeBinaryPath) .WithArguments($"-print_format json -show_format -show_streams \"{file.FullName}\"") .WithStandardOutputPipe(PipeTarget.ToStringBuilder(standardOutput)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(standardErrorOutput)) .WithValidation(CommandResultValidation.None) .ExecuteAsync(); if (result.ExitCode == 0) { var json = standardOutput.ToString(); var parsed = JsonSerializer.Deserialize <FFprobeOutput>(json); if (parsed?.Format.Duration == null) { return(false); } var parsedDuration = double.Parse(parsed.Format.Duration, CultureInfo.InvariantCulture); var duration = TimeSpan.FromSeconds(parsedDuration); return(duration > frameFinderOptions.MinimumIntroTime); } return(false); }
internal static async Task ExecuteInternal(Action <ArgumentsBuilder> configure, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return; } var builder = new ArgumentsBuilder(); configure(builder); var args = builder.Build(); Logger.WriteLine($"{ToolPath} {args}", LogLevel.Normal); var stdOutBuffer = new StringBuilder(); var stdOut = PipeTarget.Merge(PipeTarget.ToStringBuilder(stdOutBuffer), PipeTarget.ToDelegate(l => Logger.WriteLine(l, LogLevel.Verbose))); var stdError = PipeTarget.ToDelegate(l => { if (string.IsNullOrEmpty(l)) { return; } // Suppress errors Logger.WriteWarning(l); }); var result = await Cli.Wrap(ToolPath) .WithArguments(args) .WithValidation(CommandResultValidation.None) .WithStandardErrorPipe(stdError) .WithStandardOutputPipe(stdOut) .ExecuteAsync(cancellationToken); }
public static string Command(string executable, string arguments, string workingDirectory) { var stdOutBuffer = new StringBuilder(); var stdErrBuffer = new StringBuilder(); var command = CliWrap.Cli.Wrap(executable).WithArguments( args => args .Add(arguments.Split()) ) .WithWorkingDirectory(workingDirectory) .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)); try { using var task = command.ExecuteAsync().Task; task.Wait(); } catch (AggregateException) { throw new IOException(stdErrBuffer.ToString()); } return(stdOutBuffer.ToString()); }
public void ExecuteFor(IEnumerable <RemoteRepo> repositories) { string Template(RemoteRepo r) { var args = _arguments; args = args .Replace("$repo-name", r.Name) .Replace("$proj-key", r.ProjectKey) .Replace("$http-url", r.HttpUrl) .Replace("$ssh-url", r.SshUrl) .Replace("$browser-url", r.Links.Self.FirstOrDefault()?.Href); return(args); } repositories.SafelyForEach( r => { var workingDirPath = _useCurrentDirectory ? Directory.GetCurrentDirectory() : Path.Join(Directory.GetCurrentDirectory(), r.Name); Cli.Wrap(_program) .WithArguments(Template(r)) .WithWorkingDirectory(workingDirPath) .WithStandardOutputPipe(PipeTarget.ToDelegate(_console.WriteLine)) .WithStandardErrorPipe(PipeTarget.ToDelegate(_console.Error.WriteLine)) .WithValidation(CommandResultValidation.None) .ExecuteAsync(_cancellationToken).Task.Wait(_cancellationToken); }, _cancellationToken, summarizeErrors: true); }
public async Task <string> ConvertKarToOgg(string inputFile) { var outputFilename = Path.Combine(_settings.SongCacheDir, Path.GetFileNameWithoutExtension(inputFile)) + _settings.ConvertedSongFormatExtension; if (File.Exists(outputFilename)) { return(outputFilename); } using var memStream = new MemoryStream(1024000); var guidStr = Guid.NewGuid().ToString("D"); var timidity = Cli.Wrap(_settings.TimidityPath) .WithArguments($"-Ow -W- --preserve-silence \"{inputFile}\" -o \"{guidStr}.wav\""); var lame = Cli.Wrap(_settings.LamePath) .WithArguments($"{guidStr}.wav \"{outputFilename}\"") .WithStandardErrorPipe(PipeTarget.ToDelegate(Log)); await timidity.ExecuteAsync(); await lame.ExecuteAsync(); File.Delete($"{guidStr}.wav"); return(outputFilename); }
public static async Task <bool> InstallPackage(string packageName, CancellationToken cancellationToken) { Console.WriteLine($"npm install -g {packageName}"); var isMac = EnvironmentHelper.IsRunningOnMac; var errorLines = new List <string>(); var stdOut = PipeTarget.ToDelegate(l => Console.WriteLine(l)); var stdErr = PipeTarget.ToDelegate(l => { if (string.IsNullOrEmpty(l) || (isMac && l.Contains("did not detect a Windows system"))) { return; } errorLines.Add(l); }); await Cli.Wrap("npm") .WithArguments($"install -g {packageName}") .WithValidation(CommandResultValidation.None) .WithStandardOutputPipe(stdOut) .WithStandardErrorPipe(stdErr) .ExecuteAsync(cancellationToken); if (cancellationToken.IsCancellationRequested) { return(false); } if (errorLines.Any()) { return(true); } throw new Exception(string.Join(Environment.NewLine, errorLines)); }
public async Task I_can_execute_a_command_that_pipes_its_stdout_into_multiple_streams() { // Arrange const int expectedSize = 100_000; await using var stream1 = new MemoryStream(); await using var stream2 = new MemoryStream(); await using var stream3 = new MemoryStream(); var pipeTarget = PipeTarget.Merge( PipeTarget.ToStream(stream1), PipeTarget.ToStream(stream2), PipeTarget.ToStream(stream3) ); var cmd = Cli.Wrap("dotnet") .WithArguments(a => a .Add(Dummy.Program.FilePath) .Add(Dummy.Program.PrintRandomBinary) .Add(expectedSize)) | pipeTarget; // Act await cmd.ExecuteAsync(); // Assert stream1.Length.Should().Be(expectedSize); stream2.Length.Should().Be(expectedSize); stream3.Length.Should().Be(expectedSize); stream1.ToArray().Should().BeEquivalentTo(stream2.ToArray()); stream2.ToArray().Should().BeEquivalentTo(stream3.ToArray()); }
private async Task PlaySong(YoutubeExplode.Videos.Video input) { try { await audioClient.SetSpeakingAsync(true); var streamManifest = await youtube.Videos.Streams.GetManifestAsync(input.Id, Skip.Token); var streamInfo = streamManifest.GetAudioOnlyStreams().GetWithHighestBitrate(); var stream = await youtube.Videos.Streams.GetAsync(streamInfo, Skip.Token); var memoryStream = new MemoryStream(); await Cli.Wrap("ffmpeg") .WithArguments(" -hide_banner -loglevel panic -i pipe:0 -ac 2 -f s16le -ar 48000 pipe:1") .WithStandardInputPipe(PipeSource.FromStream(stream)) .WithStandardOutputPipe(PipeTarget.ToStream(memoryStream)) .ExecuteAsync(Skip.Token); using (var discord = audioClient.CreatePCMStream(AudioApplication.Music)) { try { await discord.WriteAsync(memoryStream.ToArray(), 0, (int)memoryStream.Length, Skip.Token); } finally { await discord.FlushAsync(); } } } catch (Exception) { } }
public static EsiSystem ConvertTextSchema(EsiContext ctxt, FileInfo file) { var exeDir = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName; using (var memstream = new MemoryStream()) { var errorStringBuilder = new StringBuilder(); var capnpCmd = Cli.Wrap("capnp") .WithEnvironmentVariables(new Dictionary <string, string>() { ["LD_LIBRARY_PATH"] = exeDir }) .WithArguments($"compile -I{Path.Join(Esi.Utils.RootDir.FullName, "capnp.convert")} -o- {file.FullName}") .WithStandardOutputPipe(PipeTarget.ToStream(memstream)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(errorStringBuilder)) .WithValidation(CommandResultValidation.ZeroExitCode); try { Task.Run(async() => await capnpCmd.ExecuteAsync()).Wait(); } catch (AggregateException ex) { if (ex.InnerException is CommandExecutionException cee) { ctxt.Log.Error($"CapNProto Error:\n{errorStringBuilder.ToString()}"); } ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } Debug.Assert(memstream.Length > 0); memstream.Seek(0, SeekOrigin.Begin); return(ConvertFromCGRMessage(ctxt, memstream)); } }
public async Task <string> DownloadMediaFile(string id, string outputDirectory, string _, CancellationToken ct) { var tcs = new TaskCompletionSource <object>(); var stdout = new StringBuilder(); var stderr = new StringBuilder(); var command = $"youtube-dl -o \"%(title)s-%(id)s.%(ext)s\" -f worst --write-info-json \"{id}\""; var cmd = Cli.Wrap("youtube-dl") .WithArguments($"-o \"%(title)s-%(id)s.%(ext)s\" -f worst --write-info-json \"{id}\"") .WithWorkingDirectory(outputDirectory) .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdout)) .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stderr)); try { await cmd.ExecuteAsync(ct); } catch (Exception) { throw new Exception(stderr.ToString()); } var fileInfo = GetFileInfo(stdout.ToString(), outputDirectory); return(fileInfo.FileName); }
private static async Task Execute(string args, LogLevel logLevel, CancellationToken cancellationToken) { Logger.WriteLine($"{DotNetExe.FullPath} {args}", LogLevel.Normal); var stdErrBuffer = new StringBuilder(); var stdOut = PipeTarget.ToDelegate(l => Logger.WriteLine(l, logLevel)); var stdErr = PipeTarget.ToStringBuilder(stdErrBuffer); var result = await Cli.Wrap(DotNetExe.FullPath) .WithArguments(args) .WithStandardErrorPipe(stdOut) .WithStandardErrorPipe(stdErr) .WithValidation(CommandResultValidation.None) .ExecuteAsync(cancellationToken) .ConfigureAwait(false); var error = stdErrBuffer.ToString().Trim(); if (!string.IsNullOrEmpty(error)) { throw new Exception(error); } if (result.ExitCode > 0) { throw new Exception($"The dotnet tool unexpectidly exited without any error output with code: {result.ExitCode}"); } }
public static IGit New() { var consoleOutputTarget = PipeTarget.ToStream(Console.OpenStandardOutput()); var command = Cli.Wrap("git") .WithStandardOutputPipe(consoleOutputTarget); return(new Git(command)); }
public Task ProcessAsync(IReadOnlyList <string> inputFilePaths, string outputFilePath, string format, bool transcode, IEnumerable <string> additionalArgs = null, IProgress <double> progress = null, CancellationToken cancellationToken = default(CancellationToken)) { var args = new List <string>(); // Set input files foreach (var inputFilePath in inputFilePaths) { args.Add($"-i \"{inputFilePath}\""); } // Set output format args.Add($"-f {format}"); // Skip transcoding if it's not required if (!transcode) { args.Add("-c copy"); } // Optimize mp4 transcoding if (transcode && string.Equals(format, "mp4", StringComparison.OrdinalIgnoreCase)) { args.Add("-preset ultrafast"); } // Set max threads args.Add($"-threads {Environment.ProcessorCount}"); // Disable stdin so that the process will not hang waiting for user input args.Add("-nostdin"); // Trim streams to shortest args.Add("-shortest"); if (additionalArgs != null) { args.AddRange(additionalArgs); } // Overwrite files args.Add("-y"); // Set output file args.Add($"\"{outputFilePath}\""); // Set up progress router var progressRouter = new FfmpegProgressRouter(progress); // Run CLI return(Cli.Wrap(_ffmpegFilePath) .WithWorkingDirectory(Directory.GetCurrentDirectory()) .WithArguments(args.JoinToString(" ")) .WithStandardErrorPipe(PipeTarget.ToDelegate((line) => progressRouter.ProcessLine(line))) // handle stderr to parse and route progress .WithValidation(CommandResultValidation.None) // disable stderr validation because ffmpeg writes progress there .ExecuteAsync(cancellationToken)); }
public async Task <int> ExecuteWithCliWrap() { await using var stream1 = new MemoryStream(); await using var stream2 = new MemoryStream(); var result = await(Cli.Wrap(FilePath).WithArguments(Args) | PipeTarget.Merge(PipeTarget.ToStream(stream1), PipeTarget.ToStream(stream2))).ExecuteAsync(); return(result.ExitCode); }
public static Task <IDisposable> Run(string baseWorkingDirectory) { var completed = false; var tcs = new TaskCompletionSource <IDisposable>(); var cancellationSource = new CancellationTokenSource(); var logDirectory = Path.Combine(baseWorkingDirectory, "logs"); if (!Directory.Exists(logDirectory)) { Directory.CreateDirectory(logDirectory); } void HandleConsoleLine(string line) { if (line.Contains("listener started on 0.0.0.0:4723")) { Logger.WriteLine(line, LogLevel.Minimal, defaultLog); if (!completed) { tcs.SetResult(new AppiumTask(cancellationSource)); } completed = true; } else if (line.Contains("make sure there is no other instance of this server running already") || line.Contains("listen EADDRINUSE: address already in use 0.0.0.0:4723")) { Logger.WriteWarning(line, defaultLog); if (!completed) { tcs.SetResult(new AppiumTask(cancellationSource)); } completed = true; } else { Logger.WriteLine(line, LogLevel.Verbose, defaultLog); } } var stdOut = PipeTarget.ToDelegate(HandleConsoleLine); var stdErr = PipeTarget.Merge( PipeTarget.ToFile(Path.Combine(logDirectory, "appium-error.log")), PipeTarget.ToDelegate(HandleConsoleLine)); Logger.WriteLine("Starting Appium...", LogLevel.Minimal); var toolPath = EnvironmentHelper.GetToolPath("appium"); var cmd = Cli.Wrap(toolPath) .WithStandardOutputPipe(stdOut) .WithStandardErrorPipe(stdErr) .WithValidation(CommandResultValidation.None) .ExecuteAsync(cancellationSource.Token); return(tcs.Task); }
public async Task <IKubernetes> GetClientAsync() { string path = string.Empty; await BashCommand($@"kind get kubeconfig-path --name ""{this.name}""") .WithStandardOutputPipe(PipeTarget.ToDelegate(line => path = line)) .WithStandardErrorPipe(PipeTarget.ToDelegate(line => Console.WriteLine(line))) .ExecuteAsync(); return(new Kubernetes(KubernetesClientConfiguration.BuildConfigFromConfigFile(path))); }
public void Stderr_pipe_can_be_set() { // Arrange var cmd = Cli.Wrap("foo").WithStandardErrorPipe(PipeTarget.Null); // Act var cmdOther = cmd.WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null)); // Assert cmd.Should().BeEquivalentTo(cmdOther, o => o.Excluding(c => c.StandardErrorPipe)); cmd.StandardErrorPipe.Should().NotBeSameAs(cmdOther.StandardErrorPipe); }
public static Command AddTestOutputPipe(this Command command, ITestOutputHelper testOutputHelper) { return(command .WithStandardOutputPipe( PipeTarget.Merge( command.StandardOutputPipe, PipeTarget.ToDelegate(testOutputHelper.WriteLine))) .WithStandardErrorPipe( PipeTarget.Merge( command.StandardErrorPipe, PipeTarget.ToDelegate(testOutputHelper.WriteLine)))); }
public void I_can_create_a_new_command_from_an_existing_one_by_specifying_different_stderr_pipe() { // Arrange var cmd = Cli.Wrap("foo").WithStandardErrorPipe(PipeTarget.Null); // Act var cmdOther = cmd.WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null)); // Assert cmd.Should().BeEquivalentTo(cmdOther, o => o.Excluding(c => c.StandardErrorPipe)); cmd.StandardErrorPipe.Should().NotBeSameAs(cmdOther.StandardErrorPipe); }
private static async Task <string> ExecuteInternal(Action <ArgumentsBuilder> configure, CancellationToken cancellationToken, PipeSource stdInput = null) { var toolPath = ToolPath; ThrowIfNull(toolPath, nameof(AvdManager)); var builder = new ArgumentsBuilder(); configure(builder); var args = builder.Build(); Logger.WriteLine($"{toolPath} {args}", LogLevel.Normal); var errorBuffer = new List <string>(); var stdOutBuffer = new StringBuilder(); var stdOut = PipeTarget.Merge(PipeTarget.ToStringBuilder(stdOutBuffer), PipeTarget.ToDelegate(l => Logger.WriteLine(l, LogLevel.Verbose))); var stdErr = PipeTarget.ToDelegate(l => { if (string.IsNullOrEmpty(l)) { return; } else if (l.Contains("Warning: ")) { Logger.WriteWarning(l); } else { errorBuffer.Add(l); } }); var cmd = Cli.Wrap(toolPath) .WithArguments(args) .WithValidation(CommandResultValidation.None) .WithStandardErrorPipe(stdErr) .WithStandardOutputPipe(stdOut); if (stdInput != null) { cmd = cmd.WithStandardInputPipe(stdInput); } await cmd.ExecuteAsync(cancellationToken); if (errorBuffer.Any()) { throw new Exception(string.Join(Environment.NewLine, errorBuffer)); } return(stdOutBuffer.ToString().Trim()); }
/// <summary> /// Executes the command as an observable event stream. /// </summary> public static IObservable <CommandEvent> Observe( this Command command, Encoding standardOutputEncoding, Encoding standardErrorEncoding, CancellationToken cancellationToken = default) => Observable.Create <CommandEvent>(observer => { var stdOutPipe = PipeTarget.Merge( command.StandardOutputPipe, PipeTarget.ToDelegate( s => observer.OnNext(new StandardOutputCommandEvent(s)), standardOutputEncoding) ); var stdErrPipe = PipeTarget.Merge( command.StandardErrorPipe, PipeTarget.ToDelegate( s => observer.OnNext(new StandardErrorCommandEvent(s)), standardErrorEncoding) ); var commandPiped = command .WithStandardOutputPipe(stdOutPipe) .WithStandardErrorPipe(stdErrPipe); var commandTask = commandPiped.ExecuteAsync(cancellationToken); observer.OnNext(new StartedCommandEvent(commandTask.ProcessId)); // Don't pass cancellation token to continuation because we need it to always trigger // regardless of how the task completed. _ = commandTask .Task .ContinueWith(t => { // Canceled tasks don't have exception if (t.IsCanceled) { observer.OnError(new OperationCanceledException("Command execution has been canceled.")); } else if (t.Exception == null) { observer.OnNext(new ExitedCommandEvent(t.Result.ExitCode)); observer.OnCompleted(); } else { observer.OnError(t.Exception); } }, TaskContinuationOptions.None); return(Disposable.Null); });
/// <summary> /// Executes the command asynchronously. /// The result of this execution contains the standard output and standard error streams buffered in-memory as strings. /// This method can be awaited. /// </summary> public static CommandTask <BufferedCommandResult> ExecuteBufferedAsync( this Command command, Encoding standardOutputEncoding, Encoding standardErrorEncoding, CancellationToken cancellationToken = default) { var stdOutBuffer = new StringBuilder(); var stdErrBuffer = new StringBuilder(); var stdOutPipe = PipeTarget.Merge( command.StandardOutputPipe, PipeTarget.ToStringBuilder(stdOutBuffer, standardOutputEncoding) ); var stdErrPipe = PipeTarget.Merge( command.StandardErrorPipe, PipeTarget.ToStringBuilder(stdErrBuffer, standardErrorEncoding) ); var commandPiped = command .WithStandardOutputPipe(stdOutPipe) .WithStandardErrorPipe(stdErrPipe) .WithValidation(CommandResultValidation.None); // disable validation because we have our own return(commandPiped .ExecuteAsync(cancellationToken) .Select(r => { // Transform the result var result = new BufferedCommandResult( r.ExitCode, r.StartTime, r.ExitTime, stdOutBuffer.ToString(), stdErrBuffer.ToString() ); // We perform validation separately here because we want to include stderr in the exception as well if (result.ExitCode != 0 && command.Validation.IsZeroExitCodeValidationEnabled()) { throw CommandExecutionException.ExitCodeValidation( command.TargetFilePath, command.Arguments, result.ExitCode, result.StandardError.Trim() ); } return result; })); }