I created a PowerShell script that uses a WPF GUI. It has the option to choose a server from a combobox (more servers will be added later). The chosen server can be rebooted by clicking on the reboot button. Then a script, that will check the reboot procedure, will run in Button.Add_Click
and the results will be shown in a textbox.
The problem is that the GUI freezes until Button.Add_Click
is finished and will then show the info in the textbox.
I tried to solve this by implementing a runspace. But now I run into new problems.
In the ComboBox.add_SelectionChanged
section I cannot get the selected combobox content. I want to store the content in a variable $computer
.
In the Button.Add_Click
section I can’t write to the textbox. When I use $Global:uiHash.Window.Dispatcher.Invoke([action]
$Global:uiHash.TextBox.AppendText("Check that reboot is initiated properly
n")},"Normal")` then the GUI freezes.
Here is the full code:
$Global:uiHash = [hashtable]::Synchronized(@{})
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("uiHash",$Global:uiHash)
$psCmd = [PowerShell]::Create().AddScript({
$Global:uiHash.Error = $Error
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
$xaml = @"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="CSS Server Reboot" Height="450" Width="800">
<Grid>
<ComboBox Name="Server_Combobox" HorizontalAlignment="Left" Margin="240,107,0,0" VerticalAlignment="Top" Width="120">
<ComboBoxItem Name="Server1">10.15.12.148</ComboBoxItem>
</ComboBox>
<Label Name="Title_Label" Content="CSS – Console Quentris, Server Reboot
" HorizontalAlignment="Left" Margin="240,41,0,0" VerticalAlignment="Top" Height="34" Width="284" FontSize="16"/>
<Button Name="Reboot_Button" Content="Reboot" HorizontalAlignment="Left" Margin="449,107,0,0" VerticalAlignment="Top" Width="75"/>
<TextBox Name="Reboot_Textbox" Grid.Column="1" HorizontalAlignment="Left" Height="173" Margin="81,180,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="294"/>
</Grid>
"@
$Global:uiHash.Window=[Windows.Markup.XamlReader]::Parse($xaml )
$Global:uiHash.TextBox = $Global:uiHash.window.FindName("Reboot_Textbox")
$Global:uiHash.Button = $Global:uiHash.window.FindName("Reboot_Button")
$Global:uiHash.ComboBox = $Global:uiHash.window.FindName("Server_Combobox")
$Global:uiHash.Window.ShowDialog() | out-null
})
$psCmd.Runspace = $newRunspace
$handle = $psCmd.BeginInvoke()
Start-Sleep -Milliseconds 100
$computer = ""
$Global:uiHash.ComboBox.add_SelectionChanged({
$Script:computer = $Global:uiHash.Combobox.SelectedItem.Content
})
$Global:uiHash.Button.Add_Click({
$Username = 'xxxx'
$Password = 'xxxx'
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$SecureString = $pass
$MySecureCreds = New-Object -TypeName
System.Management.Automation.PSCredential -ArgumentList
$Username,$SecureString
Restart-Computer -ComputerName $computer -Force -Credential $MySecureCreds
$timeout=5
$MAX_PINGTIME = $timeout * 60
$max_iterations = $MAX_PINGTIME/5
$Notification_timeout = 10 # in seconds
function ping-host {
param($pc)
$status = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$pc'"
if( $status.statuscode -eq 0) {
return 1
} else {
return 0
}
}
if(ping-host -pc $computer) {
$status = "online`n"
for ($i=0; $i -le $max_iterations; $i++) {
if (!(ping-host -pc $computer )) {
break
}
Start-Sleep -Seconds 5
if($i -eq $max_iterations) {
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("$computer never went down in last $timeout minutesn")},"Normal")
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("Check that reboot is initiated properly
n")},"Normal")
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("$computer is still ONLINE; Check that reboot is initiated properly`n")},"Normal")
exit
}
}
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("$computer is offline now; monitoring for online status`n")},"Normal")
} else {
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("$computer is offline; Monitoring for online statusn")},"Normal")
n"
$status = "offline
}
for ($i=0; $i -le $max_iterations; $i++) {
if ((ping-host -pc $computer )) {
break
}
Start-Sleep -Seconds 5
if($i -eq $max_iterations) {
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("Your computer never came back online in last $MAX_PINGTIME secondsn")},"Normal")
n")},"Normal")
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("Check that nothing is preventing starup
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("$Computer is NOT coming online; Something is preventing its startup`n")},"Normal")
exit
}
}
$Global:uiHash.Window.Dispatcher.Invoke([action]
{$Global:uiHash.TextBox.AppendText("Your computer is Online Now; Task done;
exiting")},"Normal")
})
The reason that the GUI freeze is that the job you are running is in the same instance. Since the GUI runs in the same instance it will have to wait until whatever code is running is done before it can go back to taking new input.
You could try and solve this running the code you want to execute when you click the button as a job, this way the code will run in a new process on the machine and not block the session the GUI is running in.
This can be done with the Start-Job cmdlet