PowerShell v2.0 equivalent to "get-job | receive-job -AutoRemoveJob -Wait"? - powershell

What is the PowerShell v2.0 equivalent to this v3.0 code snippt:
for($i=0; $i -lt $num_jobs; $i++) {
Write-Host -ForegroundColor Darkgreen "[i] Job" $i "starting..."
Start-Job -ScriptBlock $insert_data -ArgumentList 'host', 'user', 'pass', 'db', $i, $log_out[$i] | Out-Null;
}
get-job | receive-job -AutoRemoveJob -Wait
I tried the following without luck
for($i=0; $i -lt $num_jobs; $i++) {
Write-Host -ForegroundColor Darkgreen "[i] Job" $i "starting..."
Start-Job -ScriptBlock $insert_data -ArgumentList 'host', 'user', 'pass', 'db', $i, $log_out[$i] | Out-Null;
}
get-job | receive-job -Wait
get-job | remove-job
It fails on PowerShell v2.0 with:
Remove-Job : The command cannot remove the job with the 3 session identifier be
cause the job is not finished. To remove the job, first stop the job, or use th
e Force parameter.

The best I can come up with for V2 is:
Get-Job | % {while ($_.HasMoreData) { Receive-Job $_; Sleep 1 }; Remove-Job $_}
Note: I recommend using the Get-Job -Id parameter in the event you have more than one job running. Also, a 1 second sleep might be a bit long, you could tweak that to say 250 millisecs.
For multiple jobs, you could do this:
while ($jobs = Get-Job) { $jobs | %{if ($_.HasMoreData) {Receive-Job $_} else {Remove-Job $_}}; Sleep 1};

try this in two distinct lines:
get-job | wait-job | receive-job
get-job | remove-job

Related

Add a timeout within a foreach loop and write to host - Powershell

I have a small script that runs through all share paths within a csv file and invokes a quick Get-ChildItem to count the number of files within the folder/subfolders etc. This is working but it is taking an age, especially if a folder have more than 500k files.
I would like to add a timeout within the loop, so that it will just write-host and then output into another column 'TIMED OUT' but i can't seem to get it to work.
I have tried a few different ways but somehow my loop breaks and runs the top line in my csv over and over.
This is the code i have so far:
...
#Import middle to filter unique
$sharesMiddle = Import-Csv -Path './middle.csv' | Sort-Object NFTSPath -Unique
$result = foreach ($share in $sharesMiddle) {
#Replace the colon for $ within the path
$nftsPath = join-path \\ $share.AssetName $share.Path.Replace(':', '$')
$path = $share.Path
$count = Invoke-Command -computername $share.AssetName -ScriptBlock { param($path) (Get-ChildItem $path -File -Recurse).count } -Credential $Cred -Verbose -ArgumentList $path
Write-Host $nftsPath : $count
$share | Select-Object *, #{n = "Files"; e = { $count } }
}
$result | Export-CSV '.\newcsvfile.csv' -NoTypeInformation
Any help on this would be super!
Thanks.
I tested the following which should be suitable for you:
# define the time limit in seconds
$TimeoutLimitSeconds = 2
# Define the job, start job
$job = Start-Job -ScriptBlock {420+69 ; start-sleep -Seconds 3}
# Await job
$job | Wait-Job -Timeout ( $TimeoutLimitSeconds ) | Out-Null
# If job doesn't finish before the time limit is reached
if ($job.state -eq 'Running') {
# Job timed out
$job | Stop-Job | Remove-Job
$job = $null
$output = "TIMED OUT"
# If job managed to finalize in time
}Else{
# Finished on time
$job | Stop-Job
$output = Receive-Job $job
$job | Remove-Job
}
# Write the output to host
Write-Host "output is: $output"
output is: TIMED OUT
# define the time limit in seconds
$TimeoutLimitSeconds = 4
# Define the job, start job
$job = Start-Job -ScriptBlock {420+69 ; start-sleep -Seconds 3}
# Await job
$job | Wait-Job -Timeout ( $TimeoutLimitSeconds ) | Out-Null
# If job doesn't finish before the time limit is reached
if ($job.state -eq 'Running') {
# Job timed out
$job | Stop-Job | Remove-Job
$job = $null
$output = "TIMED OUT"
# If job managed to finalize in time
}Else{
# Finished on time
$job | Stop-Job
$output = Receive-Job $job
$job | Remove-Job
}
# Write the output to host
Write-Host "output is: $output"
output is: 489

