ProcessAsync — Run a shell command asynchronously — C#

/// <summary>
/// An Async wrapper for <see cref="Process"/>.
/// </summary>
public class ProcessAsync
{

    private string _fileName;
    private string _arguments;

    public ProcessAsync(string fileName, string arguments)
    {
        _fileName = fileName;
        _arguments = arguments;
    }

    public async Task<string> Run(StringBuilder stdin = null)
    {

        // Initialise
        var cmd = new Process();
        cmd.StartInfo.FileName = _fileName;
        cmd.StartInfo.RedirectStandardInput = true;
        cmd.StartInfo.RedirectStandardOutput = true;
        cmd.StartInfo.CreateNoWindow = true;
        cmd.StartInfo.UseShellExecute = false;
        cmd.StartInfo.Arguments = _arguments;

        // Create a task that waits for the Process to finish
        var cmdExited = new CmdExitedTaskWrapper();
        cmd.EnableRaisingEvents = true;
        cmd.Exited += cmdExited.EventHandler;

        // Start process
        cmd.Start();

        // Pass any stdin if necessary
        if (stdin != null) {
            await cmd.StandardInput.WriteAsync(stdin);
            await cmd.StandardInput.FlushAsync();
            cmd.StandardInput.Close();
        }

        // Wait for process to end and return stdout
        await cmdExited.Task;
        return cmd.StandardOutput.ReadToEnd();

    }

    /// <remarks>
    /// We can't wait on a Process directly, so create a wrapper for a
    /// task that waits for the <see cref="Process.Exited"/> Event to be
    /// raised.
    /// </remarks>
    private class CmdExitedTaskWrapper
    {

        private TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();

        public void EventHandler(object sender, EventArgs e)
        {
            _tcs.SetResult(true);
        }

        public Task Task => _tcs.Task;

    }

}

Usage

var p = new ProcessAsync("pandoc", $"\"{filePath}\" -s -o \"{outFile}\"");
Console.WriteLine(await p.Run());