Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/build-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ on:
branches: [ '*' ]

env:
BUILD_VERSION: 0.1.${{github.run_number}}
BUILD_VERSION: 0.2.${{github.run_number}}
SLN_PATH: src/ShellRunner.sln
TEST_PATH: src/TestApp/bin/Release/net6.0/TestApp.dll
TEST_PATH: src/TestApp/bin/Release/net8.0/TestApp.dll

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x

- name: Restore dependencies
run: dotnet restore $SLN_PATH
Expand All @@ -30,7 +30,7 @@ jobs:
-p:Version=$BUILD_VERSION

- name: Upload ShellRunner Build Artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4.6.1
with:
name: BuildArtifacts
path: ./**/bin/Release/**/*
Expand All @@ -42,10 +42,10 @@ jobs:
steps:

- name: Download artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4.1.9
with:
name: BuildArtifacts

- name: Test
run: dotnet $TEST_PATH
run: dotnet $TEST_PATH bash

20 changes: 10 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ on:
branches: [ "stable" ]

env:
BUILD_VERSION: 0.1.${{github.run_number}}
BUILD_VERSION: 0.2.${{github.run_number}}
SLN_PATH: src/ShellRunner.sln
TEST_PATH: src/TestApp/bin/Release/net6.0/TestApp.dll
TEST_PATH: src/TestApp/bin/Release/net8.0/TestApp.dll
PROJECT_PATH: src/ShellRunner/ShellRunner.csproj
PRE_RELEASE_ARTIFACTS: ./**/ShellRunner.$BUILD_VERSION-alpha.nupkg
RELEASE_ARTIFACTS: ./**/ShellRunner.$BUILD_VERSION.nupkg
Expand All @@ -17,11 +17,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v2
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
dotnet-version: 8.0.x

- name: Restore dependencies
run: dotnet restore $SLN_PATH
Expand All @@ -33,7 +33,7 @@ jobs:
-p:Version=$BUILD_VERSION

- name: Test
run: dotnet $TEST_PATH
run: dotnet $TEST_PATH bash

- name: Pack-Alpha
run: dotnet pack $PROJECT_PATH
Expand All @@ -50,13 +50,13 @@ jobs:
-p:PackageVersion=$BUILD_VERSION

- name: Upload ShellRunner PreRelease Artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4.6.1
with:
name: PreReleaseArtifacts
path: $PRE_RELEASE_ARTIFACTS

- name: Upload ShellRunner Release Artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4.6.1
with:
name: ReleaseArtifacts
path: $RELEASE_ARTIFACTS
Expand All @@ -70,7 +70,7 @@ jobs:
steps:

- name: Download artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4.1.9
with:
name: PreReleaseArtifacts

Expand All @@ -87,7 +87,7 @@ jobs:
steps:

- name: Download artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4.1.9
with:
name: ReleaseArtifacts

Expand Down
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
# ShellRunner

Run command line tools inside CSharp.
Run command line tools using CSharp.

Supports Windows and Linux (Powershell, bash, and cmd).

Example running some dotnet CLI commands:

```csharp
using ShellRunner;

CommandRunner
CommandBuilder cb = await CommandRunner
.UsePowershell()
.StartProcess()
// print the dotnet info command
.AddCommand("dotnet --info")
// build a csharp project
.AddCommand("cd C:\\source\\repos\\MyProject")
.AddCommand("dotnet build MyProject.sln -c Release")
// Print the output
.AddCommand("cd bin\\Release")
.AddCommand("Get-ChildItem -r")
.Run();
.AddCommand(@"cd C:\source\repos\MyProject")
.AddCommand("dotnet build MyProject.sln -c Release", key: "build-output")
// Print the file list
.AddCommand(@"cd bin\Release\net8.0")
.AddCommand("Get-ChildItem -r", key: "files")
.RunAsync();
```

Shell Runner is really useful to create a wrapper for several other
commands and to work with data before or after you run the commands.
commands and to work with data before or after you run the commands.

After running the commands you can optionally access the output of each command
separately so you know the result of each command specifically.

