two background jobs colision

Ask your PowerShell-related questions, including questions on cmdlet development!
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 8 years and 2 months 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
User avatar
noescape
Posts: 16
Last visit: Tue Jan 04, 2022 2:17 am

two background jobs colision

Post by noescape »

So I have a GUI script for station management with multiple function and for many functions I use the powershell studio JobTracker framework with no problems. Until yesterday, when I decided to convert a test-connection function to run as a job with the prebuilt job framework aswell.
It looks like this:
  1. $script:pingtimer = New-Object System.Windows.Forms.Timer
  2.         $script:pingtimer.Enabled = $true
  3.         $pingtimer.Interval = 10000
  4.         $pingtimer.Add_Tick({
  5.             if ($pc -eq $env:COMPUTERNAME)
  6.             {
  7.                 $panel_online.BackColor = 'GreenYellow'
  8.                 $labelStatusPCOnOff.Text = "LOCALHOST"
  9.                 $script:statusTargetPc = $true
  10.             }
  11.             elseif (!($pc))
  12.             {
  13.                 $panel_online.BackColor = 'Red'
  14.                 $labelStatusPCOnOff.Text = "NO TARGET"
  15.                 $script:statusTargetPc = $false
  16.             }
  17.             else
  18.             {
  19.                 Add-JobTracker -Name "ping" -JobScript { `
  20.                     param ($pc)
  21.                     $a = Test-Connection -ComputerName $pc -Quiet -Count 1
  22.                     return $a
  23.                 } -CompletedScript { `
  24.                     $ping = Receive-Job -Name "ping"
  25.                     if ($ping)
  26.                     {
  27.                         $panel_online.BackColor = 'GreenYellow'
  28.                         $labelStatusPCOnOff.Text = "ONLINE"
  29.                         $script:statusTargetPc = $true
  30.                     }
  31.                     else
  32.                     {
  33.                         $panel_online.BackColor = 'Red'
  34.                         $labelStatusPCOnOff.Text = "OFFLINE"
  35.                         $script:statusTargetPc = $false
  36.                     }
  37.                 } -ArgumentList $pc
  38.             }
  39.         })
  40.         $script:pingtimer.Start()
The pingtimer will create a new job every 10 seconds and then return an online/offline value in the completedscript block which then updates the form. Works like charm.

