Async Progress Bar and compiled Forms Script

Ask questions about creating Graphical User Interfaces (GUI) in PowerShell and using WinForms controls.
Forum rules
Do not post any licensing information in this forum.

Any code longer than three lines should be added as code using the 'Select Code' dropdown menu or attached as a file.
Locked
User avatar
CermakPOI
Posts: 38
Last visit: Sun Dec 27, 2020 8:53 am

Async Progress Bar and compiled Forms Script

Post by CermakPOI »

Hi,
I tried to create an async Progress bar:

Code: Select all

function Show-WaitDialog {
<#
	.SYNOPSIS
		Shows a wait Dialog
	
	.DESCRIPTION
		Shows a wait dialog as foreground window.
		Must be closed manually!
	
	.PARAMETER Title
		Dialog Title
	
	.PARAMETER Message
		Dialog Message
	
	.PARAMETER CloseMe
		Closes the Dialog
	
	.EXAMPLE
		PS C:\> Show-Wait -Title "Mytitle" -Message "Please wait for the fucntion to finish..."
	
	.NOTES
		0.0.1	20.02.2017	CER	Description
#>
	
	[CmdletBinding()]
	param
	(
	[string]$Title = "Working...",
	[Parameter(ValueFromRemainingArguments = $true)][string]$Message = "Please Wait...",
	[switch]$CloseMe
	)
	
	$strSB = @'
param($Title,$Message)
# Benötigte Assemblies laden
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[Void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms.VisualStyles')

# Progress Form erstellen
$ProgressForm = New-Object System.Windows.Forms.Form
$ProgressForm.Text = $Title
$ProgressForm.Width = 350
$ProgressForm.Height = 200
$ProgressForm.MaximizeBox = $False
$ProgressForm.MinimizeBox = $False
$ProgressForm.ControlBox = $False
$ProgressForm.ShowIcon = $False
$ProgressForm.StartPosition = 1
$ProgressForm.Visible = $False
$ProgressForm.FormBorderStyle = 'FixedDialog'
#$ProgressForm.WindowState = "Normal"

# Textfeld erzeugen
$InText = New-Object System.Windows.Forms.Label
$InText.Text = $Message
$InText.Location = '18,26'
$InText.Size = New-Object System.Drawing.Size(330, 38)

# Progressbar erzeugen
$progressBar1 = New-Object System.Windows.Forms.ProgressBar
$ProgressBar1.Name = 'LoadingBar'
$ProgressBar1.Style = 'Marquee'
$ProgressBar1.Location = '17,101'
$ProgressBar1.Size = '300,18'
$ProgressBar1.MarqueeAnimationSpeed = 40

# Textfeld in die Form einfügen
$ProgressForm.Controls.Add($InText)

# Progressbar in die Form einfügen
$ProgressForm.Controls.Add($ProgressBar1)

# Speichern der Form in die Hashtable
$sharedData.Form = $ProgressForm

$ProgressForm.TopMost = $true
[System.Windows.Forms.Application]::EnableVisualStyles()
[System.Windows.Forms.Application]::Run($ProgressForm)
$ProgressForm.TopMost = $true
#[System.Windows.Forms.Application]::DoEvents()
	
'@
	$objSB = [Scriptblock]::Create($strSB)
	if (-not $closeMe.Ispresent) {
		# Hashtable für Datenaustausch zwischen den Threads
		$script:sharedData = [HashTable]::Synchronized(@{
		})
		$Script:sharedData.Form = $Null
		# Runspace für die Progress Form mit der Hashtable zum synchronisieren vorbereiten
		$newRunspace = [RunSpaceFactory]::CreateRunspace()
		$newRunspace.ApartmentState = 'STA'
		$newRunspace.ThreadOptions = 'ReuseThread'
		$null = $newRunspace.Open()
		$newRunspace.SessionStateProxy.setVariable('sharedData', $sharedData)
		# Thread für die Progress Form (eine eigene asynchrone Powershell)
		# mit dem vorbereiteten Runspace starten und die Function 'ShowProgressForm' als Script übergeben
		$script:PS = [PowerShell]::Create()
		$PS.Runspace = $newRunspace
		#$Scriptcall = $PS.AddScript($Function:ShowProgressForm)
		$Scriptcall = $PS.AddScript($objSB)
		$null = $Scriptcall.AddParameter("Title", $Title);
		$null = $Scriptcall.AddParameter("Message", $Message);
		# Thread (Runspace) für die Progress Form asynchron starten
		$Script:AsyncResult = $PS.BeginInvoke()
	} else {
		#Write-Host "closing..."
		# Progress Form befindet sich in der Synchronized Hashtable
		# über die Hashtable kann die Form im einem anderen Thread angesprochen werden 
		If ($sharedData.Form) {
			$sharedData.Form.close()
		}
		# Thread (Runspace) für die Progress Form asynchron beenden
		$PS.Endinvoke($AsyncResult)
		# Thread (Runspace) für die Progress Form zerstören
		$PS.dispose()
		try {
			$newRunspace.CloseAsync()
			$newRunspace.Close()
		} catch {}
		
	}
}
It's working as expected - if it runs interactively in a ps-session.
If it runs as a Powershell Studio compiled Exe GUI the GUI closes normally but the Exe stays active (get-process).

Does anybody know why and/or what i can do to close the exe itself?
Thanks
Peter

jvierra
Posts: 14704
Last visit: Sat Feb 27, 2021 5:46 am
Answers: 8
Has voted: 2 times
Been upvoted: 6 times

Re: Async Progress Bar and compiled Forms Script

Post by jvierra »

It appears that some of your code is missing. A function must be called to work.

User avatar
CermakPOI
Posts: 38
Last visit: Sun Dec 27, 2020 8:53 am

Re: Async Progress Bar and compiled Forms Script

Post by CermakPOI »

Of course i call the function first with the title and message parameters filled.
When the progress window should be closed i call it with -Closeme.

The thing is that the .exe keeps open where a ps1-script closes normally.

how do i completely close this kind of runspace?

My workaround i to

Code: Select all

stop-process -id $pid

jvierra
Posts: 14704
Last visit: Sat Feb 27, 2021 5:46 am
Answers: 8
Has voted: 2 times
Been upvoted: 6 times

Re: Async Progress Bar and compiled Forms Script

Post by jvierra »

The issue is that the variables in the function do not exist the second time you call the function.

Add "Throw $_" to the catch block and you will see what I mean.

Make your else statement look like this and you will be able to see and fix the problem:
  1. } else {
  2.  
  3.         try {
  4.  
  5.             $sharedData.Form.close()
  6.  
  7.             $PS.Endinvoke($AsyncResult)
  8.  
  9.             $PS.dispose()
  10.  
  11.             $newRunspace.CloseAsync()
  12.  
  13.             $newRunspace.Close()
  14.  
  15.         }
  16.  
  17.         catch {
  18.  
  19.             Throw $_
  20.  
  21.         }
  22.  
  23.        
  24.  
  25.     }

Locked