2

On linux, there is the timeout command, which has a very nice and simple syntax:

timeout 120 command [args]

It's simple. It runs the command and kills it if the command runs over the time limit. Despite my best efforts, "solutions" on windows are multiple lines, don't display the output of the command to the terminal, and the cygwin "timeout" failed to kill the process if I increased the timeout to more than one minute (I have no explanation for this). Does anyone have a better idea?

7
  • 3
    Possible duplicate of adding a timeout to batch/powershell Commented Sep 23, 2017 at 0:49
  • That linked solution doesn't display output to the screen as it is running. Commented Sep 23, 2017 at 1:00
  • You could do something like for /f "tokens=3 delims=; " %I in ('wmic process call create "ping localhost -n 10" ^| find "ProcessId"') do >NUL (timeout /t 5 /nobreak && taskkill /im %I) in the cmd console, or double the %% in a bat script. If you're putting this in a bat script, you can dress it up by putting it in a function then calling the function. If console, you could set a doskey macro. Commented Sep 23, 2017 at 1:05
  • @rojo That's almost fine, but the stdout occurs in a new window. Is there any way to capture the stdout in the same window? Commented Sep 23, 2017 at 1:12
  • @xaav I was thinking maybe something with start /b, but there'd be no easy way to get the spawned process's PID. You'd have to taskkill /im "imagename eq ping.exe" or similar, which might have unfortunate effects of your executable is running in multiple concurrent windows. Also, that'd make the program non-interactive, if that matters. Commented Sep 23, 2017 at 1:15

3 Answers 3

3

I mean there is timeout.exe but I don't think that gives you quite the same functionality that you are looking for.

I am not aware of a timeout equivalent for Windows. Following the suggestion in the linked answer PowerShell jobs would be a suggestion on how to replicate timeouts behavior. I rolled a simple sample function

function timeout{
    param(
        [int]$Seconds,
        [scriptblock]$Scriptblock,
        [string[]]$Arguments
    )

    # Get a time stamp of before we run the job
    $now = Get-Date 

    # Execute the scriptblock as a job
    $theJob = Start-Job -Name Timeout -ScriptBlock $Scriptblock -ArgumentList $Arguments

    while($theJob.State -eq "Running"){
        # Display any output gathered so far. 
        $theJob | Receive-Job

        # Check if we have exceeded the timeout.
        if(((Get-Date) - $now).TotalSeconds -gt $Seconds){
            Write-Warning "Task has exceeded it allotted running time of $Seconds second(s)."
            Remove-Job -Job $theJob -Force
        }
    }

    # Job has completed natually
    $theJob | Remove-Job -ErrorAction SilentlyContinue
}

This starts a job and keeps checking for its output. So you should get near live updates of the running process. You do not have to use -ScriptBlock and could opt for -Command based jobs. I will show an example using the above function and a script block.

timeout 5 {param($e,$o)1..10|ForEach-Object{if($_%2){"$_`: $e"}else{"$_`: $o"};sleep -Seconds 1}} "OdD","eVeN"

This will print the numbers 1 to 10 as well as the numbers evenness. Between displaying a number there will be a pause of 1 second. If the timeout is reached a warning is displayed. In the above example all 10 number will not be displayed since the process was only allowed 5 seconds.

Function could use some touching up and likely there is someone out there that might have done this already. My take on it at least.

Sign up to request clarification or add additional context in comments.

Comments

0

Based on @Matt answer, below is a function which actually behaves like linux's timeout. But this one works for external executables, not for powershell scripts and commandlets.

E.g., when running timeout 10 blabla.exe a b c --d, blabla.exe will recieve 4 args and will have 10 seconds to process them before pwsh will interrupt and return.

function Start-TimeoutCommand ($Seconds) {
    $now = Get-Date
    $global:lastJobStatus = 'running'
    $application = $args[0]
    $arguments = $args[1..$args.count]
    $job = Start-Job {
        param($Application, $Arguments)
        #Write-Host -foreg yellow $Application $Arguments
        & $Application @Arguments
    } -ArgumentList $Application, $Arguments
    
    do {
        Start-Sleep -Milliseconds 500
        
        # Display any output gathered so far
        $job | Receive-Job
        
        # Check if we have exceeded the timeout
        if (((Get-Date) - $now).TotalSeconds -gt $Seconds) {
            Write-Warning "Task has exceeded allowed running time of $Seconds second(s)."
            $global:lastJobStatus = 'interrupted'
            $job | Stop-Job
            $job | Remove-Job -Force
            return
        }
    } while ($job.State -eq "Running")
    
    # Get any final output
    $job | Receive-Job
    
    # Job has completed naturally
    $job | Remove-Job -ErrorAction SilentlyContinue
    $global:lastJobStatus = 'finished'
}

Set-Alias -Name timeout -Value Start-TimeoutCommand

Comments

0

Assuming you have the the Threadjob module. Run the job in a background thread, and then sleep a certain time and then forcibly receive the job. I couldn't do "receive-job -force -autoremove" without -wait, which I didn't want. The first job takes too long, and there's no output. The second job completes within the timeout.

$timeout = 5
$script = { sleep $using:joblength; "joblength $using:joblength done" }

$joblength = 10
$job = start-threadjob $script
sleep $timeout
stop-job $job
receive-job $job
remove-job $job

$joblength = 1
$job = start-threadjob $script
sleep $timeout
stop-job $job
receive-job $job
remove-job $job

Output:

joblength 1 done

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.