PowerShell Tip: Utilizing Runspaces for Responsive WPF GUI Applications

For those with limited C# or VB.Net programming skills, using PowerShell with XAML code is a great way to create a simple GUI application.  But because the PowerShell host is single-threaded, all your code must run sequentially.  When you have long running commands, this can cause the GUI to freeze and be unresponsive, and you can’t do anything else in the GUI until the running command has finished.

But thanks to runspaces, it is possible to do multi-threading and run several commands in parallel.  We can send individual commands to their own runspace, or to a runspace pool, and because they are executing outside of the host application’s runspace (eg console or ISE), the GUI will be much more responsive and not suffer from locks and freezing.

Take for example this simple WPF application I built, which uses PowerShell’s Test-Connection cmdlet to “ping” up to 5 servers and get the network latency, or response delay:

CAPTURE2

In the count box, I select the number of times I want to “ping” the machine.  I can ping machines individually, or ping them all.  Because the code is running in a single thread, I have to wait for one ping to finish before I can ping another machine.  In fact, once I click Ping, I cannot do anything else in the GUI until the first ping has finished.  If I select “5” in the count box, for example (it will test the connection to the machine about once a second), it will take at least 5 seconds before I can do anything else.  if I click Ping all, and all machines have a count of 5, the GUI will freeze and it takes at least 25 seconds for the results to appear, because PowerShell will ping each machine sequentially, and then update the GUI application with the results.

CAPTURE3

Test it for yourself with the following code:


Add-Type –assemblyName PresentationFramework

#Build the GUI
[xml]$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="PowerShell Runspace Demo" Height="283" Width="782" WindowStartupLocation = "CenterScreen">
    <Grid Margin="0,0,0,-1">
        <Button x:Name="Ping1" Content="Ping" HorizontalAlignment="Left" Margin="119,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <Button x:Name="Ping2" Content="Ping" HorizontalAlignment="Left" Margin="255,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <Button x:Name="Ping3" Content="Ping" HorizontalAlignment="Left" Margin="387,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <Button x:Name="Ping4" Content="Ping" HorizontalAlignment="Left" Margin="524,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <Button x:Name="Ping5" Content="Ping" HorizontalAlignment="Left" Margin="656,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <TextBox x:Name="ComputerName1" HorizontalAlignment="Left" Height="23" Margin="105,79,0,0" TextWrapping="Wrap" Text="SERVER-01" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="ComputerName2" HorizontalAlignment="Left" Height="23" Margin="243,79,0,0" TextWrapping="Wrap" Text="SERVER-02" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="ComputerName3" HorizontalAlignment="Left" Height="23" Margin="374,79,0,0" TextWrapping="Wrap" Text="SERVER-03" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="ComputerName4" HorizontalAlignment="Left" Height="23" Margin="509,79,0,0" TextWrapping="Wrap" Text="SERVER-04" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="ComputerName5" HorizontalAlignment="Left" Height="23" Margin="640,79,0,0" TextWrapping="Wrap" Text="SERVER-05" VerticalAlignment="Top" Width="120"/>
        <ComboBox x:Name="Count1" HorizontalAlignment="Left" Margin="137,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <ComboBox x:Name="Count2" HorizontalAlignment="Left" Margin="274,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <ComboBox x:Name="Count3" HorizontalAlignment="Left" Margin="403,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <ComboBox x:Name="Count4" HorizontalAlignment="Left" Margin="540,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <ComboBox x:Name="Count5" HorizontalAlignment="Left" Margin="669,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <TextBox x:Name="Result1" HorizontalAlignment="Left" Height="56" Margin="128,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <TextBox x:Name="Result2" HorizontalAlignment="Left" Height="56" Margin="264,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <TextBox x:Name="Result3" HorizontalAlignment="Left" Height="56" Margin="397,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <TextBox x:Name="Result4" HorizontalAlignment="Left" Height="56" Margin="535,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <TextBox x:Name="Result5" HorizontalAlignment="Left" Height="56" Margin="669,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <Label Content="ComputerName:" HorizontalAlignment="Left" Margin="3,75,0,0" VerticalAlignment="Top" Height="26" Width="97"/>
        <Label Content="Count:" HorizontalAlignment="Left" Margin="3,107,0,0" VerticalAlignment="Top" Height="26" Width="94"/>
        <Label Content="Avg Latency (ms):" HorizontalAlignment="Left" Margin="0,182,0,0" VerticalAlignment="Top" Height="26" Width="111"/>
        <Label Content="Runspace Demo: Test-Connection" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="338" FontSize="20" FontWeight="Bold"/>
        <Button x:Name="Pingall" Content="Ping all" HorizontalAlignment="Left" Margin="656,14,0,0" VerticalAlignment="Top" Width="89" Height="37" FontWeight="Bold"/>
    </Grid>
