📄 OutGridTree/OutGridTree.cs
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Text;

namespace OutGridTree;

[Cmdlet(VerbsData.Out, "GridTree")]
[Alias("ogt")]
public sealed class OutGridTree : PSCmdlet
{
    [Parameter(ValueFromPipeline = true)]
    public PSObject InputObject { get; set; } = AutomationNull.Value;

    [Parameter(Mandatory = true)]
    public string WindowExe { get; set; } = "";

    [Parameter]
    public string? Title { get; set; }

    [Parameter]
    public string[]? Headers { get; set; }

#nullable disable
    private NamedPipeServerStream pipe;
    private Process windowProcess;
    private StreamWriter writer;

#nullable restore

    protected override void BeginProcessing()
    {
        var pipeName = Path.GetTempFileName();
        pipe = new(pipeName, PipeDirection.Out);

        windowProcess = Process.Start(
            new ProcessStartInfo()
            {
                FileName = WindowExe,
                Arguments = pipeName,
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
            }
        );
        pipe.WaitForConnection();

        writer = new(pipe);

        if (MyInvocation.BoundParameters.ContainsKey(nameof(Title)) && Title is not null)
        {
            writer.WriteLine($"TITLE: {Title}");
        }
        else
        {
            writer.WriteLine($"TITLE: {MyInvocation.Line}");
        }

        if (MyInvocation.BoundParameters.ContainsKey(nameof(Headers)) && Headers is not null)
        {
            writer.WriteLine($"HEADERS: {string.Join(",", Headers)}");
        }
    }

    protected override void ProcessRecord()
    {
        if (!pipe.IsConnected)
        {
            TerminateDueToWindowClosed();
            return;
        }
        try
        {
            writer.WriteLine(
                $"RECORD: {Convert.ToBase64String(Encoding.UTF8.GetBytes(PSSerializer.Serialize(InputObject)))}"
            );
        }
        catch (IOException)
        {
            TerminateDueToWindowClosed();
            return;
        }
    }

    private void TerminateDueToWindowClosed() =>
        ThrowTerminatingError(
            new(new InvalidOperationException("Window closed"), "WindowClosed", ErrorCategory.InvalidOperation, null)
        );

    protected override void EndProcessing()
    {
        // TODO: Wait for window to close
        if (pipe.IsConnected)
        {
            writer.Flush();
        }
    }

    protected override void StopProcessing()
    {
        if (pipe.IsConnected)
        {
            pipe?.Close();
            pipe?.Disconnect();
        }
        pipe?.Dispose();
        if (!windowProcess.HasExited)
        {
            windowProcess?.Close();
        }
        windowProcess?.Dispose();
    }
}