```csharp
Console.WriteLine("Outputs:");

// the outputs are organized by command
foreach (var cmd in cb.Commands)
foreach(var output in cmd.Output)
Console.WriteLine(" " + output.Data);

// OR use keys declared above
var buildCmd = cb.GetCommand("build-output").Output;

Console.ForegroundColor = ConsoleColor.Blue;

foreach(var output in buildCmd)
Console.WriteLine(" " + output.Data);
```
93 changes: 52 additions & 41 deletions src/ShellRunner/CommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,78 +1,89 @@
using System;
using System.Diagnostics;
using System.Diagnostics;

namespace ShellRunner;

public class CommandBuilder
{
CommandBuilderOptions _options;
CommandBuilderOptions options;
List<ProcessCommand> commands;
Dictionary<string, ProcessCommand> commandMap;

public ProcessStartInfo StartInfo { get; set; }
public Process Process { get; set; }
public IReadOnlyList<IProcessedCommand> Commands => commands.AsReadOnly();

internal CommandBuilder(CommandBuilderOptions options, Process process)
{
Process = process;

Commands = new List<ProcessCommand>();

_options = options;
commands = new List<ProcessCommand>();
this.commandMap = new Dictionary<string, ProcessCommand>();

this.options = options;

StartInfo = new ProcessStartInfo
{
FileName = options.File,
Arguments = options.Args,
FileName = this.options.File,
Arguments = this.options.Args,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
RedirectStandardOutput = options.RedirectStandardOutput,
RedirectStandardError = options.RedirectStandardError,
RedirectStandardInput = options.RedirectStandardInput
RedirectStandardOutput = this.options.RedirectStandardOutput,
RedirectStandardError = this.options.RedirectStandardError,
RedirectStandardInput = this.options.RedirectStandardInput
};
}

public List<ProcessCommand> Commands { get; private set; }

public CommandBuilder AddWorkingDirectory(string workingDirectory)
/// <summary>
/// Add a command to the command list.
/// </summary>
/// <param name="command"></param>
/// <exception cref="ArgumentException">When a duplicate key is added.</exception>
public void AddCommand(ProcessCommand command)
{
StartInfo.WorkingDirectory = workingDirectory;
return this;
this.commands.Add(command);

if(this.commandMap.ContainsKey(command.Key))
throw new ArgumentException($"Command with key \"{command.Key}\" already exists.");

this.commandMap.Add(command.Key, command);
}

private void Proc_ErrorDataReceived(object sender, DataReceivedEventArgs e)
/// <summary>
/// Get a specific command by key.
/// </summary>
/// <param name="key">The key for the command ran earlier.</param>
/// <returns></returns>
/// <exception cref="KeyNotFoundException">When a key isn't present in the dictionary.</exception>
public IProcessedCommand GetCommand(string key)
{
try
{
if (e.Data is null)
return;

var fg = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"Error: {e.Data}");
Console.ForegroundColor = fg;
return this.commandMap[key];
}
catch(Exception ex)
catch (KeyNotFoundException)
{
var fg = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error while processing error data.");
Console.WriteLine(ex);
Console.ForegroundColor = fg;
throw new KeyNotFoundException($"Command with key \"{key}\" not found.");
}
}

private void Proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
public CommandBuilder AddWorkingDirectory(string workingDirectory)
{
try
{
Console.WriteLine(e.Data);
}
catch (Exception ex)
StartInfo.WorkingDirectory = workingDirectory;
return this;
}

public async Task<CommandBuilder> RunAsync(CancellationToken cancellationToken = default)
{
foreach(var command in commands)
{
var fg = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error while processing output data.");
Console.WriteLine(ex);
Console.ForegroundColor = fg;
if (cancellationToken.IsCancellationRequested)
break;

await command.RunCommandAsync(this.Process, cancellationToken);
}

return this;
}

}
1 change: 1 addition & 0 deletions src/ShellRunner/CommandBuilderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ShellRunner;

[DebuggerDisplay("{Shell} {File} {Args}")]
public record CommandBuilderOptions
{
public CommandBuilderOptions(ShellType shellType, string file, string args)
Expand Down
3 changes: 0 additions & 3 deletions src/ShellRunner/CommandOutput.cs

This file was deleted.

Loading