</Window>
"@

$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

function NullCount {
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null
[Microsoft.VisualBasic.Interaction]::MsgBox("Please select a ping count first",'OKOnly,Information',"Ping")
}

function Ping {
param($result,$count,$ComputerName)
$result.Clear()
if ($count.SelectedItem.Content -eq $null)
    {NullCount; break}
$Con = Test-Connection -ComputerName $($ComputerName.Text) -Count $count.SelectedItem.Content
$avg = [math]::Round(($con.ResponseTime | measure -Average).Average)
$result.AddText($avg)
}

# XAML objects
# ComputerNames
$ComputerName1 = $Window.FindName("ComputerName1")
$ComputerName2 = $Window.FindName("ComputerName2")
$ComputerName3 = $Window.FindName("ComputerName3")
$ComputerName4 = $Window.FindName("ComputerName4")
$ComputerName5 = $Window.FindName("ComputerName5")
# Count
$Count1 = $Window.FindName("Count1")
$Count2 = $Window.FindName("Count2")
$Count3 = $Window.FindName("Count3")
$Count4 = $Window.FindName("Count4")
$Count5 = $Window.FindName("Count5")
# Ping buttons
$Ping1 = $Window.FindName("Ping1")
$Ping2 = $Window.FindName("Ping2")
$Ping3 = $Window.FindName("Ping3")
$Ping4 = $Window.FindName("Ping4")
$Ping5 = $Window.FindName("Ping5")
$Pingall = $Window.FindName("Pingall")
# Result boxes
$Result1 = $Window.FindName("Result1")
$Result2 = $Window.FindName("Result2")
$Result3 = $Window.FindName("Result3")
$Result4 = $Window.FindName("Result4")
$Result5 = $Window.FindName("Result5")

# Click Actions
$Ping1.Add_Click(
    {
        Ping -result $Result1 -count $Count1 -ComputerName $ComputerName1
    })

$Ping2.Add_Click(
    {
        Ping -result $Result2 -count $Count2 -ComputerName $ComputerName2
    })

$Ping3.Add_Click(
    {
        Ping -result $Result3 -count $Count3 -ComputerName $ComputerName3
    })

$Ping4.Add_Click(
    {
        Ping -result $Result4 -count $Count4 -ComputerName $ComputerName4
    })

$Ping5.Add_Click(
    {
        Ping -result $Result5 -count $Count5 -ComputerName $ComputerName5
    })

$Pingall.Add_Click(
    {
        if ($count1.SelectedItem.Content -eq $null -or $count2.SelectedItem.Content -eq $null -or $count3.SelectedItem.Content -eq $null -or $count4.SelectedItem.Content -eq $null -or $count5.SelectedItem.Content -eq $null)
            {NullCount; break}
        Ping -result $Result1 -count $Count1 -ComputerName $ComputerName1
        Ping -result $Result2 -count $Count2 -ComputerName $ComputerName2
        Ping -result $Result3 -count $Count3 -ComputerName $ComputerName3
        Ping -result $Result4 -count $Count4 -ComputerName $ComputerName4
        Ping -result $Result5 -count $Count5 -ComputerName $ComputerName5
    })

$Window.ShowDialog() | Out-Null

To get around this delay, I can create a new runspace for each time I call the function which runs the Test-Connection cmdlet.  This means the command itself is run in a separate instance and the host runspace is not used.  Usually when running a command in another runspace, it is still necessary for the host runspace to wait for the command to complete in order to receive the results.  However, in this case, because we are running a GUI we are actually able to pass the result straight to the GUI from within the separate runspace.  This means the host PowerShell instance does not need to wait for the results, and therefore the GUI application itself remains responsive and does not suffer any freezing or delay, which is very cool 🙂

Sounds complicated, so lets explain further.

First we create a synchronized hashtable, which is a “thread-safe” way to store and access objects and properties by any runspace.  Then we load the XAML code (created using Visual Studio) for the WPF app into a window, adding it to the hashtable so it can be accessed in another runspace:


$syncHash = [hashtable]::Synchronized(@{})
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )

