Search code examples
wpfpowershelluser-interfacehashtablerunspace

In Powershell, displaying a server restart check script in the textbox, freezes the WPF GUI


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 properlyn")},"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&#xD;&#xA;" 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 properlyn")},"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")
$status = "offline
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("Your computer never came back online in last $MAX_PINGTIME secondsn")},"Normal")
$Global:uiHash.Window.Dispatcher.Invoke([action]{$Global:uiHash.TextBox.AppendText("Check that nothing is preventing starup
n")},"Normal")
$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")            


 })

Solution

  • 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