Using Button - Start Job control with multiple jobs

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.
This topic is 5 years and 3 weeks old and has exceeded the time allowed for comments. Please begin a new topic or use the search feature to find a similar but newer topic.
Locked
tphillipstx
Posts: 7
Last visit: Wed Apr 20, 2022 6:20 am

Using Button - Start Job control with multiple jobs

Post by tphillipstx »

Product, version and build: PowerShell Studio 2019, version 5.6.159
32 or 64 bit version of product: 64 bit
Operating system: Windows 10
32 or 64 bit OS: 64 bit

I'm trying to get some of my previous PowerShell scripts into a GUI. One that I use has multiple jobs (PS cmdlet Start-Job). Some jobs run in parallel and others wait for a previous job to complete before they start. I'm hoping to use the Start Job control because the jobs I'm running now cause the GUI to become unresponsive. I have read the PowerShell Studio: Creating Responsive Forms article but my ignorance doesn't help with finding the answer there.
I've attached what is just the code as it comes out-of-the-box for the Button - Start Job control and a couple of added items.
What I need help with is how to add additional jobs to the start job. So, is it possible and if yes, how, do I add additional jobs to the start job control? For example, add:
$scriptblock = {
start-sleep -s 3
write-output "step 3 complete"
write-output "step 4 complete"
}

