C# WPF run PowerShell async with Reactive and AsyncRelayCommand

Viewed 62

I want to use a button click to execute a PowerShell script asynchronously and display the output directly in my UI. It is based on this StackOverflow question.

Since I couldn't quite figure out the implementation, I tried to solve it myself. It works, but I think there is a lot of room for improvement. Is my implementation a good approach? In particular, the three observables that I combine in the GetPowerShellOutputObservable method don't feel right to me. onCompleted is never called, although the observer.OnCompleted(); is passed through in powershellEnded-Observable.

MyViewModel.cs

public MyViewModel()
{        
    ExecuteCommand = new AsyncRelayCommand<string>(Execute, CanExecute);
    // ...
}

public IAsyncRelayCommand ExecuteCommand { get; }

private Task Execute(string scriptFile)
{
    _service.GetPowerShellOutputObservable(scriptFile).Subscribe(
        onNext:         output => print(output),
        onError:        exception => print($"Exception: {exception.Message}"),
        onCompleted:    () => print("Completed!")
    );
    return Task.CompletedTask;
}

private bool CanExecute(string param)
{
    return !string.IsNullOrEmpty(param) && File.Exists(param);
}


public void print(string message)
{
    Console.WriteLine(message);

    //App.Current.Dispatcher.Invoke((Action)delegate
    //{
        CompleteCommandOutput += DateTime.Now.ToString("HH:mm:ss:fff ") + message + "\r\n";
    //});

}

MyPowerShellWorker.cs

// ...
public IObservable<string> GetPowerShellOutputObservable(string scriptFile)
{
    var scriptContents = File.ReadAllText(scriptFile);
    var powerShell = PowerShell.Create();
    powerShell.AddScript(scriptContents);
    var powerShellOutputBuffer = new PSDataCollection<string>();

    // Observable<string>: Streams.Information
    var powershellStreamInformationDataAdded = Observable.FromEventPattern<DataAddedEventArgs>(
        handler => powerShell.Streams.Information.DataAdded += handler,
        handler => powerShell.Streams.Information.DataAdded -= handler)
        .Select(eventPattern =>
        {
            PSDataCollection<InformationRecord> collection = eventPattern.Sender as PSDataCollection<InformationRecord>;
            return collection[eventPattern.EventArgs.Index].MessageData.ToString();
        });

    // Observable<string>: OutputBuffer
    var powershellOutputBufferDataAdded = Observable.FromEventPattern<DataAddedEventArgs>(
            handler => powerShellOutputBuffer.DataAdded += handler,
            handler => powerShellOutputBuffer.DataAdded -= handler)
        .Select(eventPattern =>
        {
            PSDataCollection<string> collection = eventPattern.Sender as PSDataCollection<string>;
            return collection[eventPattern.EventArgs.Index];
        });

        
    // Observable<string>: PowerShell ended
    var powershellEnded = Observable.Create<string>(
        observer =>
        {
            var invokeAndEndInvokePowerShellTask = Task.Factory.FromAsync(
                                                        powerShell.BeginInvoke(
                                                            (PSDataCollection<PSObject>)null,
                                                            powerShellOutputBuffer),
                                                        powerShell.EndInvoke);

            invokeAndEndInvokePowerShellTask.ContinueWith(a => {
                powerShell.Dispose();
            });

            invokeAndEndInvokePowerShellTask.ContinueWith(a => {
                if (a.Exception == null)
                {
                    observer.OnCompleted();
                }
                else
                {
                    observer.OnError(a.Exception);
                }
            });

            return Disposable.Empty;
        });

    return Observable.Merge(new List<IObservable<string>>() {
        powershellStreamInformationDataAdded,
        powershellOutputBufferDataAdded,
        powershellEnded
    });
}
0 Answers
Related