How to modify the output of PowerShell Jobs if they take too long

I have a relatively long/complex script that blindly runs a batch of commands against several devices (as jobs). Once in awhile, a couple of these jobs continue to run indefinitely. I’ve added a Wait-Job -Timeout command to my script (see below) in order to force-stop jobs that are taking too long to run.
I’d like to change the output for these hung jobs to read “This device is busy or unstable”. How can I do this? I'm guessing I need to add something to the tail-end of the pipeline (in the last line of code below).
$Jobs += Get-Job
$jobs | Wait-Job -Timeout 5 | out-null
Get-Job | ? {$_.State -eq 'Running'} | Stop-Job -PassThru
One way is to iterate over the jobs that are currently running and write your message for each one:
$Jobs += Get-Job
$Jobs | Wait-Job -Timeout 5 | Out-Null
Get-Job | ? { $_.State -eq 'Running' } | Stop-Job -PassThru | % { Write-Host "This device is busy or unstable" }
You can also add info from the jobs that are being stopped, like the job ID for example:
Get-Job | ? { $_.State -eq 'Running' } | Stop-Job -PassThru | % { Write-Host "This device is busy or unstable: $($_.Id)" }
UPDATE: You can use a hashtable to store the job IDs that were "force stopped". Then iterate through the Jobs using Receive-Job to get the output, and check if the job is in the ForceStoppedIds table. If it is write your custom message, otherwise just output the message. Here's a simple test I ran.
Start-Job -ScriptBlock { Write-Output "Starting 1."; Start-Sleep -Seconds 3; Write-Output "1 complete."; } | Out-Null
Start-Job -ScriptBlock { Write-Output "Starting 2."; Start-Sleep -Seconds 60; Write-Output "2 complete."; } | Out-Null
Start-Job -ScriptBlock { Write-Output "Starting 3."; Start-Sleep -Seconds 2; Write-Output "3 complete."; } | Out-Null
$Jobs += Get-Job
$Jobs | Wait-Job -Timeout 5 | Out-Null
$ForceStoppedIds = #{}
$Jobs | ? { $_.State -eq 'Running' } | Stop-Job -PassThru | % { $ForceStoppedIds[$_.Id] = $true }
foreach ($job in $Jobs) {
$jobOutput = Receive-Job $job -Wait
if ($ForceStoppedIds.Contains($job.Id)) {
Write-Host "Custom message about stopped job: $($jobOutput)"
}
else {
Write-Host $jobOutput
}
}
One thing to be cautious of is how jobs output information (ie. Write-Host, Write-Output, return, etc.). If you're not getting the results you expect, double check the job's ScriptBlock to see how the information is being written/returned/etc. I'm sure there are much more elegant ways of doing this, but hopefully this will help.

Powershell Multijob script. Make file open when job completes instead of waiting until all jobs complete

I'm writing a script to ping multiple sites and then open the file to show you the results. I would like the results to open as they finish. Instead it waits until all jobs are finished before opening the files. I've also had issues where it will only open some of the files. Any help would be appreciated
$count = 500
$sites = "www.google.com","8.8.8.8","127.0.0.1"
foreach ($site in $sites)
{
Remove-Item "C:\WFSupport\Self Service Tool\$site.txt"
start-job -Name $site -ScriptBlock { param ($count,$site) ping -n $count $site } -ArgumentList $count, $site
}
While ((Get-Job).State -match 'Running')
{
foreach ($Job in Get-Job | where {$_.HasMoreData})
{
$Jobname = $Job.Name
Receive-Job $Job | Out-File -Encoding ascii -Append "C:\WFSupport\Self Service Tool\$Jobname.txt"
}
Start-Sleep -Seconds 10
}
While ((Get-Job).State -match 'Completed')
{
foreach ($Job in Get-Job | where {$_.HasMoreData})
{
$Jobname = $Job.Name
Receive-Job $Job | Out-File -Encoding ascii -Append "C:\WFSupport\Self Service Tool\$Jobname.txt"
Invoke-Item "C:\WFSupport\Self Service Tool\$Jobname.txt"
}
Get-Job | Remove-Job
}
This is because the While loop checking for 'Running' doesn't stop until all the jobs are stopped running. None of the code below that will run until that while loop finishes.
while ((Get-Job).State -match 'Running') {
foreach ($job in Get-Job | where {$_.HasMoreData}) {
$jobname = $job.name
Receive-Job $Job | Out-File -Encoding ascii -Append "C:\WFSupport\Self Service Tool\$Jobname.txt"
if ($job.State -like 'Completed'){
Invoke-Item "C:\WFSupport\Self Service Tool\$Jobname.txt"
$job | remove-job
}
}
start-sleep -seconds 10
}

