two background jobs colision

Ask your Windows PowerShell-related questions, including questions on cmdlet development!
Forum rules
Do not post any licensing information in this forum.
User avatar
noescape
Posts: 16
Joined: Wed Apr 22, 2015 4:57 am

two background jobs colision

Post by noescape » Wed Jan 13, 2016 2:30 am

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.  
  3.         $script:pingtimer.Enabled = $true
  4.  
  5.         $pingtimer.Interval = 10000
  6.  
  7.         $pingtimer.Add_Tick({
  8.  
  9.             if ($pc -eq $env:COMPUTERNAME)
  10.  
  11.             {
  12.  
  13.                 $panel_online.BackColor = 'GreenYellow'
  14.  
  15.                 $labelStatusPCOnOff.Text = "LOCALHOST"
  16.  
  17.                 $script:statusTargetPc = $true
  18.  
  19.             }
  20.  
  21.             elseif (!($pc))
  22.  
  23.             {
  24.  
  25.                 $panel_online.BackColor = 'Red'
  26.  
  27.                 $labelStatusPCOnOff.Text = "NO TARGET"
  28.  
  29.                 $script:statusTargetPc = $false
  30.  
  31.             }
  32.  
  33.             else
  34.  
  35.             {
  36.  
  37.                 Add-JobTracker -Name "ping" -JobScript { `
  38.  
  39.                     param ($pc)
  40.  
  41.                     $a = Test-Connection -ComputerName $pc -Quiet -Count 1
  42.  
  43.                     return $a
  44.  
  45.                 } -CompletedScript { `
  46.  
  47.                     $ping = Receive-Job -Name "ping"
  48.  
  49.                     if ($ping)
  50.  
  51.                     {
  52.  
  53.                         $panel_online.BackColor = 'GreenYellow'
  54.  
  55.                         $labelStatusPCOnOff.Text = "ONLINE"
  56.  
  57.                         $script:statusTargetPc = $true
  58.  
  59.                     }
  60.  
  61.                     else
  62.  
  63.                     {
  64.  
  65.                         $panel_online.BackColor = 'Red'
  66.  
  67.                         $labelStatusPCOnOff.Text = "OFFLINE"
  68.  
  69.                         $script:statusTargetPc = $false
  70.  
  71.                     }
  72.  
  73.                 } -ArgumentList $pc
  74.  
  75.             }
  76.  
  77.         })
  78.  
  79.         $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.                
  4.  
  5.                 #get job results
  6.  
  7.                 Param ($Job)
  8.  
  9.                 $script:scanafter = Receive-Job -Job $Job -Keep
  10.  
  11.                
  12.  
  13.                 Add-Log -Color 'Red' -Value "COMPARE SCAN COMPLETE, GENERATING RESULTS..." -NewLine
  14.  
  15.                 Show-NotifyIcon -BalloonTipTitle "PCT" -BalloonTipText "COMPARE SCAN COMPLETE" -BalloonTipIcon 'Info' -NotifyIcon $notifyicon_PCT
  16.  
  17.                
  18.  
  19.                 #compare scans and show differences
  20.  
  21.                 $script:compareafter = [ordered]@{ }
  22.  
  23.                 $compareafter.apps = compare $scanbefore.apps $scanafter.apps -Property ProgramName, DisplayVersion -PassThru | ? { $_.Sideindicator -eq "=>" }
  24.  
  25.                 $compareafter.appfolders = compare $scanbefore.appfolders $scanafter.appfolders -Property FullName -PassThru | ? { $_.Sideindicator -eq "=>" }
  26.  
  27.                 $compareafter.filteredappfolders = @()
  28.  
  29.                 $previous = $null
  30.  
  31.                 $compareafter.appfolders.fullname | foreach {
  32.  
  33.                     if (($previous -eq $null) -or ($_ -notlike "$previous*"))
  34.  
  35.                     {
  36.  
  37.                         $previous = $_
  38.  
  39.                         $compareafter.filteredappfolders += "$_"
  40.  
  41.                     }
  42.  
  43.                 }
  44.  
  45.                 Clear-Variable previous
  46.  
  47.                 $compareafter.exefiles = compare $scanbefore.exefiles $scanafter.exefiles -Property FullName -PassThru | ? { $_.Sideindicator -eq "=>" }
  48.  
  49.                 $compareafter.registry = compare $scanbefore.registry $scanafter.registry | ? { $_.Sideindicator -eq "=>" }
  50.  
  51.                 $compareafter.drivers = compare $scanbefore.drivers $scanafter.drivers -Property OriginalFileName, Version -PassThru | ? { $_.Sideindicator -eq "=>" }
  52.  
  53.                 $compareafter.services = compare $scanbefore.services $scanafter.services -Property Name, DisplayName -PassThru | ? { $_.Sideindicator -eq "=>" }
  54.  
  55.                 $compareafter.tasks = compare $scanbefore.tasks $scanafter.tasks -Property TaskName, TaskPath, Description | ? { $_.Sideindicator -eq "=>" }
  56.  
  57.                 $compareafter.envvar = compare $scanbefore.envvar $scanafter.envvar -Property Name, Value | ? { $_.Sideindicator -eq "=>" }
  58.  
  59.                 $compareafter.startmenu = compare $scanbefore.startmenu $scanafter.startmenu -Property FullName | ? { $_.Sideindicator -eq "=>" }
  60.  
  61.                
  62.  
  63.                 <#
  64.  
  65.                 #save dialog
  66.  
  67.                 $SaveCompareDialog = New-Object System.Windows.Forms.SaveFileDialog
  68.  
  69.                 $SaveCompareDialog.Title = "Save Compare Results"
  70.  
  71.                 $SaveCompareDialog.initialDirectory = "$env:LOCALAPPDATA\PCT"
  72.  
  73.                 $SaveCompareDialog.FileName = $PackageFullName + "_AppScan"
  74.  
  75.                 $SaveCompareDialog.filter = “Text files (*.txt)|*.txt”
  76.  
  77.                 $DialogResult = $SaveCompareDialog.ShowDialog()
  78.  
  79.                 if ($DialogResult -eq [System.Windows.Forms.DialogResult]::OK)
  80.  
  81.                 { }
  82.  
  83.                 #>
  84.  
  85.                
  86.  
  87.                 #format data and output to a file
  88.  
  89.                    
  90.  
  91.                     $savefilepath = "$env:LOCALAPPDATA\PCT\AppScan.txt" #$SaveCompareDialog.FileName
  92.  
  93.                     Remove-Item -Path $savefilepath -Force -ea 'SilentlyContinue'
  94.  
  95.                     New-Item $savefilepath -ItemType File -Force | Add-Content -value "METERING DIFFERENCES:`r`r" -Encoding Unicode
  96.  
  97.                     $compareafter.apps | select ProgramName, DisplayVersion, Publisher, InstallDate, UninstallString | ft | Out-String | Out-File $savefilepath -Append -Force
  98.  
  99.                     #$compareafter.appfolders | select @{ Name = "AppFolders"; Expression = "FullName" } | Out-String | Out-File $SaveCompareDialog.filename -Append -Force
  100.  
  101.                     if ($compareafter.filteredappfolders) { Add-Content $savefilepath -Value "Application Folders:`n--------------------`r" }
  102.  
  103.                     $compareafter.filteredappfolders | Out-File $savefilepath -Append -Force
  104.  
  105.                     $compareafter.exefiles | select @{ Name = "ExeFiles"; Expression = "FullName" } | Out-String | Out-File $savefilepath -Append -Force
  106.  
  107.                     $compareafter.registry | select @{ Name = "Registry"; Expression = "InputObject" } | Out-String | Out-File $savefilepath -Append -Force
  108.  
  109.                     $compareafter.drivers | select @{ Name = "Driver"; Expression = "OriginalFileName" }, @{ Name = "Description"; Expression = "ClassDescription" }, Version, ProviderName | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
  110.  
  111.                     $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
  112.  
  113.                     $compareafter.tasks | select TaskName, TaskPath, Description | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
  114.  
  115.                     $compareafter.envvar | select @{ Name = "Environment Variables"; Expression = "Name" }, Value | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
  116.  
  117.                     $compareafter.startmenu | select @{ Name = "Start Menu"; Expression = "FullName" } | Out-String | Out-File $savefilepath -Append -Force
  118.  
  119.                    
  120.  
  121.                     ii $savefilepath
  122.  
  123.                    
  124.  
  125.                     $exportcompare = show-messagebox -YesNo -M "Do you want to export compare results to Exchange?" -TM -Q -T "Export Compare Results"
  126.  
  127.                     if ($exportcompare -eq "Yes")
  128.  
  129.                     {
  130.  
  131.                         if (!(Test-Path "$exchange\MeteringFiles\$PackageFullName")) { New-Item -ItemType directory -Path "$exchange\MeteringFiles" -Name $PackageFullName }
  132.  
  133.                         Copy-Item $savefilepath "$exchange\MeteringFiles\$PackageFullName" -Force
  134.  
  135.                         $exeprops = foreach ($i in $compareafter.exefiles.fullname) { icm -cn $pc -ScriptBlock { param ($exe) Get-ItemProperty $exe } -ArgumentList $i }
  136.  
  137.                         $exeprops | ConvertTo-Csv | Out-File "$exchange\MeteringFiles\$PackageFullName\$PackageFullName.$PackageVersion EXE.csv" -Force
  138.  
  139.                     }              
  140.  
  141.                
  142.  
  143.                
  144.  
  145.                 $toolstrip_Progressbar2.Style = 'Blocks'
  146.  
  147.                 $toolstrip_Progressbar2.Value = 0
  148.  
  149.                 $toolstrip_Status.Text = "Idle"
  150.  
  151.                
  152.  
  153.             } -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.  
  3. ERROR: Název parametru: index"
  4.  
  5. Globals.ps1 (448): ERROR: At Line: 448 char: 6
  6.  
  7. ERROR: +                         $JobTrackerList.RemoveAt($index)
  8.  
  9. ERROR: +                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  10.  
  11. ERROR:     + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
  12.  
  13. ERROR:     + FullyQualifiedErrorId : ArgumentOutOfRangeException
  14.  
  15. ERROR:
  16.  
  17. 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
  18.  
  19. ERROR: b.
  20.  
  21. Globals.ps1 (449): ERROR: At Line: 449 char: 6
  22.  
  23. ERROR: +                         Remove-Job -Job $psObject.Job
  24.  
  25. ERROR: +                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  26.  
  27. ERROR:     + CategoryInfo          : InvalidOperation: (System.Manageme...n.PSRemotingJob:PSRemotingJob) [Remove-Job], ArgumentException
  28.  
  29. ERROR:     + FullyQualifiedErrorId : CannotRemoveJob,Microsoft.PowerShell.Commands.RemoveJobCommand
  30.  
  31. 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

