How to receive progress data from a running job? - powershell

I have a need to run a bunch of tasks in the background in parallel.
These jobs can run for hours .
But I need all the jobs to give progress while they are running , that the host can read and give a summary of all the jobs at some time interval . For example last task performed and percent completed.
I did a bunch of searching but I didn't find anything on how the host and all the jobs can communicate at run time to achieve my goal.
How to do this with jobs , or are their other way to run things in parallel that facilitate this ?

There is a sample project here: https://key2consulting.com/powershell-how-to-display-job-progress/
It uses Write-Progress in the job to output the status and a loop that monitors the job and displays a progress bar.
Your jobs would need to be designed to output % complete. I.e. Your job would need to know when it is 20% complete.

Here's another solution that does not use progress bars. Your jobs still need to report their progress with periodic writes. If the jobs return data, the data should be stored to a file or other destination. This code reads and discards the returned data as the returned text is used in the update messages.
A better solution could let each job write the status messages to a SQL table and a simple looping screen reads and displays that data. The job's default response is still ready for Receive-Job
For demo purposes only...
#load your jobs here... Note that they periodically report their status.
Start-Job -ScriptBlock {$i = 0; 1..10 | foreach { Start-Sleep -Seconds 1; $i++; "LoopA $($i*10)% complete" }; Start-Sleep -Seconds 1; }
Start-Job -ScriptBlock {$i = 0; 1..10 | foreach { Start-Sleep -Seconds 2; $i++; "LoopB $($i*10)% complete" }; Start-Sleep -Seconds 1; }
Start-Job -ScriptBlock {$i = 0; 1..10 | foreach { Start-Sleep -Seconds 3; $i++; "LoopC $($i*10)% complete" }; Start-Sleep -Seconds 1; }
$joblist = #{} #empty hash table
# get initial list of jobs...
Get-Job | foreach { $joblist.Add( $_.Name, #{ "State"=$_.State;
"HasMoreData"=$_.HasMoreData;
"LastMsg"="" } ) }
# loop and report status
do
{
$done = $true
Get-Job | foreach {
Start-Sleep -Milliseconds 200; # change for you application
# should be more frequent than job messages
# collect data from job
$joblist[$_.Name]["State"] = $_.State;
$joblist[$_.Name]["HasMoreData"] = $_.HasMoreData;
if ($_.State -eq "Running") {
if ($_.HasMoreData) {
# get the message from the job
$txt = (Receive-Job -Name $_.Name)
if ($txt.Count -gt 0) {
$joblist[$_.Name]["LastMsg"] = $txt;
}
}
$done = $false #still have Running jobs
}
}
# report status
cls
$joblist.GetEnumerator() | select Name, {$_.value.State}, {$_.value.HasMoreData}, {$_.value.LastMsg} | ft
} until ($done)
# final status...
cls
$joblist.GetEnumerator() | select Name, {$_.value.State}, {$_.value.HasMoreData}, {$_.value.LastMsg} | ft
# demo clean up.
#Get-Job | Stop-Job
#Get-Job | Remove-Job

Related

To check a windows service state as running by running a loop upto 3 increments

Hi I have a scenario where I need to to check a windows service state as running by running a loop upto 3 increments and after every increment wait for 10 sec before incrementing the loop.How to achieve this?
Perhaps using a simple for() loop like this:
$serviceName = 'TheServiceName'
$maxTries = 3
for ($i = 0; $i -lt $maxTries; $i++) {
$status = (Get-Service -Name $serviceName).Status
Write-Host "Service '$serviceName' status is $status"
if ($status -eq 'Running') {
# exit the loop
break
}
# service is not running, so sleep for 10 seconds and try again
Start-Sleep -Seconds 10
}
Make sure you test on the service Name or DisplayName property, they differ..

How to convert infinity while loop to a pipeline statement

