In yesterdays blogpost I showed you how to add powershell to your VB.Net application the esay way.

Today I will show you a better way to do this.

You might have noticed in the previous version that the system was waiting for your statement to finish before it showed anything on the screen, in other words it was doing this in an synchronized way, this is how the powerconsole extension for Visual studio currently works. It would be better to do this in an asyncronized manner, like the nuget console.

It isn’t all that hard to accomplish this but it need some extra code an a diffent way of working. First of all you will need to use the Runspace and pipelines powershell makes available for you.

I refined my code a little So I can easily choose which one to run.

Here is how the app looks.

I added a button to run sync and async . And a button with some sample code you can run. In this version BTW write-host will not really work. But write will so use that.

To start with I made an interface so I could easily run one or the other.

vbnet Namespace PowerShell Public Interface IRun Sub Run(ByVal Command As String) Event OutputChanged(ByVal Result As String) Event RunFinished() End Interface End Namespace Then I implemented then sync way.

```vbnet Imports System.Text

Namespace PowerShell Public Class RunSync Implements IRun

    Public Sub Run(ByVal Command As String) Implements IRun.Run
        Dim _Result = New StringBuilder
        Dim Results = System.Management.Automation.PowerShell.Create.AddScript(Command).AddCommand("out-String").Invoke(Of String)()
        For Each Result In Results
            _Result.AppendLine(Result)
        Next
        RaiseEvent OutputChanged(_Result.ToString)
        RaiseEvent RunFinished()
    End Sub

    Public Event OutputChanged(ByVal Output As String) Implements IRun.OutputChanged

    Public Event RunFinished() Implements IRun.RunFinished
End Class

End Namespace``` Which is pretty much the same as what we already had in out previous example.

Then the async one.

```vbnet Imports System.Management.Automation.Runspaces Imports System.Management.Automation

Namespace PowerShell Public Class RunASync Implements IRun

    Private _RunSpace As Runspace
    Private _PipeLine As Pipeline
    Private WithEvents _OutPut As PipelineReader(Of PSObject)

    Public Event OutPutChanged(ByVal Output As String) Implements IRun.OutputChanged

    Public Sub New()
        _RunSpace = RunspaceFactory.CreateRunspace
        _RunSpace.Open()
    End Sub

    Public Sub Run(ByVal Command As String) Implements IRun.Run
        _PipeLine = _RunSpace.CreatePipeline(Command)
        _PipeLine.Input.Close()
        _OutPut = _PipeLine.Output
        _PipeLine.InvokeAsync()
    End Sub

    Private Sub _Output_DataReady(ByVal sender As Object, ByVal e As System.EventArgs) Handles _OutPut.DataReady
        Dim data = _PipeLine.Output.NonBlockingRead()
        If data.Count > 0 Then
            For Each d In data
                RaiseEvent OutPutChanged(d.ToString & Environment.NewLine)
            Next
        End If
        If _PipeLine.Output.EndOfPipeline Then
            RaiseEvent RunFinished()
        End If
    End Sub

    Public Event RunFinished() Implements IRun.RunFinished
End Class

End Namespace``` It is pretty simple, make a runspace via the factory. Make a pipeline and hand it your command. Then close the input (very important). And Invoke the command Async. You can then handle the dataready event from the output. Do a NonBlockingRead from the Output and raise an event. When your are at the end of the pipeline you can also send an event to say you are done. Easy as pie.

The codebehind of the form now looks like this.

```vbnet Imports WindowsApplication2.PowerShell

Public Class frmPowershell

Private WithEvents _Run As IRun
Private _RunSync As IRun
Private _RunASync As IRun

Public Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.
    _RunSync = New RunSync
    _RunASync = New RunASync
End Sub

Private Sub Clear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClear.Click
    txtResult.Text = ""
End Sub

Private Sub Run_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRun.Click
    Run(_RunSync)
End Sub

Private Sub Run(ByVal RunType As IRun)
    btnRun.Enabled = False
    btnRunAsync.Enabled = False
    _Run = RunType
    _Run.Run(Me.txtCommand.Text)
End Sub

Private Delegate Sub RunCompleted(ByVal Result As String)

Private Sub _Run_OutputChanged(ByVal Output As String) Handles _Run.OutputChanged
    If Me.InvokeRequired Then
        Me.Invoke(New RunCompleted(AddressOf _Run_OutputChanged), New Object() {Output})
    Else
        Me.txtResult.AppendText(Output)
    End If
End Sub

Private Sub btnForLoop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnForLoop.Click
    Me.txtCommand.AppendText("for($x = 0; $x -le 10; $x++)" & Environment.NewLine)
    Me.txtCommand.AppendText("{" & Environment.NewLine)
    Me.txtCommand.AppendText("Write ""Child Task $x"" ;" & Environment.NewLine)
    Me.txtCommand.AppendText("Sleep -Milliseconds 300;" & Environment.NewLine)
    Me.txtCommand.AppendText("}" & Environment.NewLine)
End Sub

Private Delegate Sub RunFinishedDelegate()

Private Sub _Run_RunFinished() Handles _Run.RunFinished
    If Me.InvokeRequired Then
        Me.Invoke(New RunFinishedDelegate(AddressOf _Run_RunFinished), Nothing)
    Else
        Me.btnRun.Enabled = True
        Me.btnRunAsync.Enabled = True
    End If
End Sub

Private Sub btnRunAsync_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRunAsync.Click
    Run(_RunASync)
End Sub

End Class``` You can now click the two buttons to notice the difference between sync and async. Also notice the insane amount of code you have to use to make updating winforms controls in an async matter (I so hate that).

I think the next step is to create a profile variable and thus also add an external script to our pipeline. Repeat after me, this is fun.