CrazyMetal
CrazyMetal

Reputation: 193

WPF runspace crashes using powershell version 2

I'm writing a powershell script, which installes various drivers.

My script is working fine, so I want to add a gui using WPF. First I created a gui using WPF, nothing spectacular, just a window with a label.

I would like to update this label from my install script. So I create two runspaces, one that creates and shows the WPF gui and the other one executes my install script. That works fine as long as I'm using powershell version 3 or higher. With powershell 2, which I had to use with a new Windows 7 installation, the wpf runspace crashes down.

I hope there is a way to get this working with powershell version 2.

Here is a sample script, demonstating what I'm doing.

#########################################################################################
#
#   W P F - R U N S P A C E
#
#########################################################################################
$syncHashWpfLuaNotification = [hashtable]::Synchronized(@{})
$runspaceWpfLuaNotification =[runspacefactory]::CreateRunspace()
$runspaceWpfLuaNotification.ApartmentState = "STA"
$runspaceWpfLuaNotification.ThreadOptions = "ReuseThread"         
$runspaceWpfLuaNotification.Open()
$runspaceWpfLuaNotification.SessionStateProxy.SetVariable("syncHashWpfLuaNotification",$syncHashWpfLuaNotification)          
$psCmd = [PowerShell]::Create().AddScript({   
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
    [xml]$xaml = @"
<Window 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TreiberInstaller" Height="431" Width="626" Background="Black"
        WindowStyle="None" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="2*" />
            <RowDefinition Height="1*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>

        <Label Name="lblProgress" Content="Progress"  HorizontalAlignment="Center" Grid.Column="1" Grid.Row="2" VerticalAlignment="Top" Foreground="White" FontFamily="Calibri" FontSize="18" HorizontalContentAlignment="Center"/>

    </Grid>
</Window>
"@
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    $syncHashWpfLuaNotification.Window = [Windows.Markup.XamlReader]::Load( $reader )
    $syncHashWpfLuaNotification.lblProgress = $syncHashWpfLuaNotification.window.FindName("lblProgress")
    $syncHashWpfLuaNotification.Window.ShowDialog() | Out-Null
    $syncHashWpfLuaNotification.Error = $Error
})
$psCmd.Runspace = $runspaceWpfLuaNotification
$data = $psCmd.BeginInvoke()
Sleep -Milliseconds 450 # Wait a moment that the gui is ready




#########################################################################################
#
#   W O R K E R - R U N S P A C E
#
#########################################################################################
$ScriptBlock = { 
    #----------------------------------------------------------------------
    #   SetLabelText: Sets the lable-text
    #----------------------------------------------------------------------
    function SetLabelText
    {
        param(
                [Parameter(Position=0, Mandatory = $true, ValueFromPipeline = $false)]
                [ValidateNotNullOrEmpty()]
                [string]$Text
            )

        if(-not $syncHashWpfLuaNotification) {return}

        try
        {
            $syncHashWpfLuaNotification.Window.Dispatcher.invoke(
                    [action]{$syncHashWpfLuaNotification.lblProgress.Content = $Text},
                    "Normal"
            )
        }
        catch {}
    }

    #----------------------------------------------------------------------
    #   CloseProgressWindow: Closes the window
    #----------------------------------------------------------------------
    function CloseProgressWindow()
    {
        if(-not $syncHashWpfLuaNotification) {return}

        try
        {
            $syncHashWpfLuaNotification.Window.Dispatcher.invoke(
                    [action]{$syncHashWpfLuaNotification.Window.Close()},
                    "Normal"
            )
        }
        catch{}
    }



    #Starting here
    SetLabelText -Text "Starting installation..."

    Sleep 2

    for($i=1;$i -le 19; $i++)
    {
        SetLabelText -Text ("Progress Step " + $i)
    }

    for($i=20;$i -le 24; $i++)
    {
        SetLabelText -Text ("Progress Step " + $i)
        Sleep 1
    }

    CloseProgressWindow
} #End of $ScriptBlock


$syncHash1 = [hashtable]::Synchronized(@{})
$workerRunspace =[runspacefactory]::CreateRunspace()
$workerRunspace.ApartmentState = "STA"
$workerRunspace.ThreadOptions = "ReuseThread"         
$workerRunspace.Open()
$workerRunspace.SessionStateProxy.SetVariable("syncHash1",$syncHash1)          
$workerRunspace.SessionStateProxy.SetVariable("syncHashWpfLuaNotification",$syncHashWpfLuaNotification)          
$psCmd1 = [PowerShell]::Create().AddScript($ScriptBlock)
$psCmd1.Runspace = $workerRunspace
$data = $psCmd1.BeginInvoke()






#########################################################################################
#
#   S C R I P T   E N D   
#
#########################################################################################

#Wait for end of both runspaces
while(($runspaceWpfLuaNotification.RunspaceAvailability -eq "Busy") -or ($workerRunspace.RunspaceAvailability -eq "Busy"))
{
    if($runspaceWpfLuaNotification.RunspaceAvailability -eq "Busy") { Write-Host "Window is open" }
    if($workerRunspace.RunspaceAvailability -eq "Busy") { Write-Host "Worker is running" }

    Sleep 1
}

Write-Host "Script ended"

Upvotes: 1

Views: 618

Answers (2)

pianoboy
pianoboy

Reputation: 46

Try downloading WPFRunspace, which should work with PS V2. It provides a background worker for WPF and Forms based scripts.

Upvotes: 0

user4003407
user4003407

Reputation: 22132

There are some problems with how your pass delegates to Dispatcher.Invoke call.

  • ScriptBlock literals are bounded to current session state. If you pass bounded ScriptBlock to different Runspace, then it can cause some undesired effects, like code not invoked in target Runspace or deadlocks. See my other answer about this. So, first thing you need to do is to create new not bounded ScriptBlock. You can do that with [ScriptBlock]::Create method.
  • Since ScriptBlock is no more bounded to current session state, you can not refer variables from it, like you do with $Text in your code. You should pass them as additional parameters to Dispatcher.Invoke.
  • In .NET Framework 3.5, which will be used with PowerShell v2, there is no Invoke(Action, DispatcherPriority) overload in Dispatcher class, so that, Invoke([Action]{...}, "Normal") will be resolved to this Invoke(Delegate, Object[]) method instead. You should use different overload: Invoke(DispatcherPriority, Delegate), which exists in .NET Framework 3.5.

Upvotes: 1

Related Questions