I'm trying to use PowerShell pipeline for some recurring tasks and checks, like
perform certain checks X times or skip forward after the response in pipeline will have different state.
The simplest script I can write to do such checks is something like this:
do {
$result=Update-ACMEIdentifier dns1 -ChallengeType dns-01
if($result.Status -ne 'pending')
{
"Hurray"
break
}
"Still pending"
Start-Sleep -s 3
} while ($true)
The question is - how can I write this script as a single pipeline.
It looks like the only thing I need is infinity pipeline to start with:
1..Infinity |
%{ Start-Sleep -Seconds 1 | Out-Null; $_ } |
%{Update-ACMEIdentifier dns1 -ChallengeType dns-01 } |
Select -ExpandProperty Status | ?{$_ -eq 'pending'} |
#some code here to stop infinity producer or stop the pipeline
So is there any simple one-liner, which allows me to put infinity object producer on one side of the pipeline?
Good example of such object may be a tick generator that generates current timestamp into pipeline every 13 seconds
#PetSerAl gave the crucial pointer in a comment on the question: A script block containing an infinite loop, invoked with the call operator (&), creates an infinite source of objects that can be sent through a pipeline:
& { while ($true) { ... } }
A later pipeline segment can then stop the pipeline on demand.
Note:
As of PS v5, only Select-Object is capable of directly stopping a pipeline.
An imperfect generic pipeline-stopping function can be found in this answer of mine.
Using break to stop the pipeline is tricky, because it doesn't just stop the pipeline, but breaks out of any enclosing loop - safe use requires wrapping the pipeline in a dummy loop.
Alternatively, a Boolean variable can be used to terminate the infinite producer.
Here are examples demonstrating each approach:
A working example with Select-Object -First:
& { while ($true) { Get-Date; Start-Sleep 1 } } | Select-Object -First 5
This executes Get-Date every second indefinitely, but is stopped by Select-Object after 5 iterations.
An equivalent example with break and a dummy loop:
do {
& { while ($true) { Get-Date; Start-Sleep 1 } } |
% { $i = 0 } { $_; if (++$i -eq 5) { break } } # `break` stops the pipeline and
# breaks out of the dummy loop
} while ($false)
An equivalent example with a Boolean variable that terminates the infinite producer:
& { while (-not $done) { Get-Date; Start-Sleep 1 } } |
% { $done = $false; $i = 0 } { $_; if (++$i -eq 5) { $done = $true } }
Note how even though $done is only initialized in the 2nd pipeline segment - namely in the ForEach-Object (%) cmdlet's (implicit) -Begin block - that initialization still happens before the 1st pipeline segment - the infinite producer - starts executing.Thanks again, #PetSerAl.
Not sure why you'd want to use a pipeline over a loop in this scenario, but it is possible by using a bit of C#; e.g.
$Source = #"
using System.Collections.Generic;
public static class Counter
{
public static bool Running = false;
public static IEnumerable<long> Run()
{
Running = true;
while(Running)
{
for (long l = 0; l <= long.MaxValue; l++)
{
yield return l;
if (!Running) {
break;
}
}
}
}
}
"#
Add-Type -TypeDefinition $Source -Language CSharp
[Counter]::Run() | %{
start-sleep -seconds 1
$_
} | %{
"Hello $_"
if ($_ -eq 12) {
[Counter]::Running = $false;
}
}
NB: Because the numbers are generated in parallel with the pipeline execution it's possible that the generator may create a backlog of numbers before it's stopped. In my testing that didn't happen; but I believe that scenario is possible.
You'll also notice that I've stuck a for loop inside the while loop; that's to ensure that the values produced are valid; i.e. so I don't overrun the max value for the data type.
Update
Per #PetSerAl's comment above, here's an adapted version in pure PowerShell:
$run=$true; &{for($i=0;$run;$i++){$i}} | %{ #infinite loop outputting to pipeline demo
"hello $_";
if($_ -eq 10){"stop";$run=$false <# stopping condition demo #>}
}

Why does a simple Powershell Interrupter with Start-Job, Wait-Job not return results?

We have a jenkins git job that hangs on remote checking every so often. So I want to abort the checking with a timeout.
I understand that wait-job will return null if the timer had to stop the background job being awaited. So when a code block is long-running, it should return a null. That works for me in the command line.
However, when a job is short, the below code still getting null when I run the function in the ISE. When I debug it, it works fine. Help?
thank you!
Anne
function Test-TimeoutJob {
<#
.EXAMPLE
BUG:
> Test-TimeoutJob -theCodeBlock {write-output 'hi'}
Test-TimeoutJob : Type of theCodeBlock= scriptblock ; Text= write-output 'hi' .
Test-TimeoutJob : Return code from starting the job = , True .
Id Name PSJobTypeName State HasMoreData Location
-- ---- ------------- ----- ----------- --------
16 Job16 BackgroundJob Running True localhost
Test-TimeoutJob : Got null output of wait-job on id# 16 .
End : Now= 01/25/2016 17:12:02
#>
param (
$theCodeBlock , # infinite; $i=0; do {$i++; echo $i; } while ($true)
$theTimeoutSeconds = 1 # Beware; default=-1sec means wait infinitely #3
)
$thisFcn = 'Test-TimeoutJob'
# If null input, then set it.
if ( ! $theCodeBlock ) {
$theCodeBlock = {
#Show-TimeNow -theMessage "CodeBlock"
#Start-Sleep -Seconds 10
# your commands here, e.g.
Get-ChildItem *.cs | select name
}
}
$theCodeType = $theCodeBlock.GetType()
$theCodeStr = $theCodeBlock.ToString()
Write-Host "$thisFcn : Type of theCodeBlock= $theCodeType ; Text= $theCodeStr ."
$theJob = Start-Job -ScriptBlock $theCodeBlock
write-host "$thisFcn : Return code from starting the job = $LASTEXITCODE , $? ."
$jobid = $theJob.Id
Get-Job $jobid
$answaitobj = Wait-Job $theJob.Id -Timeout $theTimeoutSeconds
if ( $answaitobj -eq $null ) {
Write-Host "$thisFcn : Got null output of wait-job on id# $jobid . "
}
elseif ( $answaitobj ) {
$jobStatus = $theJob.State
$anstype = $answaitobj.GetType()
Write-Host "$thisFcn : the answer, supposed to be job, has type= $anstype ; status= $jobStatus ."
Stop-Job $theJob
$ansId = $theJob.Id
Write-Host "Job $ansId has been ended, with status= $jobStatus ; Thus it has finished or been stopped due to timeout."
# For our purposes of abending a script, we do not need to
# either get its data, which is null, or cleanup, which automatically occurs
# once jenkins finishes the call to posh.
# Get first element of output iterable
#$ans = Receive-Job -Job $theJob -Keep
#Remove-Job -force $theJob
}
Show-TimeNow -theMessage 'End'
}
function Show-TimeNow {
param (
$theMessage = 'Hello from Show-TimeNow'
)
$now = Get-Date
Write-Output "$theMessage : Now= $now"
}
The documentation never promises that Wait-Job would return anything if the timeout expires, so why would you expect it to?
-Timeout<Int32>
Determines the maximum wait time for each background job, in seconds. The default, -1, waits until the job completes, no matter how long it runs. The timing starts when you submit the Wait-Job command, not the Start-Job command.
If this time is exceeded, the wait ends and the command prompt returns, even if the job is still running. No error message is displayed.
Check the state of the job after Wait-Job returned to determine if the job is completed or not:
if ($job.State -eq 'Completed') {
Write-Host 'Finished.'
} else {
Write-Host 'Timeout expired. Stopping job.'
Stop-Job -Id $job.Id
}
The answer turned out that, on my system, one second was too short of a timeout, even for the simplest program. Setting the timeout to a longer number of seconds returned the expected results.

Memory leak in Powershell 3.0 script to monitor log file

I've written a script in Powershell 3.0 to monitor a log file for specific errors. The script starts a background process, which monitors the file. When anything gets written to the file, the background process simply passes it to the foreground process, if it matches the proper format (a datestamped line). The foreground process then counts the number of errors.
Everything works correctly with no errors. The issue is that, as the source logfile grows in size, the memory consumed by Powershell increases dramatically. These logs are capped at ~24M before they are rotated, which amounts to ~250K lines. In my tests, by the time the log size reaches ~80K lines or so, the monitor process is consuming 250M RAM (foreground and background processes combined. They're consuming ~70M combined when they first start. This type of growth is unacceptable in our environment. What can I do to decrease this?
Here's the script:
# Constants.
$F_IN = "C:\Temp\test.log"
$RE = "^\d+-\d+-\d+ \d+:\d+:\d+,.+ERROR.+Foo$"
$MAX_RESTARTS = 3 # Max restarts for failed background job.
$SLEEP_DELAY = 60 # In seconds.
# Background job.
$SCRIPT_BLOCK = { param($f, $r)
Get-Content -Path $f -Tail 0 -Wait -EA SilentlyContinue `
| Where { $_ -match $r }
}
function Start-FileMonitor {
Param([parameter(Mandatory=$true,Position=0)][alias("f")]
[String]$file,
[parameter(Mandatory=$true,Position=1)][alias("b")]
[ScriptBlock]$SCRIPT_BLOCK,
[parameter(Mandatory=$true,Position=2)][alias("re","r")]
[String]$regex)
$j = Start-Job -ScriptBlock $SCRIPT_BLOCK -Arg $file,$regex
return $j
}
function main {
# Tail log file in the background, return any errors.
$job = Start-FileMonitor -b $SCRIPT_BLOCK -f $F_IN -r $RE
$restarts = 0 # Current number of restarts.
# Poll background $job every $SLEEP_DELAY seconds.
While ($true) {
$a = (Receive-Job $job | Measure-Object)
If ($job.JobStateInfo.State -eq "Running") {
$restarts = 0
If ($a.Count -gt 0) {
$t0 = $a.Count
Write-Host "Error Count: ${t0}"
}
}
Else {
If ($restarts -lt $MAX_RESTARTS) {
$job = Start-FileMonitor -b $SCRIPT_BLOCK -f $F_IN -r $RE
$restarts++
Write-Host "Background job not running. Attempted restart ${restarts}."
}
Else {
Write-Host "`$MAX_RESTARTS (${MAX_RESTARTS}) exceeded. Exiting."
Break
}
}
# Sleep for $SLEEP_DELAY.
Start-Sleep -Seconds $SLEEP_DELAY
}
Write-Host "Done."
}
# Execute script.
main
...and here's the sample data:
2015-11-19 00:00:00, WARN Foo
2015-11-19 00:00:00, ERROR Foo
In order to replicate this issue:
Paste the sample data lines into the file C:\Temp\test.log. Save.
Start the monitoring script.
Paste additional sample data lines into the log and save. Wait for the Error Count: line to confirm that everything is working correctly.
Continue to paste additional lines and watch the memory consumption for powershell.exe in Task Manager. Note how much it increases at 400 lines...800 lines...8,000 lines...80,000 lines...

