Don't let your entire workflow be hung up because one of the scripts is stuck on some task. There's a way around that!

Have you ever written an excellent PowerShell script only to find that it just hangs on some task without any indication why? It's especially frustrating if the script is being kicked off by an unattended process. The entire workflow may be hung up just because one of the scripts is hung on some task. Fortunately, there's a way around this. To prevent a script from hanging indefinitely, we can add a timeout feature to it. Adding a timeout feature to a script forces the script to either exit or continue if a certain task is taking too long.

To add a timeout feature to a PowerShell script requires a few different tasks:

  • Start a timer
  • Invoke some piece of code
  • Check the code's status every so often
  • If timeout is exceeded, do something
  • If timeout is not exceeded, continue with the script
  • Stop the timer

Let's see an example of how this whole process will work.

The first thing we need to do is define a timeout. Most often, a timeout in seconds will work. I want to ensure my code doesn't last for more than 10 seconds, so I'll set a variable for that.

$Timeout = 10 ## seconds

Next, I'll need to take whatever code I want to wait on and add it to a scriptblock. For this example, imagine I've created a few background jobs further up the script. I'd like to wait until all of these jobs are completed before continuing.

$jobs = Get-Job
$Condition = {param($jobs) 'Running' -not in $jobs.State } 
$ConditionArgs = $jobs

Next, I need to define how long in between checks my script should perform the task.

$RetryInterval = 5 ## seconds

Now I'll start the timer.

$timer = [Diagnostics.Stopwatch]::StartNew()

Now that the timer is started, I can now invoke that piece of code I need to be done.

## Start checking the condition scriptblock. Do this as long as the action hasn't exceeded
## the timeout or the condition scriptblock returns something other than $false or $null.
while (($timer.Elapsed.TotalSeconds -lt $Timeout) -and (& $Condition $ConditionArgs)) {
     ## Wait a specific interval
     Start-Sleep -Seconds $RetryInterval
 
     ## Check the time
     $totalSecs = [math]::Round($timer.Elapsed.TotalSeconds,0)
     Write-Verbose -Message "Still waiting for action to complete after [$totalSecs] seconds..."
 }

Once the timeout has been exceeded, or the task has completed, I'll then need to stop the timer.

$timer.Stop()

Now I can check to see if the timeout was exceeded or if the task completed on its own. Here, I'm throwing in an exception indicating that the action did not complete if the timeout stopped it. Otherwise, I'm just writing out a verbose statement that any code can follow after that.

## Return status of what happened
if ($timer.Elapsed.TotalSeconds -gt $Timeout) {
    throw 'Action did not complete before timeout period.'
} else {
    Write-Verbose -Message 'Action completed before the timeout period.'
}

For a function called Wait-Action that incorporates all of this functionality into a PowerShell function, download it from the PowerShell Gallery:

Install-Script -Name Wait-Action

Join the Jar Tippers on Patreon

It takes a lot of time to write detailed blog posts like this one. In a single-income family, this blog is one way I depend on to keep the lights on. I'd be eternally grateful if you could become a Patreon patron today!

Become a Patron!