Support Forums

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

Postby 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:

$script:pingtimer = New-Object System.Windows.Forms.Timer
$script:pingtimer.Enabled = $true
$pingtimer.Interval = 10000
$pingtimer.Add_Tick({
if ($pc -eq $env:COMPUTERNAME)
{
$panel_online.BackColor = 'GreenYellow'
$labelStatusPCOnOff.Text = "LOCALHOST"
$script:statusTargetPc = $true
}
elseif (!($pc))
{
$panel_online.BackColor = 'Red'
$labelStatusPCOnOff.Text = "NO TARGET"
$script:statusTargetPc = $false
}
else
{
Add-JobTracker -Name "ping" -JobScript { `
param ($pc)
$a = Test-Connection -ComputerName $pc -Quiet -Count 1
return $a
} -CompletedScript { `
$ping = Receive-Job -Name "ping"
if ($ping)
{
$panel_online.BackColor = 'GreenYellow'
$labelStatusPCOnOff.Text = "ONLINE"
$script:statusTargetPc = $true
}
else
{
$panel_online.BackColor = 'Red'
$labelStatusPCOnOff.Text = "OFFLINE"
$script:statusTargetPc = $false
}
} -ArgumentList $pc
}
})
$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:

-CompletedScript {
 
#get job results
Param ($Job)
$script:scanafter = Receive-Job -Job $Job -Keep
 
Add-Log -Color 'Red' -Value "COMPARE SCAN COMPLETE, GENERATING RESULTS..." -NewLine
Show-NotifyIcon -BalloonTipTitle "PCT" -BalloonTipText "COMPARE SCAN COMPLETE" -BalloonTipIcon 'Info' -NotifyIcon $notifyicon_PCT
 
#compare scans and show differences
$script:compareafter = [ordered]@{ }
$compareafter.apps = compare $scanbefore.apps $scanafter.apps -Property ProgramName, DisplayVersion -PassThru | ? { $_.Sideindicator -eq "=>" }
$compareafter.appfolders = compare $scanbefore.appfolders $scanafter.appfolders -Property FullName -PassThru | ? { $_.Sideindicator -eq "=>" }
$compareafter.filteredappfolders = @()
$previous = $null
$compareafter.appfolders.fullname | foreach {
if (($previous -eq $null) -or ($_ -notlike "$previous*"))
{
$previous = $_
$compareafter.filteredappfolders += "$_"
}
}
Clear-Variable previous
$compareafter.exefiles = compare $scanbefore.exefiles $scanafter.exefiles -Property FullName -PassThru | ? { $_.Sideindicator -eq "=>" }
$compareafter.registry = compare $scanbefore.registry $scanafter.registry | ? { $_.Sideindicator -eq "=>" }
$compareafter.drivers = compare $scanbefore.drivers $scanafter.drivers -Property OriginalFileName, Version -PassThru | ? { $_.Sideindicator -eq "=>" }
$compareafter.services = compare $scanbefore.services $scanafter.services -Property Name, DisplayName -PassThru | ? { $_.Sideindicator -eq "=>" }
$compareafter.tasks = compare $scanbefore.tasks $scanafter.tasks -Property TaskName, TaskPath, Description | ? { $_.Sideindicator -eq "=>" }
$compareafter.envvar = compare $scanbefore.envvar $scanafter.envvar -Property Name, Value | ? { $_.Sideindicator -eq "=>" }
$compareafter.startmenu = compare $scanbefore.startmenu $scanafter.startmenu -Property FullName | ? { $_.Sideindicator -eq "=>" }
 
<#
#save dialog
$SaveCompareDialog = New-Object System.Windows.Forms.SaveFileDialog
$SaveCompareDialog.Title = "Save Compare Results"
$SaveCompareDialog.initialDirectory = "$env:LOCALAPPDATA\PCT"
$SaveCompareDialog.FileName = $PackageFullName + "_AppScan"
$SaveCompareDialog.filter = “Text files (*.txt)|*.txt”
$DialogResult = $SaveCompareDialog.ShowDialog()
if ($DialogResult -eq [System.Windows.Forms.DialogResult]::OK)
{ }
#>

 
#format data and output to a file
 
$savefilepath = "$env:LOCALAPPDATA\PCT\AppScan.txt" #$SaveCompareDialog.FileName
Remove-Item -Path $savefilepath -Force -ea 'SilentlyContinue'
New-Item $savefilepath -ItemType File -Force | Add-Content -value "METERING DIFFERENCES:`r`r" -Encoding Unicode
$compareafter.apps | select ProgramName, DisplayVersion, Publisher, InstallDate, UninstallString | ft | Out-String | Out-File $savefilepath -Append -Force
#$compareafter.appfolders | select @{ Name = "AppFolders"; Expression = "FullName" } | Out-String | Out-File $SaveCompareDialog.filename -Append -Force
if ($compareafter.filteredappfolders) { Add-Content $savefilepath -Value "Application Folders:`n--------------------`r" }
$compareafter.filteredappfolders | Out-File $savefilepath -Append -Force
$compareafter.exefiles | select @{ Name = "ExeFiles"; Expression = "FullName" } | Out-String | Out-File $savefilepath -Append -Force
$compareafter.registry | select @{ Name = "Registry"; Expression = "InputObject" } | Out-String | Out-File $savefilepath -Append -Force
$compareafter.drivers | select @{ Name = "Driver"; Expression = "OriginalFileName" }, @{ Name = "Description"; Expression = "ClassDescription" }, Version, ProviderName | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
$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
$compareafter.tasks | select TaskName, TaskPath, Description | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
$compareafter.envvar | select @{ Name = "Environment Variables"; Expression = "Name" }, Value | ft -Wrap | Out-String | Out-File $savefilepath -Append -Force
$compareafter.startmenu | select @{ Name = "Start Menu"; Expression = "FullName" } | Out-String | Out-File $savefilepath -Append -Force
 
ii $savefilepath
 
$exportcompare = show-messagebox -YesNo -M "Do you want to export compare results to Exchange?" -TM -Q -T "Export Compare Results"
if ($exportcompare -eq "Yes")
{
if (!(Test-Path "$exchange\MeteringFiles\$PackageFullName")) { New-Item -ItemType directory -Path "$exchange\MeteringFiles" -Name $PackageFullName }
Copy-Item $savefilepath "$exchange\MeteringFiles\$PackageFullName" -Force
$exeprops = foreach ($i in $compareafter.exefiles.fullname) { icm -cn $pc -ScriptBlock { param ($exe) Get-ItemProperty $exe } -ArgumentList $i }
$exeprops | ConvertTo-Csv | Out-File "$exchange\MeteringFiles\$PackageFullName\$PackageFullName.$PackageVersion EXE.csv" -Force
}
 
 
$toolstrip_Progressbar2.Style = 'Blocks'
$toolstrip_Progressbar2.Value = 0
$toolstrip_Status.Text = "Idle"
 
} -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:

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.
ERROR: Název parametru: index"