Next, inside a function, we create and open a new single-threaded runspace and pass any variables we need to it, including the hashtable:


$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = "STA"
$Runspace.ThreadOptions = "ReuseThread"
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
$Runspace.SessionStateProxy.SetVariable("count",$count)
$Runspace.SessionStateProxy.SetVariable("ComputerName",$ComputerName)
$Runspace.SessionStateProxy.SetVariable("TargetBox",$TargetBox)

Next we define the code that will execute in the runspace:


$code = {
    $syncHash.Window.Dispatcher.invoke(
    [action]{ $syncHash.$TargetBox.Clear() })
    $Con = Test-Connection -ComputerName $ComputerName -Count $count
    $average = [math]::Round(($con.ResponseTime | measure -Average).Average)
    $syncHash.Window.Dispatcher.invoke(
    [action]{ $syncHash.$TargetBox.Text = $average }
    )
}

We use the Window.Dispatcher.Invoke() method to send a command to the GUI app window. First I send a command to clear the textbox that contains the latency value if it has been populated before.  Next, I run the test-connection cmdlet and then calculate the average response time.  I then pass this result to the GUI textbox.  Because the WPF app window is included in the synchronized hashtable, any runspace we create, including the host runspace, can use it.

Finally, we create an instance of PowerShell in the runspace, and invoke the code.


$PSinstance = [powershell]::Create().AddScript($Code)
$PSinstance.Runspace = $Runspace
$job = $PSinstance.BeginInvoke()

Using runspaces in this way means I can click on any of the ping commands in the GUI and I don’t have to wait for it to finish before I click on another button, or do something else, because the background work is essentially being “offloaded”.

Further, we can wrap the entire application in a separate runspace too, which means the host application that created it, ie the PowerShell console or ISE, can continue to be used for other commands while the GUI is running.  You can even interact with the GUI itself from the console window.

Here’s the full code for the app utilizing runspaces, so you can see the difference in responsiveness for yourself:


Add-Type –assemblyName PresentationFramework

$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = "STA"
$Runspace.ThreadOptions = "ReuseThread" 
$Runspace.Open()

$code = {

#Build the GUI
[xml]$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="PowerShell Runspace Demo" Height="283" Width="782" WindowStartupLocation = "CenterScreen">
    <Grid Margin="0,0,0,-1">
        <Button x:Name="Ping1" Content="Ping" HorizontalAlignment="Left" Margin="119,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <Button x:Name="Ping2" Content="Ping" HorizontalAlignment="Left" Margin="255,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <Button x:Name="Ping3" Content="Ping" HorizontalAlignment="Left" Margin="387,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <Button x:Name="Ping4" Content="Ping" HorizontalAlignment="Left" Margin="524,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <Button x:Name="Ping5" Content="Ping" HorizontalAlignment="Left" Margin="656,146,0,0" VerticalAlignment="Top" Width="93" Height="31"/>
        <TextBox x:Name="ComputerName1" HorizontalAlignment="Left" Height="23" Margin="105,79,0,0" TextWrapping="Wrap" Text="SERVER-01" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="ComputerName2" HorizontalAlignment="Left" Height="23" Margin="243,79,0,0" TextWrapping="Wrap" Text="SERVER-02" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="ComputerName3" HorizontalAlignment="Left" Height="23" Margin="374,79,0,0" TextWrapping="Wrap" Text="SERVER-03" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="ComputerName4" HorizontalAlignment="Left" Height="23" Margin="509,79,0,0" TextWrapping="Wrap" Text="SERVER-04" VerticalAlignment="Top" Width="120"/>
        <TextBox x:Name="ComputerName5" HorizontalAlignment="Left" Height="23" Margin="640,79,0,0" TextWrapping="Wrap" Text="SERVER-05" VerticalAlignment="Top" Width="120"/>
        <ComboBox x:Name="Count1" HorizontalAlignment="Left" Margin="137,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <ComboBox x:Name="Count2" HorizontalAlignment="Left" Margin="274,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <ComboBox x:Name="Count3" HorizontalAlignment="Left" Margin="403,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <ComboBox x:Name="Count4" HorizontalAlignment="Left" Margin="540,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <ComboBox x:Name="Count5" HorizontalAlignment="Left" Margin="669,107,0,0" VerticalAlignment="Top" Width="56" Height="34">
            <ComboBoxItem Content="1"/>
            <ComboBoxItem Content="2"/>
            <ComboBoxItem Content="3"/>
            <ComboBoxItem Content="4"/>
            <ComboBoxItem Content="5"/>
            <ComboBoxItem Content="6"/>
            <ComboBoxItem Content="7"/>
            <ComboBoxItem Content="8"/>
        </ComboBox>
        <TextBox x:Name="Result1" HorizontalAlignment="Left" Height="56" Margin="128,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <TextBox x:Name="Result2" HorizontalAlignment="Left" Height="56" Margin="264,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <TextBox x:Name="Result3" HorizontalAlignment="Left" Height="56" Margin="397,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <TextBox x:Name="Result4" HorizontalAlignment="Left" Height="56" Margin="535,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <TextBox x:Name="Result5" HorizontalAlignment="Left" Height="56" Margin="669,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="75" FontSize="18"/>
        <Label Content="ComputerName:" HorizontalAlignment="Left" Margin="3,75,0,0" VerticalAlignment="Top" Height="26" Width="97"/>
        <Label Content="Count:" HorizontalAlignment="Left" Margin="3,107,0,0" VerticalAlignment="Top" Height="26" Width="94"/>
        <Label Content="Avg Latency (ms):" HorizontalAlignment="Left" Margin="0,182,0,0" VerticalAlignment="Top" Height="26" Width="111"/>
        <Label Content="Runspace Demo: Test-Connection" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="338" FontSize="20" FontWeight="Bold"/>
        <Button x:Name="Pingall" Content="Ping all" HorizontalAlignment="Left" Margin="656,14,0,0" VerticalAlignment="Top" Width="89" Height="37" FontWeight="Bold"/>
    </Grid>
</Window>
"@

$syncHash = [hashtable]::Synchronized(@{})
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )

