Marquee Progress Bar freezes in Powershell - windows

I have been working with Powershell for a time now, it is not the ideal programming environment, but got stuck with my program.
My program is an GUI with a marquee progressbar and a search job.
What my program does: After you run the script with Powershell it will restart Powershell in STA mode if the mode is MTA. After that it will ask for a folder location. After you entered the folder location it will start the search job and will search the location for files. Every file will be stored into an array. That array will be printed out into the tempfile.txt that will be saved on your desktop. Meanwhile the job is searching for the files the GUI will display a form with a Marquee Progress bar.
What my program needs to do: After the job is finished with searching and storing the files it must close the form.
I have tried it to do with the $formSearchingFiles.Close() command but I have noticed Jobs can not close their 'parent' thread, so this job will not be able to close the form.
I have also tried to solve it with the Wait-Job cmdlet but then the Marquee Progress bar will freeze, or the form will not show up at all.
I have looked alot around internet for solutions but I can not find one that suits this problem. I was thinking about multi-processing, but I do not know if this is possible in Powershell 2.0 (I am limited to 2.0 or lower).
I also do not know if there is a way that the Search-Job can notify the main thread that it is finished with the task, so that the main thread can continue with the program, without freezing the progress bar.
I hope I have explained enough about the program and my problem.
# Get the path of the script
$scriptPath = ((Split-Path $script:MyInvocation.MyCommand.Path) + "\")
$scriptName = $MyInvocation.MyCommand.Name
$script = $scriptPath + $scriptName
# Check if powershell is running in STA(Single Threaded Apartment) or MTA(Multi Threaded Apartment) mode.
# If it is running in MTA mode then restart Powershell in STA mode.
if ([threading.thread]::CurrentThread.GetApartmentState() -eq "MTA")
{
Write-Host Restarting Powershell in STA mode
& $env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe -sta "& {&'$script'}"
}
else
{
$folderPath = $currentFolderLocation.Text
$tempFile = $currentStagingLocation.Text
$tempFile += "\fileArray.txt"
function OnApplicationLoad {
return $true #return true for success or false for failure
}
function OnApplicationExit {
$script:ExitCode = 0 #Set the exit code for the Packager
}
function Call-Searching_pff {
[void][reflection.assembly]::Load("mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
[void][reflection.assembly]::Load("System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
[void][reflection.assembly]::Load("System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
[void][reflection.assembly]::Load("System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
[void][reflection.assembly]::Load("System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
[void][reflection.assembly]::Load("System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
[void][reflection.assembly]::Load("System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
[void][reflection.assembly]::Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
[void][reflection.assembly]::Load("System.ServiceProcess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
[System.Windows.Forms.Application]::EnableVisualStyles()
$formSearchingFiles = New-Object 'System.Windows.Forms.Form'
$label = New-Object 'System.Windows.Forms.Label'
$progressbar = New-Object 'System.Windows.Forms.ProgressBar'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
$FormEvent_Load={
$folderPath = &read-host "Enter path"
$tempFile = (([Environment]::GetFolderPath("Desktop")) + "\tempfile.txt" )
$SearchJob = Start-Job -scriptblock {
param ($folderPath, $tempFile)
$fileArray = #()
# Get all files and folders under the specified path
$items = Get-ChildItem -Path $folderPath -Recurse
foreach ($item in $items)
{
# Check if the item is a file or a folder
if (!($item.PSIsContainer))
{
# Extract path of file with path of entered folder
$extractedPath = $item.FullName
$extractedPath = $extractedPath.Replace($folderPath, "")
$fileArray += $extractedPath
}
}
# Save array in temporary file
$fileArray | out-file $tempFile
$formSearchingFiles.Close() #Does not work inside job :(
} -ArgumentList #($folderPath, $tempFile)
}
$Form_StateCorrection_Load=
{
#Correct the initial state of the form to prevent the .Net maximized form issue
$formSearchingFiles.WindowState = $InitialFormWindowState
}
$Form_Cleanup_FormClosed=
{
#Remove all event handlers from the controls
try
{
$formSearchingFiles.remove_Load($FormEvent_Load)
$formSearchingFiles.remove_Load($Form_StateCorrection_Load)
$formSearchingFiles.remove_FormClosed($Form_Cleanup_FormClosed)
}
catch [Exception]{ }
}
# formSearchingFiles
$formSearchingFiles.Controls.Add($label)
$formSearchingFiles.Controls.Add($progressbar)
$formSearchingFiles.ClientSize = '394, 122'
$formSearchingFiles.FormBorderStyle = 'FixedDialog'
$formSearchingFiles.MaximizeBox = $False
$formSearchingFiles.Name = "formSearchingFiles"
$formSearchingFiles.StartPosition = 'CenterScreen'
$formSearchingFiles.Text = "Compatibility Checker"
$formSearchingFiles.add_Load($FormEvent_Load)
# label
$label.Location = '12, 27'
$label.Name = "label"
$label.Size = '368, 26'
$label.TabIndex = 1
$label.Text = "Searching for files, please wait.."
$label.TextAlign = 'MiddleCenter'
# progressbar
$progressbar.Location = '12, 68'
$progressbar.MarqueeAnimationSpeed = 40
$progressbar.Name = "progressbar"
$progressbar.Size = '370, 30'
$progressbar.Style = 'Marquee'
$progressbar.TabIndex = 0
#Save the initial state of the form
$InitialFormWindowState = $formSearchingFiles.WindowState
#Init the OnLoad event to correct the initial state of the form
$formSearchingFiles.add_Load($Form_StateCorrection_Load)
#Clean up the control events
$formSearchingFiles.add_FormClosed($Form_Cleanup_FormClosed)
#Show the Form
return $formSearchingFiles.ShowDialog()
} #End Function
#Call OnApplicationLoad to initialize
if((OnApplicationLoad) -eq $true)
{
#Call the form
Call-Searching_pff | Out-Null
#Perform cleanup
OnApplicationExit
}
}

I've found the solution for my own problem. Solution: synchronized hash table as "communication link" between threads.
After you created a hash table you can add variables and objects to it. All threads (that you give access to the hash) can read/write to those variables and objects.
Create sync. hash table:
$syncHash = [hashtable]::Synchronized(#{})
#Where $syncHash is the name of your hash table
Add Variables and Objects to the hash table:
$syncHash.ProgressBar = $progressBar
#Create new variable ProgressBar in hash table and assign $progressBar to it
Create new thread and allow the use of the hash table:
$processRunspace =[runspacefactory]::CreateRunspace()
$processRunspace.ApartmentState = "STA"
$processRunspace.ThreadOptions = "ReuseThread"
$processRunspace.Open()
$processRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
$psCmd = [PowerShell]::Create().AddScript({
#Your Thread Code Here
})
$psCmd.Runspace = $processRunspace
$data = $psCmd.BeginInvoke()
Change value of $progressBar from new thread:
$syncHash.ProgressBar.Value = 1
Thanks to: http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/

Related

How to trigger Powershell Script when a new File created inside a folder? [duplicate]

I am new to PowerShell and I am trying to use the System.IO.FileSystemWatcher to monitor the presence of a file in a specified folder. However, as soon as the file is detected I want to stop monitoring this folder immediately and stop the FileSystemWatcher. The plan is to incorporate the PowerShell script into a SQL Agent to enable users to restore their own databases. Basically I need to know the command to stop FileSystemWatcher from monitoring as soon as one file is found. Here is the script so far.
### SET FOLDER TO WATCH + FILES TO WATCH + SUBFOLDERS YES/NO
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\TriggerBatch"
$watcher.Filter = "*.*"
$watcher.IncludeSubdirectories = $true
$watcher.EnableRaisingEvents = $true
### DEFINE ACTIONS AFTER A EVENT IS DETECTED
$action = { $path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
Add-content "C:\log2.txt" -value $logline
}
### DECIDE WHICH EVENTS SHOULD BE WATCHED + SET CHECK FREQUENCY
$created = Register-ObjectEvent $watcher Created -Action $action
while ($true) {sleep 1}
## Unregister-Event Created ??
##Stop-ScheduledTask ??
Unregister-Event $created.Id
This will unregister the event. You will probably want to add this to the $action.
Do note that if there are events in the queue they will still be fired.
This might help too.
Scriptblocks that are run as an action on a subscribed event have access to the $Args, $Event, $EventArgs and $EventSubscriber automatic variables.
Just add the Unregister-Event command to the end of your scriptblock, like so:
### DEFINE ACTIONS AFTER A EVENT IS DETECTED
$action = { $path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$logline = "$(Get-Date), $changeType, $path"
Add-content "C:\log2.txt" -value $logline
Unregister-Event -SubscriptionId $EventSubscriber.SubscriptionId
}
This is the pattern for an event that only performs an action once and then cleans itself up.
It's difficult to effectively explore these automatic variables since they are within the scope of a Job, but you can futz with them by assigning them to global variables while you are sketching out your code. You may also get some joy with Wait-Debugger and Debug-Runspace. In the case of the $EventSubscriber variable, it returns the exact object you get if you run Get-EventSubscriber (having created a single subscription already). That's how I found the SubscriptionId property.
If you want to stop/unregister all registered events you can call
Get-EventSubscriber|Unregister-Event

Prevent overwrite pop-up when writing into excel using a powershell

I have a bunch of csv files from which I am writing data into a particular worksheet of an existing excel file. I have the below code and it works while looping through the CSV files and writing data into the existing worksheet
$CSVs ="rpt.test1",
"rpt.test2"
foreach ($csv in $CSVs)
{
$csv_name = $csv
echo "n - - - $sav_name - - -n"
foreach ($source in $Sources)
{
$src = $source
$inputCSV = "C:\Users\xxxx\Desktop\$src.$csv_name.csv"
$Path = "C:\Users\xxxx\Desktop\$csv_name.xlsx"
### Create a new Excel Workbook with one empty sheet
#$excel = New-Object -ComObject excel.application
#$workbook = $excel.Workbooks.Add(1)
#$worksheet = $workbook.worksheets.Item(1)
# Open the Excel document and pull in the 'Play' worksheet
$excel = New-Object -Com Excel.Application
$Workbook = $Excel.Workbooks.Open($Path)
$page = 'data'
$worksheet = $Workbook.worksheets | where-object {$_.Name -eq $page}
# Delete the current contents of the page
$worksheet.Cells.Clear() | Out-Null
### Build the QueryTables.Add command
### QueryTables does the same as when clicking "Data ยป From Text" in Excel
$TxtConnector = ("TEXT;" + $inputCSV)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)
### Set the delimiter (, or ;) according to your regional settings
$query.TextFileOtherDelimiter = $Excel.Application.International(5)
### Set the format to delimited and text for every column
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,2 * $worksheet.Cells.Columns.Count
$query.AdjustColumnWidth = 1
### Execute & delete the import query
$query.Refresh()
$query.Delete()
$Workbook.SaveAs($Path,51)
$excel.Quit()
}
Since it is an existing excel workbook, it throws a pop-up every time a file is being over-written. Have more than 15 CSV's and clicking Yes everytime is annoying
I have tried
$excel.DisplayAlerts = FALSE
and I have tried
$excel.CheckCompatibility = $False
and pretty much anything available on the internet. I am still learning powershell and at my wits end trying to stop this. Any help would be very much appreciated
Use display alerts statement before the SaveAs call:
$excel.DisplayAlerts = $false;
$excel.ActiveWorkbook.SaveAs($xlsFile);
$excel.DisplayAlerts = $false worked for me.

How to compare age of local file with file on FTP server and download if remote copy is newer in PowerShell

I'm in the process of writing a PowerShell script to help in the process of setting up new PC's for my work. This will hopefully be used by more than just me so I'm trying to think of everything.
I have offline installers (java, flash, reader, etc) saved on our FTP server that the script downloads if a local copy hasn't already been saved in the Apps directory that gets created. Periodically the files on the FTP server will get updated as new versions of the programs are released. I want the script to have an option of checking for newer versions of the installers in case someone likes to carry around the local copies and forgets to check the server every now and then. It also will need to work in Windows 7 without any need to import additional modules unless there's an easy way to do that on multiple PC's at a time. I know about the import command, but the experiences I've had needed me to copy the module files into multiple places on the PC before it'd work.
Right now I haven't had much luck finding any solutions. I've found code that checks for modified dates on local files, or files on a local server, but nothing that deals with FTP other than uploading\downloading files.
Here's the last thing I tried. I tried a combination of what I found for local files with FTP. Didn't work too well.
I'm new to PowerShell, but I've been pretty good at piecing this whole thing together so far. However, this idea is becoming troublesome.
Thank you for the help.
$ftpsite = "ftp://ftpsite.com/folder/"
$firefox = (Get-Item $dir\Apps\install_firefox.exe).LastWriteTime.toString("MM/dd/yyyy")
if ($firefoxftp = (Get-ChildItem $ftpsite/install_firefox.exe | Where{$_.LastWriteTime -gt $firefox})) {
$File = "$dir\Apps\install_firefox.exe"
$ftp = "ftp://ftpsite.com/folder/install_firefox.exe"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
$webclient.DownloadFile($uri, $File)
}
UPDATE:
Here's what I have after Martin's help. It kind of works. It downloads the file from FTP, but it's not comparing the remote and local correctly. The remote file returns 20150709140505 and the local file returns 07/09/2015 2:05:05 PM. How do I format one to look like the other before the comparison, and is "-gt" the correct comparison to use?
Thanks!
function update {
$ftprequest = [System.Net.FtpWebRequest]::Create("ftp://ftpsite.com/Script_Apps/install_firefox.exe")
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::GetDateTimestamp
$response = $ftprequest.GetResponse().StatusDescription
$tokens = $response.Split(" ")
$code = $tokens[0]
$localfile = (Get-Item "$dir\Apps\install_firefox.exe").LastWriteTimeUtc
if ($tokens -gt $localfile) {
write-host "Updating Firefox Installer..."
$File = "$dir\Apps\install_firefox.exe"
$ftp = "ftp://ftpsite.com/Script_Apps/install_firefox.exe"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
$webclient.DownloadFile($uri, $File)
"Updated Firefox" >> $global:logfile
mainmenu
}
else {
Write-Host "Local Copy is Newer."
sleep 3
mainmenu
}
}
UPDATE 2:
Seems to be working! Here's the code. Thanks for the help!
function update {
$ftprequest = [System.Net.FtpWebRequest]::Create("ftp://ftpserver.com/Script_Apps/install_firefox.exe")
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::GetDateTimestamp
$response = $ftprequest.GetResponse().StatusDescription
$tokens = $response.Split(" ")
$code = $tokens[0]
$localtime = (Get-Item "$dir\Apps\install_firefox.exe").LastWriteTimeUtc
if ($code -eq 213) {
$tokens = $tokens[1]
$localtime = "{0:yyyymmddHHmmss}" -f [datetime]$localtime
}
if ($tokens -gt $localtime) {
write-host "Updating Firefox Installer..."
$File = "$dir\Apps\install_firefox.exe"
$ftp = "ftp://ftpserver.com/Script_Apps/install_firefox.exe"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
$webclient.DownloadFile($uri, $File)
"Updated Firefox" >> $global:logfile
mainmenu
}
else {
Write-Host "Local Copy is Newer."
sleep 3
mainmenu
}
}
You cannot use the WebClient class to check remote file timestamp.
You can use the FtpWebRequest class with its GetDateTimestamp FTP "method" and parse the UTC timestamp string it returns. The format is specified by RFC 3659 to be YYYYMMDDHHMMSS[.sss].
That would work only if the FTP server supports MDTM command that the method uses under the cover (most servers do, but not all).
$url = "ftp://ftpsite.com/folder/install_firefox.exe"
$ftprequest = [System.Net.FtpWebRequest]::Create($url)
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::GetDateTimestamp
$response = $ftprequest.GetResponse().StatusDescription
$tokens = $response.Split(" ")
$code = $tokens[0]
if ($code -eq 213)
{
Write-Host "Timestamp is" $tokens[1]
}
else
{
Write-Host "Error" $response
}
It would output something like:
Timestamp is 20150709065036
Now you parse it, and compare against a UTC timestamp of a local file:
(Get-Item "install_firefox.exe").LastWriteTimeUtc
Or save yourself some time and use an FTP library/tool that can do this for you.
For example with WinSCP .NET assembly, you can synchronize whole remote folder with installers with a local copy with one call to the Session.SynchronizeDirectories. Or your can limit the synchronization to a single file only.
# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"
# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions
$sessionOptions.Protocol = [WinSCP.Protocol]::Ftp
$sessionOptions.HostName = "ftpsite.com"
$session = New-Object WinSCP.Session
# Connect
$session.Open($sessionOptions)
$transferOptions = New-Object WinSCP.TransferOptions
# Synchronize only this one file.
# If you remove the file mask, all files in the folder are synchronized:
$transferOptions.FileMask = "install_firefox.exe"
$session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Local, "$dir\Apps", "/folder",
$False, $False, [WinSCP.SynchronizationCriteria]::Time,
$transferOptions).Check()
To use the assembly, just extract a contents of .NET assembly package to your script folder. No other installation is needed.
The assembly supports not only the MDTM, but also other alternative methods to retrieve the timestamp.
See also a related Powershell example that shows both the above code and other techniques.
(I'm the author of WinSCP)

Creating a zipped/compressed folder in Windows using Powershell or the command line

I am creating a nightly database schema file and would like to put all the files created each night, one for each database, into a folder and compress that folder.
I have a PowerShell script that creates the schema.Only creation script of the db's and then adds all the files to a new folder. The problem lies within the compression portion of this process.
Does anybody have any idea if this can be accomplished with the pre-installed Windows utility that handles folder compression?
It would be best to use that utility if possible rather than something like 7zip (I don't feel like installing 7zip on every customers' server and it may take IT years to do it if I ask them).
A native way with latest .NET 4.5 framework, but entirely feature-less:
Creation:
Add-Type -Assembly "System.IO.Compression.FileSystem" ;
[System.IO.Compression.ZipFile]::CreateFromDirectory("c:\your\directory\to\compress", "yourfile.zip") ;
Extraction:
Add-Type -Assembly "System.IO.Compression.FileSystem" ;
[System.IO.Compression.ZipFile]::ExtractToDirectory("yourfile.zip", "c:\your\destination") ;
As mentioned, totally feature-less, so don't expect an overwrite flag.
Here's a couple of zip-related functions that don't rely on extensions: Compress Files with Windows PowerShell.
The main function that you'd likely be interested in is:
function Add-Zip
{
param([string]$zipfilename)
if(-not (test-path($zipfilename)))
{
set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
(dir $zipfilename).IsReadOnly = $false
}
$shellApplication = new-object -com shell.application
$zipPackage = $shellApplication.NameSpace($zipfilename)
foreach($file in $input)
{
$zipPackage.CopyHere($file.FullName)
Start-sleep -milliseconds 500
}
}
Usage:
dir c:\demo\files\*.* -Recurse | Add-Zip c:\demo\myzip.zip
There is one caveat: the shell.application object's NameSpace() function fails to open up the zip file for writing if the path isn't absolute. So, if you passed a relative path to Add-Zip, it'll fail with a null error, so the path to the zip file must be absolute.
Or you could just add a $zipfilename = resolve-path $zipfilename at the beginning of the function.
As of PowersShell 5 there is a Compress-Archive cmdlet that does the task out of the box.
This compresses .\in contents to .\out.zip with System.IO.Packaging.ZipPackage following the example here
$zipArchive = $pwd.path + "\out.zip"
[System.Reflection.Assembly]::Load("WindowsBase,Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
$ZipPackage=[System.IO.Packaging.ZipPackage]::Open($zipArchive, [System.IO.FileMode]"OpenOrCreate", [System.IO.FileAccess]"ReadWrite")
$in = gci .\in | select -expand fullName
[array]$files = $in -replace "C:","" -replace "\\","/"
ForEach ($file In $files) {
$partName=New-Object System.Uri($file, [System.UriKind]"Relative")
$part=$ZipPackage.CreatePart($partName, "application/zip", [System.IO.Packaging.CompressionOption]"Maximum")
$bytes=[System.IO.File]::ReadAllBytes($file)
$stream=$part.GetStream()
$stream.Write($bytes, 0, $bytes.Length)
$stream.Close()
}
$ZipPackage.Close()
Used voithos' answer to zip files up in powershell, just had one problem with the Add-Zip function, the Start-sleep -milliseconds 500 caused problems if the file couldn't be fully zipped up in that time -> the next one starting before it was complete caused errors and some files not to be zipped.
So after playing around for a bit, first trying to get a counter going to check the count of the $zipPackage.Items() and only continuing after the items count increased (which did not work as it would return 0 in some cases when it should not) I found that it will return 0 if the package is still zipping/copying the files up (I think, haha). Added a simple while loop with the start-sleep inside of it, waiting for the zipPackage.Items().count to be a non-zero value before continuing and this seems to solve the problem.
function Add-Zip
{
param([string]$zipfilename)
if(-not (test-path($zipfilename)))
{
set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
(dir $zipfilename).IsReadOnly = $false
}
$shellApplication = new-object -com shell.application
$zipPackage = $shellApplication.NameSpace($zipfilename)
foreach($file in $input)
{
$zipPackage.CopyHere($file.FullName)
do
{
Start-sleep -milliseconds 250
}
while ($zipPackage.Items().count -eq 0)
}
}
Using PowerShell Version 3.0:
Copy-ToZip -File ".\blah" -ZipFile ".\blah.zip" -Force
Hope this helps.

Dismiss a Powershell form controlled by a start-job task

I've been tasked with building a powershell script with a GUI which enables users to install network printers. I've succesfully managed to do so, but I cannot meet the requirement that the user be shown a 'please wait' window whilst the printers install. If I switch to the window from the main thread, the GUI hangs. If I move showing the window to a seperate job, I'm never able to close the window again. Here's my attempt:
$waitForm = New-Object 'System.Windows.Forms.Form'
$CloseButton_Click={
# open "please wait form"
Start-Job -Name waitJob -ScriptBlock $callWork -ArgumentList $waitForm
#perform long-running (duration unknown) task of adding several network printers here
$max = 5
foreach ($i in $(1..$max)){
sleep 1 # lock up the thread for a second at a time
}
# close the wait form - doesn't work. neither does remove-job
$waitForm.Close()
Remove-Job -Name waitJob -Force
}
$callWork ={
param $waitForm
[void][reflection.assembly]::Load("System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
$waitForm = New-Object 'System.Windows.Forms.Form'
$labelInstallingPrintersPl = New-Object 'System.Windows.Forms.Label'
$waitForm.Controls.Add($labelInstallingPrintersPl)
$waitForm.ClientSize = '502, 103'
$labelInstallingPrintersPl.Location = '25, 28'
$labelInstallingPrintersPl.Text = "Installing printers - please wait..."
$waitForm.ShowDialog($this)
}
Does anyone know how I can dismiss the $waitForm window when the long-running task has concluded?
You could try to run the Windows Forms dialog on the main thread and do the actual work in a background job:
Add-Type -Assembly System.Windows.Forms
$waitForm = New-Object 'System.Windows.Forms.Form'
$labelInstallingPrintersPl = New-Object 'System.Windows.Forms.Label'
$waitForm.Controls.Add($labelInstallingPrintersPl)
$waitForm.ClientSize = '502, 103'
$labelInstallingPrintersPl.Location = '25, 28'
$labelInstallingPrintersPl.Text = "Installing printers - please wait..."
$waitForm.ShowDialog($this)
Start-Job -ScriptBlock $addPrinters | Wait-Job
$waitForm.Close()
$addPrinters = {
$max = 5
foreach ($i in $(1..$max)) {
sleep 1 # lock up the thread for a second at a time
}
}
This first answer was correct, create the form on the main thread and perform the long running task on a separate thread. The reason it doesn't execute the main code until after the form is dismissed is because you're using the 'ShowDialog' method of the form, this method haults subsequent code execution until the form is closed.
Instead use the 'show' method, code execution will continue, you should probably include some event handlers to dispose of the form
Add-Type -Assembly System.Windows.Forms
$waitForm = New-Object 'System.Windows.Forms.Form'
$labelInstallingPrintersPl = New-Object 'System.Windows.Forms.Label'
$waitForm.Controls.Add($labelInstallingPrintersPl)
$waitForm.ClientSize = '502, 103'
$labelInstallingPrintersPl.Location = '25, 28'
$labelInstallingPrintersPl.Text = "Installing printers - please wait..."
$waitForm.Add_FormClosed({
$labelInstallingPrintersPl.Dispose()
$waitForm.Dispose()
})
$waitForm.Show($this)
Start-Job -ScriptBlock $addPrinters | Wait-Job
$waitForm.Close()
$addPrinters = {
$max = 5
foreach ($i in $(1..$max)) {
sleep 1 # lock up the thread for a second at a time
}
}
How about adding a Windows.Forms.Progressbar to the main GUI window? Update its value step by step when adding printers, so users will see that the application is working.

Resources