Problems start to appear when I run a different job function that I use to scan a remote computer for specific changes (software metering), to be specific, the problem occurs when this job finishes its "scriptblock" part and jumps to "completedscript" part. This part looks like this:
  1. -CompletedScript {
  2.                
  3.                 #get job results
  4.                 Param ($Job)
  5.                 $script:scanafter = Receive-Job -Job $Job -Keep
  6.                
  7.                 Add-Log -Color 'Red' -Value "COMPARE SCAN COMPLETE, GENERATING RESULTS..." -NewLine
  8.                 Show-NotifyIcon -BalloonTipTitle "PCT" -BalloonTipText "COMPARE SCAN COMPLETE" -BalloonTipIcon 'Info' -NotifyIcon $notifyicon_PCT
  9.                
  10.                 #compare scans and show differences
  11.                 $script:compareafter = [ordered]@{ }
  12.                 $compareafter.apps = compare $scanbefore.apps $scanafter.apps -Property ProgramName, DisplayVersion -PassThru | ? { $_.Sideindicator -eq "=>" }
  13.                 $compareafter.appfolders = compare $scanbefore.appfolders $scanafter.appfolders -Property FullName -PassThru | ? { $_.Sideindicator -eq "=>" }
  14.                 $compareafter.filteredappfolders = @()
  15.                 $previous = $null
  16.                 $compareafter.appfolders.fullname | foreach {
  17.                     if (($previous -eq $null) -or ($_ -notlike "$previous*"))
  18.                     {
  19.                         $previous = $_
  20.                         $compareafter.filteredappfolders += "$_"
  21.                     }
  22.                 }
  23.                 Clear-Variable previous
  24.                 $compareafter.exefiles = compare $scanbefore.exefiles $scanafter.exefiles -Property FullName -PassThru | ? { $_.Sideindicator -eq "=>" }
  25.                 $compareafter.registry = compare $scanbefore.registry $scanafter.registry | ? { $_.Sideindicator -eq "=>" }
  26.                 $compareafter.drivers = compare $scanbefore.drivers $scanafter.drivers -Property OriginalFileName, Version -PassThru | ? { $_.Sideindicator -eq "=>" }
  27.                 $compareafter.services = compare $scanbefore.services $scanafter.services -Property Name, DisplayName -PassThru | ? { $_.Sideindicator -eq "=>" }
  28.                 $compareafter.tasks = compare $scanbefore.tasks $scanafter.tasks -Property TaskName, TaskPath, Description | ? { $_.Sideindicator -eq "=>" }
  29.                 $compareafter.envvar = compare $scanbefore.envvar $scanafter.envvar -Property Name, Value | ? { $_.Sideindicator -eq "=>" }
  30.                 $compareafter.startmenu = compare $scanbefore.startmenu $scanafter.startmenu -Property FullName | ? { $_.Sideindicator -eq "=>" }
  31.                
  32.                 <#
  33.                 #save dialog
  34.                 $SaveCompareDialog = New-Object System.Windows.Forms.SaveFileDialog
  35.                 $SaveCompareDialog.Title = "Save Compare Results"
  36.                 $SaveCompareDialog.initialDirectory = "$env:LOCALAPPDATA\PCT"
  37.                 $SaveCompareDialog.FileName = $PackageFullName + "_AppScan"
  38.                 $SaveCompareDialog.filter = “Text files (*.txt)|*.txt”
  39.                 $DialogResult = $SaveCompareDialog.ShowDialog()
  40.                 if ($DialogResult -eq [System.Windows.Forms.DialogResult]::OK)
  41.                 { }
  42.                 #>
  43.                
  44.                 #format data and output to a file
  45.                    
  46.                     $savefilepath = "$env:LOCALAPPDATA\PCT\AppScan.txt" #$SaveCompareDialog.FileName
  47.                     Remove-Item -Path $savefilepath -Force -ea 'SilentlyContinue'
  48.                     New-Item $savefilepath -ItemType File -Force | Add-Content -value "METERING DIFFERENCES:`r`r" -Encoding Unicode
  49.                     $compareafter.apps | select ProgramName, DisplayVersion, Publisher, InstallDate, UninstallString | ft | Out-String | Out-File $savefilepath -Append -Force
  50.                     #$compareafter.appfolders | select @{ Name = "AppFolders"; Expression = "FullName" } | Out-String | Out-File $SaveCompareDialog.filename -Append -Force
  51.                     if ($compareafter.filteredappfolders) { Add-Content $savefilepath -Value "Application Folders:`n--------------------`r" }
  52.                     $compareafter.filteredappfolders | Out-File $savefilepath -Append -Force
  53.                     $compareafter.exefiles | select @{ Name = "ExeFiles"; Expression = "FullName" } | Out-String | Out-File $savefilepath -Append -Force
  54.                     $compareafter.registry | select @{ Name = "Registry"; Expression = "InputObject" } | Out-String | Out-File $savefilepath -Append -Force
  55.                     $compareafter.drivers | select @{ Name = "Driver"; Expression = "OriginalFileName" }, @{ Name = "Description"; Expression = "ClassDescription" }, Version, ProviderName | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
  56.                     $compareafter.services | select @{ Name = "Service"; Expression = "Name" }, @{ Name = "Service Display Name"; Expression = "DisplayName" }, Status | ? { $_.Service -ne "PSEXESVC" } | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
  57.                     $compareafter.tasks | select TaskName, TaskPath, Description | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
  58.                     $compareafter.envvar | select @{ Name = "Environment Variables"; Expression = "Name" }, Value | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
  59.                     $compareafter.startmenu | select @{ Name = "Start Menu"; Expression = "FullName" } | Out-String | Out-File $savefilepath -Append -Force
  60.                    
  61.                     ii $savefilepath
  62.                    
  63.                     $exportcompare = show-messagebox -YesNo -M "Do you want to export compare results to Exchange?" -TM -Q -T "Export Compare Results"
  64.                     if ($exportcompare -eq "Yes")
  65.                     {
  66.                         if (!(Test-Path "$exchange\MeteringFiles\$PackageFullName")) { New-Item -ItemType directory -Path "$exchange\MeteringFiles" -Name $PackageFullName }
  67.                         Copy-Item $savefilepath "$exchange\MeteringFiles\$PackageFullName" -Force
  68.                         $exeprops = foreach ($i in $compareafter.exefiles.fullname) { icm -cn $pc -ScriptBlock { param ($exe) Get-ItemProperty $exe } -ArgumentList $i }
  69.                         $exeprops | ConvertTo-Csv | Out-File "$exchange\MeteringFiles\$PackageFullName\$PackageFullName.$PackageVersion EXE.csv" -Force
  70.                     }              
  71.                
  72.                
  73.                 $toolstrip_Progressbar2.Style = 'Blocks'
  74.                 $toolstrip_Progressbar2.Value = 0
  75.                 $toolstrip_Status.Text = "Idle"
  76.                
  77.             } -ArgumentList ($pc)