function RunspacePing {
param($syncHash,$Count,$ComputerName,$TargetBox)
if ($Count -eq $null)
    {NullCount; break}

$syncHash.Host = $host
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = "STA"
$Runspace.ThreadOptions = "ReuseThread" 
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("syncHash",$syncHash) 
$Runspace.SessionStateProxy.SetVariable("count",$count)
$Runspace.SessionStateProxy.SetVariable("ComputerName",$ComputerName)
$Runspace.SessionStateProxy.SetVariable("TargetBox",$TargetBox)

$code = {
    $syncHash.Window.Dispatcher.invoke(
    [action]{ $syncHash.$TargetBox.Clear() })
    $Con = Test-Connection -ComputerName $ComputerName -Count $count
    $average = [math]::Round(($con.ResponseTime | measure -Average).Average)
    $syncHash.Window.Dispatcher.invoke(
    [action]{ $syncHash.$TargetBox.Text = $average }
    )
}
$PSinstance = [powershell]::Create().AddScript($Code)
$PSinstance.Runspace = $Runspace
$job = $PSinstance.BeginInvoke()
}

function NullCount {
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null
[Microsoft.VisualBasic.Interaction]::MsgBox("Please select a ping count first",'OKOnly,Information',"Ping")
}

# XAML objects
# ComputerNames
$syncHash.ComputerName1 = $syncHash.Window.FindName("ComputerName1")
$syncHash.ComputerName2 = $syncHash.Window.FindName("ComputerName2")
$syncHash.ComputerName3 = $syncHash.Window.FindName("ComputerName3")
$syncHash.ComputerName4 = $syncHash.Window.FindName("ComputerName4")
$syncHash.ComputerName5 = $syncHash.Window.FindName("ComputerName5")
# Count
$syncHash.Count1 = $syncHash.Window.FindName("Count1")
$syncHash.Count2 = $syncHash.Window.FindName("Count2")
$syncHash.Count3 = $syncHash.Window.FindName("Count3")
$syncHash.Count4 = $syncHash.Window.FindName("Count4")
$syncHash.Count5 = $syncHash.Window.FindName("Count5")
# Ping buttons
$syncHash.Ping1 = $syncHash.Window.FindName("Ping1")
$syncHash.Ping2 = $syncHash.Window.FindName("Ping2")
$syncHash.Ping3 = $syncHash.Window.FindName("Ping3")
$syncHash.Ping4 = $syncHash.Window.FindName("Ping4")
$syncHash.Ping5 = $syncHash.Window.FindName("Ping5")
$syncHash.Pingall = $syncHash.Window.FindName("Pingall")
# Result boxes
$syncHash.Result1 = $syncHash.Window.FindName("Result1")
$syncHash.Result2 = $syncHash.Window.FindName("Result2")
$syncHash.Result3 = $syncHash.Window.FindName("Result3")
$syncHash.Result4 = $syncHash.Window.FindName("Result4")
$syncHash.Result5 = $syncHash.Window.FindName("Result5")

