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
});
}