Powershell execute script block on exception - powershell

Im looking for a way in powershell to run a script block, or some code after an exception or a terminating write-error has occurred.
Basically, if I hit a throw, or a write-error, then I want the highest scope (main) to close out all PSSessions and do a get-job | remove-job before we stop execution.
Im not sure exactly if this is possible if I have set $ErrorActionPreference = "Stop"
This is what I came up with while waiting on an answer.. but Ill keep this question up incase someone has a better solution:
#Wait on feedback from children
try{
While (Get-Job -State "Running")
{
Get-Job | Receive-Job
Start-Sleep 2
}
Get-Job | Receive-Job
} finally {
Get-PSSession | Remove-PSSession
Get-Job | Remove-Job
}
thanks!

Jobs will not send an error to the host by default, you'll need to throw an error manually.
#Wait on feedback from children
try{
While (Get-Job -State "Running")
{
if (Get-Job -State 'Failed'){ throw 'Job failed.' }
Get-Job | Receive-Job
Start-Sleep 2
}
Get-Job | Receive-Job
} finally {
Get-PSSession | Remove-PSSession
Get-Job | Remove-Job
}
You may get more information about the failure by checking the JobState property on the job object.

Related

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.

How to know that the script should exit when exit comes within a job?

I have a script which starts a job Start-Job which monitors a process. Whenever this process dies, the script will automatically restart the process, and then restart the script.
If the script can't restart the process it will send an e-mail and should exit PowerShell completely, to avoid the restart of the script. I did this by just exiting inside the job, but that doesn't seem to work.
How can I tell my script to not restart, if the process could not be restarted?
Here's my code:
$sb = {
while ((Get-Process LogikWebserver).Responding) { sleep -m 50 }
if (!(Get-Process LogikWebserver).Responding) {
#restart process
try {
Start-Process "$processpath\LogikWebserver.exe" -EA Stop
sleep -s 2
if (Get-Process LogikWebserver -EA Stop) {
Send-MailMessage
}
} catch {
Send-MailMessage
<# With this exit I want to tell the script to exit completely #>
exit
}
}
}
# start and get job
Start-Job -Name LogikWebserverWatch -ScriptBlock $sb
Wait-Job -Name LogikWebserverWatch
Get-Job -Name LogikWebserverWatch | Remove-Job -Force
# restart script
$skript = "{0}\{1}" -f $PSScriptRoot, "Watch-LogikWebserver.ps1"
& $skript
Don't catch exceptions inside the scriptblock. Receive the job output and catch exceptions there. Also, use an infinite loop for re-running the job rather than re-running the script.
$sb = {
while ((Get-Process LogikWebserver).Responding) {
Start-Sleep -Milliseconds 50
}
# restart process (at this point .Responding was already false, thus no
# point in checking again)
Start-Process "$processpath\LogikWebserver.exe" -ErrorAction Stop
Start-Sleep -Seconds 2
if (Get-Process LogikWebserver -ErrorAction Stop) {
Send-MailMessage
}
}
while ($true) {
# start and get job
try {
Start-Job -Name LogikWebserverWatch -ScriptBlock $sb |
Wait-Job |
Receive-Job -ErrorAction Stop
} catch {
Send-MailMessage
exit
} finally {
Get-Job -Name LogikWebserverWatch | Remove-Job -Force
}
}

Capture Verbose Stream from Job

I am trying to be a good a powerscript user and use Write-Verbose as per best practices, but I have no way to get the Verbose stream from a running Job.
$Job = Start-Job -Name "Scanning Work Item" -ScriptBlock{
Write-Verbose "Write-Verbose"
Write-Host "Write-Host"
}
while ($Job.HasMoreData -or $Job.State -eq "Running") {
Receive-Job -Job $Job -Verbose
Start-Sleep -Seconds 1
}
The output for this is
Write-Host
Please only answer with tested code as I have spent hours trying various permutations of Powershell script.
First of all, you're not getting any verbose ouput because you haven't changed the default VerbosePreference for the session.
As for reading Verbose ouput while the job is running, you can read each of the output stream buffers from the associated child job individually, without doing a Receive-job, and without affecting later output when you do the Receive-Job,
$Job = Start-Job -Name "Scanning Work Item" -ScriptBlock{
$VerbosePreference = 'Continue'
Write-Verbose "Write-Verbose"
Write-Host "Write-Host"
Start-Sleep -Seconds 10
}
Start-sleep -Seconds 2
$Verbose = $Job.ChildJobs[0].verbose.readall()
$verbose
while ($Job.HasMoreData -or $Job.State -eq "Running") {
Receive-Job -Job $Job
Start-Sleep -Seconds 1
}
Write-Verbose
VERBOSE: Write-Verbose
Write-Host

PowerShell Receive-Job does not work in script

When I execute $job = Start-Job { dir } an then Receive-Job $job in PowerShell console I get normal output. But when I make similar .ps1 script and run it there is no output. Other commands work correctly. How do I receive job result in scripts?
Just try wait job is completed before receive.
$job = Start-Job { dir }
Wait-Job $job | out-null
receive-job $job
other way
$job = Start-Job { dir }
while ($job.state -ne "Completed") {}
receive-job $job
You need to wait for the job to finish:
Start-Job { dir } | Wait-Job | Receive-Job

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
}