When this script block gets to the part where I save the output to a file it gets thrown away and jumps to the beginning of the "completedscript" (????) and starts doing this scriptblock again. I spent many hours debugging this and was able to figure out that the culprit is the pingtimer job, that kicks in every 10 seconds and just happens to kick in during the execution of this scriptblock. For some reason which I am unable to figure out it keeps trying to remove this job before it gets to execute the whole completedscript block.
If I remove the pingtimer job, other jobs works just fine.

The Output console is showing this:
  1. ERROR: Exception calling "RemoveAt" with "1" argument(s): "Index je mimo rozsah. Index musí být nezáporný a musí být menší než velikost kolekce.
  2. ERROR: Název parametru: index"
  3. Globals.ps1 (448): ERROR: At Line: 448 char: 6
  4. ERROR: +                         $JobTrackerList.RemoveAt($index)
  5. ERROR: +                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  6. ERROR:     + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
  7. ERROR:     + FullyQualifiedErrorId : ArgumentOutOfRangeException
  8. ERROR:
  9. ERROR: Remove-Job : The command cannot remove the job because it does not exist or because it is a child job. Child jobs can be removed only by removing the parent jo
  10. ERROR: b.
  11. Globals.ps1 (449): ERROR: At Line: 449 char: 6
  12. ERROR: +                         Remove-Job -Job $psObject.Job
  13. ERROR: +                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  14. ERROR:     + CategoryInfo          : InvalidOperation: (System.Manageme...n.PSRemotingJob:PSRemotingJob) [Remove-Job], ArgumentException
  15. ERROR:     + FullyQualifiedErrorId : CannotRemoveJob,Microsoft.PowerShell.Commands.RemoveJobCommand
  16. ERROR:
Can you please help me figure out why is this happening and how to prevent the pingtimer job to interrupt other jobs?

Thanks a lot,

Thomas
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: two background jobs colision

Post by jvierra »

There is way too much code to se your issue. It is fairly certain that you have a bit of bad script somewhere. Anther issue is that you will not be able to reliably run this under a debugger while the timer is ticking. You will need to put a timer stop in at the breakpoint, then you will be able to step without having the timer force the code to execute.
User avatar
noescape
Posts: 16
Last visit: Tue Jan 04, 2022 2:17 am

Re: two background jobs colision

Post by noescape »

jvierra wrote:There is way too much code to se your issue. It is fairly certain that you have a bit of bad script somewhere. Anther issue is that you will not be able to reliably run this under a debugger while the timer is ticking. You will need to put a timer stop in at the breakpoint, then you will be able to step without having the timer force the code to execute.
Didn't mean to put so much code, but most of the completedscript block you can disregard. I have been able to discover another interesting fact that is connected to the issue - the completedscript starts to run over again from the beginning each time it gets to the point to call the MessageBox form control or any other form control for that matter (I was using SaveFileDialog before and it was doing the same thing - as soon as I called $savefiledialog.showdialog() the script breaks and starts over again).
  1. $exportcompare = show-messagebox -YesNo -M "Do you want to export compare results to Exchange?" -TM -Q -T "Export Compare Results"
Somehow this is connected with the pingtimer job I think, because when I remove that periodic job, others work just fine...

