Published: • 4 min read

How to Timeout an External Process in PowerShell

I was troubleshooting a PowerShell script that was part of an automation pipeline today. For the most part, everything ran fine, but I noticed one instance where the process would hang. The only way I was able to stop the process was to kill it from Task Manager. I knew the automation should only typically take a 5-6 seconds per run, so I wanted the script to time itself out after 30 seconds.

Surprisingly, I’ve never had to do anything like this before. After a little research I found the WaitForExit(Int32) method which I thought was perfect.

WaitForExit(Int32) is a method on the System.Diagnostics.Process object that blocks the current thread for up to the specified number of milliseconds, then returns true if the process exited within that window or false if it did not. In PowerShell, you get the process object by passing -PassThru to Start-Process, which returns the object instead of discarding it. From there you call WaitForExit(30000) for a 30-second limit, check the return value, and call Kill() if it came back false. The process is not killed automatically on timeout, so that step is explicit. Note that Kill() only sends the termination signal, so calling WaitForExit() immediately after ensures the process has fully stopped before continuing. Call Close() afterward to release the OS handle.

Here’s an example of how I used it:

$proc = Start-Process python -ArgumentList "automation.py" -PassThru

if (-not $proc.WaitForExit(30000)) {
    $proc.Kill()
    $proc.WaitForExit()
    Write-Host "Timed out. Process killed."
} else {
    Write-Host "Done. Exit code: $($proc.ExitCode)"
}

$proc.Close()

Now its important to note that there are actually two versions of WaitForExit, one includes a parameter and one does not. The key thing WaitForExit(Int32) gives you that the no-argument version does not is a decision point. With WaitForExit(), your script hands control to the process and does not get it back until the process decides to exit. If the process hangs, your script hangs with it, permanently. With WaitForExit(30000), your script hands control to the process, but gets it back after 30 seconds regardless. The process is still running at that point, it was not interrupted, you just stopped waiting for it. That is when you decide what to do next. In this case, kill it. But you could also log it, retry it, or skip it.

To make this concrete, say your script normally finishes in 5 seconds but occasionally gets stuck. With WaitForExit():

$proc = Start-Process python -ArgumentList "automation.py" -PassThru
$proc.WaitForExit()
Write-Host "Done."

If the script hangs, Write-Host "Done." never runs. Your pipeline stops here. With WaitForExit(30000), if the script finishes in 5 seconds, the method returns true after 5 seconds and you hit the else branch. If it hangs, you get control back after 30 seconds, kill it, and the rest of your script continues. The process got its window. You decided what happened when it did not use it.

The no-argument WaitForExit() does have its place, such as after Kill(), since you’re not waiting with a timeout but confirming the process is fully gone. What you want to avoid is using it in place of WaitForExit(Int32) for the timeout case itself, where you need a decision point and a way out if the process hangs.