User avatar
jvierra
Posts: 11550
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Post by jvierra » Wed Jan 13, 2016 2:48 am

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
Joined: Wed Apr 22, 2015 4:57 am

Re: two background jobs colision

Post by noescape » Wed Jan 13, 2016 3:07 am

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.                
  4.  
  5.                 #get job results
  6.  
  7.                 Param ($Job)
  8.  
  9.                 $script:scanafter = Receive-Job -Name "AppScan_After" -Keep
  10.  
  11.                
  12.  
  13.                 #compare scans and show differences
  14.  
  15.  
  16.  
  17.                 #DO THE WORK HERE              
  18.  
  19.                    
  20.  
  21.                 #format data and output to a file
  22.  
  23.                    
  24.  
  25.                 #DO THE WORK HERE
  26.  
  27.        
  28.  
  29.                
  30.  
  31.                 $exportcompare = show-messagebox -YesNo -M "Do you want to export compare results to Exchange?" -TM -Q -T "Export Compare Results"
  32.  
  33.                 if ($exportcompare -eq "Yes")
  34.  
  35.                 {
  36.  
  37.                     if (!(Test-Path "$exchange\MeteringFiles\$PackageFullName")) { New-Item -ItemType directory -Path "$exchange\MeteringFiles" -Name $PackageFullName }
  38.  
  39.                     Copy-Item $savefilepath "$exchange\MeteringFiles\$PackageFullName" -Force
  40.  
  41.                     $exeprops = foreach ($i in $compareafter.exefiles.fullname) { icm -cn $pc -ScriptBlock { param ($exe) Get-ItemProperty $exe } -ArgumentList $i }
  42.  
  43.                     $exeprops | ConvertTo-Csv | Out-File "$exchange\MeteringFiles\$PackageFullName\$PackageFullName.$PackageVersion EXE.csv" -Force
  44.  
  45.                 }
  46.  
  47.                
  48.  
  49.                 $toolstrip_Progressbar2.Style = 'Blocks'
  50.  
  51.                 $toolstrip_Progressbar2.Value = 0
  52.  
  53.                 $toolstrip_Status.Text = "Idle"
  54.  
  55.                 $timerJobTracker.Start()