Maybe this makes more sense to you than me? I understand it's way to much code to see the issue, below is a shorter version of the scriptblock with which I can still reproduce the problem.
  1. -CompletedScript {
  2.                
  3.                 #get job results
  4.                 Param ($Job)
  5.                 $script:scanafter = Receive-Job -Name "AppScan_After" -Keep
  6.                
  7.                 #compare scans and show differences
  8.  
  9.                 #DO THE WORK HERE              
  10.                    
  11.                 #format data and output to a file
  12.                    
  13.                 #DO THE WORK HERE
  14.        
  15.                
  16.                 $exportcompare = show-messagebox -YesNo -M "Do you want to export compare results to Exchange?" -TM -Q -T "Export Compare Results"
  17.                 if ($exportcompare -eq "Yes")
  18.                 {
  19.                     if (!(Test-Path "$exchange\MeteringFiles\$PackageFullName")) { New-Item -ItemType directory -Path "$exchange\MeteringFiles" -Name $PackageFullName }
  20.                     Copy-Item $savefilepath "$exchange\MeteringFiles\$PackageFullName" -Force
  21.                     $exeprops = foreach ($i in $compareafter.exefiles.fullname) { icm -cn $pc -ScriptBlock { param ($exe) Get-ItemProperty $exe } -ArgumentList $i }
  22.                     $exeprops | ConvertTo-Csv | Out-File "$exchange\MeteringFiles\$PackageFullName\$PackageFullName.$PackageVersion EXE.csv" -Force
  23.                 }
  24.                
  25.                 $toolstrip_Progressbar2.Style = 'Blocks'
  26.                 $toolstrip_Progressbar2.Value = 0
  27.                 $toolstrip_Status.Text = "Idle"
  28.                 $timerJobTracker.Start()
Line #16 executes and then the completedscriptblock starts again.
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: two background jobs colision

Post by jvierra »

You can't place a message box or anything else that blocks a completed script. It will cause the event to be re-entered and you will likely get a deadlock.

Your issue is likely one of design. We would like a list of completed jobs and a separate process where the user can select to export the results. This design would prevent conflicts.
Of course you may have other issues. I recommend simplifying the design and the code.

Why are you starting a new jobtracker in the middle of a completed scritpblock? Again. TO much complexity and conflict. THe job tracker was not designed for that kind of complexity.

If you need to re-queue items on completion do exactly that. Have a queue and run an independent time the dequeues and executes new jobs but don't try to reuse the job tracker. Each kind of job needs its own script blocks.
User avatar
noescape
Posts: 16
Last visit: Tue Jan 04, 2022 2:17 am

Re: two background jobs colision

Post by noescape »

Where do I call jobtracker from completed scriptblock? If you mean the $timerJobTracker.start() that was just for the debugging purpose as you proposed before. I can't see what else could you have in mind with that. I will have to re-design the whole thing that is true. I'm glad that I know now I can't invoke new form controls in this scriptblock without trouble, it's a shame.

Thanks for help
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: two background jobs colision

Post by jvierra »

How can you start it without stopping it?

YOU cannot use this line: show-messagebox - it will block and cause issues.. You can try to re-add it once you understand what else is happening.

Remember that the debugger does not stop background events.
User avatar
noescape
Posts: 16
Last visit: Tue Jan 04, 2022 2:17 am

Re: two background jobs colision

Post by noescape »

Yes I get it now... the line works if I remove the pingtimer job, I'll have to figure out that one first. I stopped the timer before I called the job, I just didn't add that to the post...
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: two background jobs colision

Post by jvierra »

noescape wrote:Yes I get it now... the line works if I remove the pingtimer job, I'll have to figure out that one first. I stopped the timer before I called the job, I just didn't add that to the post...
The timer needs to be stopped at the very beginning of the scritpblock. The scriptblock is called by the timer tick event. There is no way to stop it before the code is entered.
User avatar
noescape
Posts: 16
Last visit: Tue Jan 04, 2022 2:17 am

Re: two background jobs colision

Post by noescape »

Thanks for useful information, it makes sense. I figured it out myself but I'm glad it was the right place to stop the timer.
jvierra
Posts: 15439
Last visit: Tue Nov 21, 2023 6:37 pm
Answers: 30
Has voted: 4 times
Been upvoted: 33 times

Re: two background jobs colision

Post by jvierra »

noescape wrote:Thanks for useful information, it makes sense. I figured it out myself but I'm glad it was the right place to stop the timer.
Here is how to run a continuous background ping against multiple computers. It will run until you stop the job. You can use a timer to poll the job and display/update the status of the computers. Other tasks can just test the status. I use read only checkboxes to display status and se the color. If checked the computer is online and responding.
  1. workflow pingall  {
  2.     param([string[]]$computers)
  3.     while(1){
  4.         foreach -parallel($c in $computers) {
  5.             Test-Connection $c -Count 1
  6.             sleep 2
  7.         }
  8.     }
  9. }
  10. pingall 'omega', 'w8test', 'alpha'
  11. pingall 'omega', 'w8test', 'alpha' -asjob -JobName PingAll
This topic is 8 years and 2 months 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