Thanks in advance for any help.
  1.  
  2. $form1_Load={
  3.     #TODO: Initialize Form Controls here
  4. }
  5.  
  6. $buttonStartJob_Click={
  7.    
  8.     $buttonStartJob.Enabled = $false
  9.    
  10.     $textbox1.AppendText("Start Job Button selected`r`n")
  11.     #Create a New Job using the Job Tracker
  12.     $paramAddJobTracker = @{
  13.         Name      = 'JobName'
  14.         JobScript = {
  15.             #--------------------------------------------------
  16.             #TODO: Set a script block
  17.             #Important: Do not access form controls from this script block.
  18.            
  19.             Param ($Argument1) #Pass any arguments using the ArgumentList parameter
  20.             start-sleep -s 3
  21.             write-output "step 1 complete`r`n"
  22.             write-output "step 2 complete`r`n"
  23.             for ($i = 0; $i -lt 50; $i++) { Start-Sleep -Milliseconds 100 }
  24.            
  25.             #--------------------------------------------------
  26.         }
  27.         ArgumentList = $null
  28.         CompletedScript = {
  29.             Param ([System.Management.Automation.Job]$Job)
  30.             $results = Receive-Job -Job $Job
  31.             $textbox1.AppendText($results)
  32.             #Enable the Button
  33.             $textbox1.AppendText("Completed Script`r`n")
  34.             $buttonStartJob.ImageIndex = -1
  35.             $buttonStartJob.Enabled = $true
  36.         }
  37.         UpdateScript = {
  38.             Param ([System.Management.Automation.Job]$Job)
  39.             $textbox1.AppendText("Update Script`r`n")
  40.             #$results = Receive-Job -Job $Job -Keep
  41.             #Animate the Button
  42.             if ($null -ne $buttonStartJob.ImageList)
  43.             {
  44.                 if ($buttonStartJob.ImageIndex -lt $buttonStartJob.ImageList.Images.Count - 1)
  45.                 {
  46.                     $buttonStartJob.ImageIndex += 1
  47.                 }
  48.                 else
  49.                 {
  50.                     $buttonStartJob.ImageIndex = 0
  51.                 }
  52.             }
  53.         }
  54.     }
  55.    
  56.     Add-JobTracker @paramAddJobTracker
  57. }
  58.  
  59. $jobTracker_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
  60. #Event Argument: $_ = [System.Windows.Forms.FormClosedEventArgs]
  61.     #Stop any pending jobs
  62.     Stop-JobTracker
  63. }
  64.  
  65. $timerJobTracker_Tick={
  66.     Update-JobTracker
  67. }
  68.  
  69. #region Job Tracker
  70. $JobTrackerList = New-Object System.Collections.ArrayList
  71. function Add-JobTracker
  72. {
  73.     <#
  74.         .SYNOPSIS
  75.             Add a new job to the JobTracker and starts the timer.
  76.    
  77.         .DESCRIPTION
  78.             Add a new job to the JobTracker and starts the timer.
  79.    
  80.         .PARAMETER  Name
  81.             The name to assign to the job.
  82.    
  83.         .PARAMETER  JobScript
  84.             The script block that the job will be performing.
  85.             Important: Do not access form controls from this script block.
  86.    
  87.         .PARAMETER ArgumentList
  88.             The arguments to pass to the job.
  89.         .PARAMETER  CompletedScript
  90.             The script block that will be called when the job is complete.
  91.             The job is passed as an argument. The Job argument is null when the job fails.
  92.    
  93.         .PARAMETER  UpdateScript
  94.             The script block that will be called each time the timer ticks.
  95.             The job is passed as an argument. Use this to get the Job's progress.
  96.    
  97.         .EXAMPLE
  98.             Add-JobTracker -Name 'JobName' `
  99.             -JobScript {   
  100.                 Param($Argument1)#Pass any arguments using the ArgumentList parameter
  101.                 #Important: Do not access form controls from this script block.
  102.                 Get-WmiObject Win32_Process -Namespace "root\CIMV2"
  103.             }`
  104.             -CompletedScript {
  105.                 Param($Job)    
  106.                 $results = Receive-Job -Job $Job
  107.             }`
  108.             -UpdateScript {
  109.                 Param($Job)
  110.                 #$results = Receive-Job -Job $Job -Keep
  111.             }
  112.    
  113.         .LINK
  114.            
  115.     #>
  116.    
  117.     Param(
  118.     [ValidateNotNull()]
  119.     [Parameter(Mandatory=$true)]
  120.     [string]$Name,
  121.     [ValidateNotNull()]
  122.     [Parameter(Mandatory=$true)]
  123.     [ScriptBlock]$JobScript,
  124.     $ArgumentList = $null,
  125.     [ScriptBlock]$CompletedScript,
  126.     [ScriptBlock]$UpdateScript)
  127.    
  128.     #Start the Job
  129.     $job = Start-Job -Name $Name -ScriptBlock $JobScript -ArgumentList $ArgumentList
  130.    
  131.     if($null -ne $job)
  132.     {
  133.         #Create a Custom Object to keep track of the Job & Script Blocks
  134.         $members = @{   'Job' = $Job;
  135.                         'CompleteScript' = $CompletedScript;
  136.                         'UpdateScript' = $UpdateScript}
  137.        
  138.         $psObject = New-Object System.Management.Automation.PSObject -Property $members
  139.        
  140.         [void]$JobTrackerList.Add($psObject)
  141.        
  142.         #Start the Timer
  143.         if(-not $timerJobTracker.Enabled)
  144.         {
  145.             $timerJobTracker.Start()
  146.         }
  147.     }
  148.     elseif($null -ne $CompletedScript)
  149.     {
  150.         #Failed
  151.         Invoke-Command -ScriptBlock $CompletedScript -ArgumentList $null
  152.     }
  153.  
  154. }
  155.  
  156. function Update-JobTracker
  157. {
  158.     <#
  159.         .SYNOPSIS
  160.             Checks the status of each job on the list.
  161.     #>
  162.    
  163.     #Poll the jobs for status updates
  164.     $timerJobTracker.Stop() #Freeze the Timer
  165.    
  166.     for($index = 0; $index -lt $JobTrackerList.Count; $index++)
  167.     {
  168.         $psObject = $JobTrackerList[$index]
  169.        
  170.         if($null -ne $psObject)
  171.         {
  172.             if($null -ne $psObject.Job)
  173.             {
  174.                 if ($psObject.Job.State -eq 'Blocked')
  175.                 {
  176.                     #Try to unblock the job
  177.                     Receive-Job $psObject.Job | Out-Null
  178.                 }
  179.                 elseif($psObject.Job.State -ne 'Running')
  180.                 {              
  181.                     #Call the Complete Script Block
  182.                     if($null -ne $psObject.CompleteScript)
  183.                     {
  184.                         #$results = Receive-Job -Job $psObject.Job
  185.                         Invoke-Command -ScriptBlock $psObject.CompleteScript -ArgumentList $psObject.Job
  186.                     }
  187.                    
  188.                     $JobTrackerList.RemoveAt($index)
  189.                     Remove-Job -Job $psObject.Job
  190.                     $index-- #Step back so we don't skip a job
  191.                 }
  192.                 elseif($null -ne $psObject.UpdateScript)
  193.                 {
  194.                     #Call the Update Script Block
  195.                     Invoke-Command -ScriptBlock $psObject.UpdateScript -ArgumentList $psObject.Job
  196.                 }
  197.             }
  198.         }
  199.         else
  200.         {
  201.             $JobTrackerList.RemoveAt($index)
  202.             $index-- #Step back so we don't skip a job
  203.         }
  204.     }
  205.    
  206.     if($JobTrackerList.Count -gt 0)
  207.     {
  208.         $timerJobTracker.Start()#Resume the timer
  209.     }
  210. }
  211.  
  212. function Stop-JobTracker
  213. {
  214.     <#
  215.         .SYNOPSIS
  216.             Stops and removes all Jobs from the list.
  217.     #>
  218.     #Stop the timer
  219.     $timerJobTracker.Stop()
  220.    
  221.     #Remove all the jobs
  222.     while($JobTrackerList.Count -gt 0)
  223.     {
  224.         $job = $JobTrackerList[0].Job
  225.         $JobTrackerList.RemoveAt(0)
  226.         Stop-Job $job
  227.         Remove-Job $job
  228.     }
  229. }
  230. #endregion
  231.  
User avatar
davidc
Posts: 5913
Last visit: Mon Jul 08, 2019 8:55 am
Been upvoted: 2 times

Re: Using Button - Start Job control with multiple jobs

Post by davidc »

[TOPIC MOVED TO POWERSHELL GUIS FORUM BY MODERATOR]
David
SAPIEN Technologies, Inc.
User avatar
davidc
Posts: 5913
Last visit: Mon Jul 08, 2019 8:55 am
Been upvoted: 2 times

Re: Using Button - Start Job control with multiple jobs

Post by davidc »

You can add a different Button - Start Job control set for each job.

If you want the same button to create multiple jobs, then just invoke the Add-JobTracker function a second time with different parameters.
David
SAPIEN Technologies, Inc.
tphillipstx
Posts: 7
Last visit: Wed Apr 20, 2022 6:20 am

Re: Using Button - Start Job control with multiple jobs

Post by tphillipstx »

EDIT: Again, thanks David and yes, I've found it is as easy as adding and calling another job.

Thanks for the quick response David. For the single button option, is adding the second job and running the function as simple as this.

Code: Select all

	#Create a New Job using the Job Tracker
	$paramAddJobTracker2 = @{
		Name	  = 'JobName'
		JobScript = {
			#--------------------------------------------------
			#TODO: Set a script block
			#Important: Do not access form controls from this script block.
			
			Param ($Argument1) #Pass any arguments using the ArgumentList parameter
			start-sleep -s 3
			write-output "step 3 complete`r`n"
			write-output "step 4 complete`r`n"
			for ($i = 0; $i -lt 50; $i++) { Start-Sleep -Milliseconds 100 }
			
			#--------------------------------------------------
		}
		ArgumentList = $null
		CompletedScript = {
			Param ([System.Management.Automation.Job]$Job)
			$results = Receive-Job -Job $Job 
			$textbox1.AppendText($results)
			#Enable the Button
			$textbox1.AppendText("Completed Script`r`n")
			$buttonStartJob.ImageIndex = -1
			$buttonStartJob.Enabled = $true
		}
		UpdateScript = {
			Param ([System.Management.Automation.Job]$Job)
			$textbox1.AppendText("Update Script`r`n")
			#$results = Receive-Job -Job $Job -Keep
			#Animate the Button
			if ($null -ne $buttonStartJob.ImageList)
			{
				if ($buttonStartJob.ImageIndex -lt $buttonStartJob.ImageList.Images.Count - 1)
				{
					$buttonStartJob.ImageIndex += 1
				}
				else
				{
					$buttonStartJob.ImageIndex = 0
				}
			}
		}
	}
	
	Add-JobTracker @paramAddJobTracker
	Add-JobTracker @paramAddJobTracker2
User avatar
davidc
Posts: 5913
Last visit: Mon Jul 08, 2019 8:55 am
Been upvoted: 2 times

Re: Using Button - Start Job control with multiple jobs

Post by davidc »

Yes, that is correct. Just create two sets of splatted parameters and pass them to Add-JobTracker.
You might have to modified to the completed script block to check if both jobs are completed before enabling the button.
David
SAPIEN Technologies, Inc.
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: Using Button - Start Job control with multiple jobs

Post by jvierra »

Each job with the job tracker is handled separately. The update and completed scripts hand the job being processed to the script but each job can have its own scripts so we can think of separate background jobs as totally independent. This means that the script blocks assigned with the "Add-JobTracker" do not have to check the job. The only job handed to the script is the one that it was created with.

You must be careful of using any global or script scoped variables in your JobTracker scripts. If you run two or more jobs that reference the same external variables then there can be issues.

Start with simple script blocks until you understand the behavior of the JobTracker system. When it is run correctly it works amazingly well.
tphillipstx
Posts: 7
Last visit: Wed Apr 20, 2022 6:20 am

Re: Using Button - Start Job control with multiple jobs

Post by tphillipstx »

I do run multiple jobs referencing the same global variables. What kind of issues should I look out for? Can I avoid issues by creating multiple global variables with the same value?
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: Using Button - Start Job control with multiple jobs

Post by jvierra »

I do not understand what that means?

The issue is when you try to write to the same global.
This topic is 5 years and 3 weeks old and has exceeded the time allowed for comments. Please begin a new topic or use the search feature to find a similar but newer topic.
Locked