Line #16 executes and then the completedscriptblock starts again.

User avatar
jvierra
Posts: 11550
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Post by jvierra » Wed Jan 13, 2016 3:20 am

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
Joined: Wed Apr 22, 2015 4:57 am

Re: two background jobs colision

Post by noescape » Wed Jan 13, 2016 3:32 am

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

User avatar
jvierra
Posts: 11550
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Post by jvierra » Wed Jan 13, 2016 3:41 am

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
Joined: Wed Apr 22, 2015 4:57 am

Re: two background jobs colision

Post by noescape » Wed Jan 13, 2016 3:54 am

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...

User avatar
jvierra
Posts: 11550
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Post by jvierra » Wed Jan 13, 2016 3:58 am

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
Joined: Wed Apr 22, 2015 4:57 am

Re: two background jobs colision

Post by noescape » Wed Jan 13, 2016 4:11 am

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.

User avatar
jvierra
Posts: 11550
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Post by jvierra » Wed Jan 13, 2016 4:24 am

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.  
  3.     param([string[]]$computers)
  4.  
  5.     while(1){
  6.  
  7.         foreach -parallel($c in $computers) {
  8.  
  9.             Test-Connection $c -Count 1
  10.  
  11.             sleep 2
  12.  
  13.         }
  14.  
  15.     }
  16.  
  17. }
  18.  
  19. pingall 'omega', 'w8test', 'alpha'
  20.  
  21. pingall 'omega', 'w8test', 'alpha' -asjob -JobName PingAll

Locked