Variables in Start-Job do not get evaluated

My Powershell code doesn't evaluate the $agent variable:
foreach ($agent in $agentcomputers) {
Write-Output 'Starting agent on '$agent
# psexc to start the agent
Start-Job -ScriptBlock {& psexec $agent c:\grinder\examples\startAgent.cmd}
}
This link is similar to my problem, except I'm not calling an external Powershell script.
I tried adding that in, using $args[0] for $agent, and adding the -ArgumentList parameters, but that didn't work.
Edits/Replies
$agentcomputers is just a list of computer names - each on its own line:
$agentcomputers = Get-Content c:\grinder-dist\agent-computers.txt
I have also tried this - and $args[0] doesn't evaluate:
Start-Job -ScriptBlock {& psexec $args[0] c:\grinder\examples\startAgent.cmd} -ArgumentList #($agent)
Here are 3 different ways I would do it.
First, all aligned and pretty.
$agents = Get-Content c:\grinder-dist\agent-computers.txt
$jobs = {
Param($agent)
write-host "Starting agent on" $agent
& psexec \\$agent c:\grinder\examples\startAgent.cmd
}
foreach($agent in $agents) {
Start-Job -ScriptBlock $jobs -argumentlist $agent | Out-Null
}
Get-Job | Wait-Job | Receive-Job
Or you could just put it all on one line without creating any variables.
(Get-Content c:\grinder-dist\agent-computers.txt) | %{ Start-Job -ScriptBlock { param($_) write-host "Starting agent on" $_; & psexec \\$_ c:\grinder\examples\startAgent.cmd } -argumentlist $_ | Out-Null }
Get-Job | Wait-Job | Receive-Job
And in this final example, you could manage how many threads are run concurrently by doing it this way.
$MaxThreads = 5
$agents = Get-Content c:\grinder-dist\agent-computers.txt
$jobs = {
Param($agent)
write-host "Starting agent on" $agent
& psexec \\$agent c:\grinder\examples\startAgent.cmd
}
foreach($agent in $agents) {
Start-Job -ScriptBlock $jobs -argumentlist $agent | Out-Null
While($(Get-Job -State 'Running').Count -ge $MaxThreads) {
sleep 10
}
Get-Job | Wait-Job | Receive-Job
}
Here is the solution. As Andy said, I needed to use $args array with the -ArgumentList parameter. This other thread was helpful: Powershell: passing parameters to a job
foreach($agent in $agentcomputers){
$agentslash = "\\"+$agent
$args = ($agentslash,"c:\grinder\examples\startAgent.cmd")
Write-Output 'Starting agent on '$agent
#psexc to start the agent
$ScriptBlock = {& 'psexec' #args }
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $args
}

Resubmit hanging job (start-job)

I am starting several jobs (with Start-Job) and at the end of my script i do a check to see if the jobs have been running more than X seconds. I would then like to take the Running and Failed jobs and restart them until they succeed.
The jobs are named after the server that I'd like to run against (with for example Test-Connection).
My problem is that I can't figure out how to re-submit the jobs!
get-job | where { $_.state -eq "running" } | remove-job -force | start-job -ScriptBlock { echo $_ }
How can I pipe the Name of the failed/hanged job(s) to the new job I am starting?
How can i wait for the remove-job to finish before I continue on Start-Job?
Kind regards:)
One way to restart failed jobs:
Start-Job -Name Foo -ScriptBlock {
$ErrorActionPreference = 'Stop'
Get-Item C:\DoesNotExists
} | Wait-Job > $null
Get-Job | ? { $_.State -eq 'Failed' } | % {
Start-Job -Name $_.Name -ScriptBlock { iex $args[0] } -ArgumentList $_.Command | Wait-Job | Receive-Job
}