# Click Actions
$syncHash.Ping1.Add_Click(
    {
        RunspacePing -syncHash $syncHash -count $syncHash.Count1.SelectedItem.Content -ComputerName $syncHash.ComputerName1.Text -TargetBox "Result1"
    })

$syncHash.Ping2.Add_Click(
    {
        RunspacePing -syncHash $syncHash -count $syncHash.Count2.SelectedItem.Content -ComputerName $syncHash.ComputerName2.Text -TargetBox "Result2"
    })

$syncHash.Ping3.Add_Click(
    {
        RunspacePing -syncHash $syncHash -count $syncHash.Count3.SelectedItem.Content -ComputerName $syncHash.ComputerName3.Text -TargetBox "Result3"
    })

$syncHash.Ping4.Add_Click(
    {
        RunspacePing -syncHash $syncHash -count $syncHash.Count4.SelectedItem.Content -ComputerName $syncHash.ComputerName4.Text -TargetBox "Result4"
    })

$syncHash.Ping5.Add_Click(
    {
        RunspacePing -syncHash $syncHash -count $syncHash.Count5.SelectedItem.Content -ComputerName $syncHash.ComputerName5.Text -TargetBox "Result5"
    })

$syncHash.Pingall.Add_Click(
    {
        if ($syncHash.count1.SelectedItem.Content -eq $null -or $syncHash.count2.SelectedItem.Content -eq $null -or $syncHash.count3.SelectedItem.Content -eq $null -or $syncHash.count4.SelectedItem.Content -eq $null -or $syncHash.count5.SelectedItem.Content -eq $null)
            {NullCount; break}
        RunspacePing -syncHash $syncHash -count $syncHash.Count1.SelectedItem.Content -ComputerName $syncHash.ComputerName1.Text -TargetBox "Result1"
        RunspacePing -syncHash $syncHash -count $syncHash.Count2.SelectedItem.Content -ComputerName $syncHash.ComputerName2.Text -TargetBox "Result2"
        RunspacePing -syncHash $syncHash -count $syncHash.Count3.SelectedItem.Content -ComputerName $syncHash.ComputerName3.Text -TargetBox "Result3"
        RunspacePing -syncHash $syncHash -count $syncHash.Count4.SelectedItem.Content -ComputerName $syncHash.ComputerName4.Text -TargetBox "Result4"
        RunspacePing -syncHash $syncHash -count $syncHash.Count5.SelectedItem.Content -ComputerName $syncHash.ComputerName5.Text -TargetBox "Result5"
    })

$syncHash.Window.ShowDialog()
$Runspace.Close()
$Runspace.Dispose()

}

$PSinstance1 = [powershell]::Create().AddScript($Code)
$PSinstance1.Runspace = $Runspace
$job = $PSinstance1.BeginInvoke()

I haven’t covered the details of creating a WPF application in PowerShell in this post, but there are some good blogs online that cover the essentials:

http://learn-powershell.net/category/wpf/
http://blogs.technet.com/b/heyscriptingguy/archive/2014/08/01/i-39-ve-got-a-powershell-secret-adding-a-gui-to-scripts.aspx
Part I – Creating PowerShell GUIs in Minutes using Visual Studio – A New Hope
PowerShell and WPF: Writing Data to a UI From a Different Runspace
Using Background Runspaces Instead of PSJobs For Better Performance

Advertisements

5 thoughts on “PowerShell Tip: Utilizing Runspaces for Responsive WPF GUI Applications

  1. Hello,
    Nice script, but it looks like these four lines can be safely removed:

    $syncHash1 = [hashtable]::Synchronized(@{})
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    $syncHash1.Window=[Windows.Markup.XamlReader]::Load( $reader )

    $syncHash1.Host = $host

    I somehow didnt get, where is actually that $SyncHash1 var used,
    moreover if you try to run this: $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    before defining $xaml, it will throw error.

    Thank you for explanation in advance.

    🙂

    1. Hi Martin, you are absolutely right, those lines are not needed, and I’ve updated the script. I think I must have left them in there from some previous testing and forgot about them!

      Thanks for pointing it out 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s