Limited powershell start-jobs

I curious if you can answer this or point me in the right direction.
I've written a script that tests/monitors urls. I'm not posting the code ( unless you want me to ) because there is no error in the code. It works great. I can even scriptblock run it as part of start job. The issue I have seems to be that I can not run more than 3 jobs at time.. or they hang. I'm not sure why this is. I can run it for a total of 15 urls throttled to 3 and it's great. If I try to run it on 15 urls with 4 as my run limit, they will hang.. and I can kill one at a time.. until only 3 remain and those will finish. So it seems that I can only start a total of 3 powershell instances or they hang. Anyone explain why this is? All my searches lead me to pages that show how to throttle and it's not really my issue.
Watching the processes, each consumes about 25MBs of memory and sits there idle... If I kill one the other 3 will start using cpu and process go up to maybe 30MBs of memory and terminate completed. System has 8GBs of memory & a quad cord I5-2400 CPU # 3.10GHz. As requested...
Param(
$file
)
$testscript =
{
Param(
[string]$url,
#[ValidateSet('InternetExplorer','Chrome','Firefox','Safari','Opera', IgnoreCase = $true)]
[string]$browser="InternetExplorer",
[string]$teststring="Solution Center",
[int]$timeout=20,
[int]$retry
)
$i=0
do {
$userAgent = [Microsoft.PowerShell.Commands.PSUserAgent]::$browser
$data = Invoke-WebRequest $url -UserAgent $userAgent -TimeoutSec $timeout
$data.Content
$findit = $data.Content.Contains($teststring)
$i++
If ($findit){
break
}
}
while ($i -lt $retry)
if(!$findit) {
Echo "opcmsg a=PSURLCheck o=NHTSA msg_t='$teststring was not found on $url or $url failed to load'"
}
}
$urls = Import-Csv $file | % {
Start-Job -ScriptBlock $testscript -ArgumentList $_.url, $_.browser, $_.teststring, $_.retry
}
While (#(Get-Job | Where { $_.State -eq "Running" }).Count -ne 0)
{ Write-Host "Processing URLs..."
Get-Job
Start-Sleep -Seconds 5
}
$Data = ForEach ($Job in (Get-Job)) {
Receive-Job $Job
Remove-Job $Job
}
$data | select *
So I've used new system.net.webclient and I've even tried doing this with [System.Collections.Queue]... but all three methods use Jobs... so it appears.. I can not run more than three start jobs at any one time.
Are you sure your code is fine? If you're calling separate powershell sessions multiple times memory can be consumed very quickly. Check process monitor for high CPU or memory usage and ensure your blocks are terminating. Or post the code.