Reputation: 193
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
Reputation: 46
Try downloading WPFRunspace, which should work with PS V2. It provides a background worker for WPF and Forms based scripts.
Upvotes: 0
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.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
.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