4
\$\begingroup\$

This code allows you monitor the progress of the Powershell Jobs you just started. One of the key features is the ability to stop the other Jobs when 1 is failed.

For example, if you are running some Jobs with a runtime of around 60 seconds, the results needs to be combined to be of any use, Job 2 failed at 20 seconds. With this command your script will return in around 21 seconds instead of 60.

I wrote this code in response to a question on Stack Overflow. It did resemble some of my existing code. The code is altered to be more complete and generic.

To point something out that I would like some recommendations on is a clean method for updating ProgressBars in nested functions. Right now I am using the 'AllScope' option on a variable and this does not look as neat as I hoped it to be.

This is also available on GitHub.

Function Watch-Job {
    [CmdletBinding()]
    [OutputType([System.Management.Automation.Job[]])]
    Param(

        # Powershell Job(s) to Watch, 
        # normally the result of the Start/Get-Job command or -AsJob parameter. 
        [Parameter(
            Mandatory=$true,
            ValueFromPipeline=$true
        )]
        [Alias("Jobs")] 
        [ValidateNotNull()]
        [System.Management.Automation.Job[]]$Job,

        # When Multiple Jobs are provided in the $Job Parameter and a single Job fails
        # This wil wil stop the remaining Jobs that are still running.
        [Switch]$StopJobOnError,

        # This wil remove the Job when it is finished (Failed, Stopped, Completed, ...)
        [Switch]$RemoveJob
    )

    # Setup ProgessBar
    if (-Not $JobStartCount) {        
        #Create variable for the current scope (function) that is also avaiable to any child scopes (functions).
        $VariableJobStartCount = @{
            Name = "JobStartCount"
            Option = "AllScope"
            Value = ($Job | Measure-Object | Select-Object -ExpandProperty Count)        
        }; New-Variable @VariableJobStartCount                
        Write-Progress -PercentComplete 0 -Activity "Watch-Job" -Status "$JobStartCount/$JobStartCount Jobs Left" #-CurrentOperation "Waiting for a Job to Finisch"
    }

    # Wait for a single job to finisch (Failed, Stopped, Completed, ...)
    $Job | Wait-Job -Any | ForEach-Object {

        if ($_.State -eq 'Failed') {
            # Show a warning the current 'Failed' Job
            Write-Warning "Job $($_.Name) returned with state $($_.State)"

            # Stop all other Jobs.
            if ($StopJobOnError) { 
                Write-Warning "A Job has failed, forcibly stopping other Jobs"
                $Job | Stop-Job 
            }
        }

        Else {
            # Show information about the current Job
            Write-Verbose "Job $($_.Name) retuned with state $($_.State)"
        }

        # Remove the JobData for the Current Job
        if ($RemoveJob) { $_ | Remove-Job}

        # Wait for further Jobs to complete        
        $RemainingJobs = $Job | Where-Object Id -ne $_.Id
        If ($RemainingJobs) {            
            Write-Progress -PercentComplete (100-($RemainingJobs.Count)/$JobStartCount*100) -Activity "Watch-Job" -Status "$($RemainingJobs.Count)/$JobStartCount Jobs Left"
            Watch-Job -Job $RemainingJobs -StopJobOnError:$StopJobOnError -RemoveJob:$RemoveJob | Out-Null
        }

        Else {
            Write-Progress -Activity "Watch-Job" -Completed
        }
    }
    Return $Job
}

Code for testing this function:

# ------------------------------------------------
#                    Example Script
# ------------------------------------------------
Clear-Host

$ErrorActionPreference = "Stop" # Recommended, if something happens, TERMINATE!
$VerbosePreference = "Continue" # Only for this example, use -Verbose!

# DateTime this script started, used to calculate script run time
$ScriptStartDateTime = Get-Date 
Write-Verbose "Script Started at $ScriptStartDateTime"

# A place to store the Jobs
$Jobs = @()

# Create a few (15) dummy Jobs
(1..15) | ForEach-Object {
    # Create a Job that should fail in between the other Jobs
    If ($_ -eq 10) {        
        $Jobs += Start-Job -Name "Job$($_)" -ScriptBlock {
            Start-Sleep -Seconds 7
            throw "build failed somehow"
            Start-Sleep -Seconds 13
        }        
    }

    Else {
        # Create an example job that has a long running time
        $Jobs += Start-Job -Name "Job$($_)" -ArgumentList @($_) -ScriptBlock {
            param($i)
            Start-Sleep -Seconds ($i*2)
        }
    }
}

# Wait for Jobs to complete.
Watch-Job -Job $Jobs -StopJobOnError -RemoveJob -Verbose

# Output Script Runtime to the Verbose Output.
$RunTime = (Get-date) - $ScriptStartDateTime
Write-Verbose "Script Stopped at $(Get-Date)"
Write-Verbose "Total RunTime in Secconds: $($RunTime.TotalSeconds)"

Example Output:

VERBOSE: Script Started at 07/05/2018 22:21:45
VERBOSE: Job Job1 retuned with state Completed
VERBOSE: Job Job2 retuned with state Completed
VERBOSE: Job Job3 retuned with state Completed
WARNING: Job Job10 returned with state Failed
WARNING: A Job has failed, forcibly stopping other Jobs
VERBOSE: Job Job4 retuned with state Stopped
VERBOSE: Job Job5 retuned with state Stopped
VERBOSE: Job Job6 retuned with state Stopped
VERBOSE: Job Job7 retuned with state Stopped
VERBOSE: Job Job8 retuned with state Stopped
VERBOSE: Job Job9 retuned with state Stopped
VERBOSE: Job Job11 retuned with state Stopped
VERBOSE: Job Job12 retuned with state Stopped
VERBOSE: Job Job13 retuned with state Stopped
VERBOSE: Job Job14 retuned with state Stopped
VERBOSE: Job Job15 retuned with state Stopped

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
753    Job1            BackgroundJob   Completed     False           localhost            ...                      
755    Job2            BackgroundJob   Completed     False           localhost            ...                      
757    Job3            BackgroundJob   Completed     False           localhost            ...                      
759    Job4            BackgroundJob   Stopped       False           localhost            ...                      
761    Job5            BackgroundJob   Stopped       False           localhost            ...                      
763    Job6            BackgroundJob   Stopped       False           localhost            ...                      
765    Job7            BackgroundJob   Stopped       False           localhost            ...                      
767    Job8            BackgroundJob   Stopped       False           localhost            ...                      
769    Job9            BackgroundJob   Stopped       False           localhost            ...                      
771    Job10           BackgroundJob   Failed        False           localhost            ...                      
773    Job11           BackgroundJob   Stopped       False           localhost            ...                      
775    Job12           BackgroundJob   Stopped       False           localhost            ...                      
777    Job13           BackgroundJob   Stopped       False           localhost            ...                      
779    Job14           BackgroundJob   Stopped       False           localhost            ...                      
781    Job15           BackgroundJob   Stopped       False           localhost            ...                      
VERBOSE: Script Stopped at 07/05/2018 22:21:54
VERBOSE: Total RunTime in Secconds: 8.2599559
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.