Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can you set a time limit for a PowerShell script to run for?

I want to set a time limit on a PowerShell (v2) script so it forcibly exits after that time limit has expired.

I see in PHP they have commands like set_time_limit and max_execution_time where you can limit how long the script and even a function can execute for.

With my script, a do/while loop that is looking at the time isn't appropriate as I am calling an external code library that can just hang for a long time.

I want to limit a block of code and only allow it to run for x seconds, after which I will terminate that code block and return a response to the user that the script timed out.

I have looked at background jobs but they operate in a different thread so won't have kill rights over the parent thread.

Has anyone dealt with this or have a solution?

Thanks!

like image 499
David Thomas Avatar asked Jun 11 '10 03:06

David Thomas


2 Answers

Something like this should work too...

$job = Start-Job -Name "Job1" -ScriptBlock {Do {"Something"} Until ($False)}
Start-Sleep -s 10
Stop-Job $job
like image 102
boeprox Avatar answered Sep 20 '22 12:09

boeprox


Here's my solution, inspired by this blog post. It will finish running when all has been executed, or time runs out (whichever happens first).

I place the stuff I want to execute during a limited time in a function:

function WhatIWannaDo($param1, $param2)
{
    # Do something... that maybe takes some time?
    Write-Output "Look at my nice params : $param1, $param2"
}

I have another funtion that will keep tabs on a timer and if everything has finished executing:

function Limit-JobWithTime($Job, $TimeInSeconds, $RetryInterval=5)
{
    try
    {
        $timer = [Diagnostics.Stopwatch]::StartNew()

        while (($timer.Elapsed.TotalSeconds -lt $TimeInSeconds) -and ('Running' -eq $job.JobStateInfo.State)) {
            $totalSecs = [math]::Round($timer.Elapsed.TotalSeconds,0)
            $tsString = $("{0:hh}:{0:mm}:{0:ss}" -f [timespan]::fromseconds($totalSecs))
            Write-Progress "Still waiting for action $($Job.Name) to complete after [$tsString] ..."
            Start-Sleep -Seconds ([math]::Min($RetryInterval, [System.Int32]($TimeInSeconds-$totalSecs)))
        }
        $timer.Stop()
        $totalSecs = [math]::Round($timer.Elapsed.TotalSeconds,0)
        $tsString = $("{0:hh}:{0:mm}:{0:ss}" -f [timespan]::fromseconds($totalSecs))
        if ($timer.Elapsed.TotalSeconds -gt $TimeInSeconds -and ('Running' -eq $job.JobStateInfo.State)) {
            Stop-Job $job
            Write-Verbose "Action $($Job.Name) did not complete before timeout period of $tsString."

        } else {
            if('Failed' -eq $job.JobStateInfo.State){
                $err = $job.ChildJobs[0].Error
                $reason = $job.ChildJobs[0].JobStateInfo.Reason.Message
                Write-Error "Job $($Job.Name) failed after with the following Error and Reason: $err, $reason"
            }
            else{
                Write-Verbose "Action $($Job.Name) completed before timeout period. job ran: $tsString."
            }
        }        
    }
    catch
    {
    Write-Error $_.Exception.Message
    }
}

... and then finally I start my function WhatIWannaDo as a background job and pass it on to the Limit-JobWithTime (including example of how to get output from the Job):

#... maybe some stuff before?
$job = Start-Job -Name PrettyName -Scriptblock ${function:WhatIWannaDo} -argumentlist @("1st param", "2nd param")
Limit-JobWithTime $job -TimeInSeconds 60
Write-Verbose "Output from $($Job.Name): "
$output = (Receive-Job -Keep -Job $job)
$output | %{Write-Verbose "> $_"}
#... maybe some stuff after?
like image 20
malla Avatar answered Sep 21 '22 12:09

malla