Globals.ps1 (448): ERROR: At Line: 448 char: 6
ERROR: + $JobTrackerList.RemoveAt($index)
ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ERROR: + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
ERROR: + FullyQualifiedErrorId : ArgumentOutOfRangeException
ERROR:
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
ERROR: b.
Globals.ps1 (449): ERROR: At Line: 449 char: 6
ERROR: + Remove-Job -Job $psObject.Job
ERROR: + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ERROR: + CategoryInfo : InvalidOperation: (System.Manageme...n.PSRemotingJob:PSRemotingJob) [Remove-Job], ArgumentException
ERROR: + FullyQualifiedErrorId : CannotRemoveJob,Microsoft.PowerShell.Commands.RemoveJobCommand
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: 9696
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Postby 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

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

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

-CompletedScript {
 
#get job results
Param ($Job)
$script:scanafter = Receive-Job -Name "AppScan_After" -Keep
 
#compare scans and show differences
 
#DO THE WORK HERE
 
#format data and output to a file
 
#DO THE WORK HERE
 
 
$exportcompare = show-messagebox -YesNo -M "Do you want to export compare results to Exchange?" -TM -Q -T "Export Compare Results"
if ($exportcompare -eq "Yes")
{
if (!(Test-Path "$exchange\MeteringFiles\$PackageFullName")) { New-Item -ItemType directory -Path "$exchange\MeteringFiles" -Name $PackageFullName }
Copy-Item $savefilepath "$exchange\MeteringFiles\$PackageFullName" -Force
$exeprops = foreach ($i in $compareafter.exefiles.fullname) { icm -cn $pc -ScriptBlock { param ($exe) Get-ItemProperty $exe } -ArgumentList $i }
$exeprops | ConvertTo-Csv | Out-File "$exchange\MeteringFiles\$PackageFullName\$PackageFullName.$PackageVersion EXE.csv" -Force
}
 
$toolstrip_Progressbar2.Style = 'Blocks'
$toolstrip_Progressbar2.Value = 0
$toolstrip_Status.Text = "Idle"
$timerJobTracker.Start()


Line #16 executes and then the completedscriptblock starts again.
User avatar
jvierra
Posts: 9696
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Postby 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

Postby 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: 9696
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Postby 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

Postby 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: 9696
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Postby 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

Postby 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: 9696
Joined: Tue May 22, 2007 9:57 am
Contact:

Re: two background jobs colision

Postby 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.
workflow pingall  {
param([string[]]$computers)
while(1){
foreach -parallel($c in $computers) {
Test-Connection $c -Count 1
sleep 2
}
}
}
pingall 'omega', 'w8test', 'alpha'
pingall 'omega', 'w8test', 'alpha' -asjob -JobName PingAll

Return to “Windows PowerShell”

Who is online

Users browsing this forum: No registered users and 5 guests