如何设置 PowerShell 脚本运行的时间限制?

发布于 2024-09-05 04:49:29 字数 340 浏览 3 评论 0原文

我想对 PowerShell (v2) 脚本设置时间限制,以便在该时间限制到期后强制退出。

我在 PHP 中看到它们有像 set_time_limit 和 max_execution_time 这样的命令,您可以在其中限制脚本甚至函数可以执行的时间。

对于我的脚本,查看时间的 do/while 循环是不合适的,因为我正在调用可能会挂起很长时间的外部代码库。

我想限制一段代码,只允许它运行 x 秒,之后我将终止该代码块并向用户返回脚本超时的响应。

我查看过后台作业,但它们在不同的线程中运行,因此不具有对父线程的终止权限。

有人处理过这个问题或者有解决方案吗?

谢谢!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

难以启齿的温柔 2024-09-12 04:49:29

像这样的东西也应该起作用......

$job = Start-Job -Name "Job1" -ScriptBlock {Do {"Something"} Until ($False)}
Start-Sleep -s 10
Stop-Job $job

Something like this should work too...

$job = Start-Job -Name "Job1" -ScriptBlock {Do {"Something"} Until ($False)}
Start-Sleep -s 10
Stop-Job $job
不奢求什么 2024-09-12 04:49:29

这是我的解决方案,灵感来自这篇博文。当所有执行完毕或时间耗尽(以先发生者为准)时,它将完成运行。

我将我想要在有限时间内执行的内容放在一个函数中:

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

我有另一个函数可以监视计时器以及所有内容是否已完成执行:

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
    }
}

>...最后我启动我的函数 WhatIWannaDo 作为后台作业并将其传递给 Limit-JobWithTime (包括如何从作业获取输出的示例):

#... 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?

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?
你与清晨阳光 2024-09-12 04:49:29

我知道这是一篇旧文章,但我在我的脚本中使用了它。

我不确定它的使用是否正确,但是 George 提出的 System.Timers.Timer 给了我一个想法,它似乎对我有用。

我将它用于有时挂起 WMI 查询的服务器,超时会阻止它卡住。
然后,我将消息输出到日志文件,而不是写入主机,这样我就可以看到哪些服务器已损坏,并在需要时修复它们。

我也不使用 guid,而是使用服务器主机名。

我希望这是有道理的并对你有帮助。

$MyScript = {
              Get-WmiObject -ComputerName MyComputer -Class win32_operatingsystem
            }

$JobGUID = [system.Guid]::NewGuid()

$elapsedEventHandler = {
    param ([System.Object]$sender, [System.Timers.ElapsedEventArgs]$e)

    ($sender -as [System.Timers.Timer]).Stop()
    Unregister-Event -SourceIdentifier $JobGUID
    Write-Host "Job $JobGUID removed by force as it exceeded timeout!"
    Get-Job -Name $JobGUID | Remove-Job -Force
}

$timer = New-Object System.Timers.Timer -ArgumentList 3000 #just change the timeout here
Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action $elapsedEventHandler -SourceIdentifier $JobGUID
$timer.Start()

Start-Job -ScriptBlock $MyScript -Name $JobGUID

I know this is an old post, but I have used this in my scripts.

I am not sure if its the correct use of it, but the System.Timers.Timer that George put up gave me an idea and it seems to be working for me.

I use it for servers that sometimes hang on a WMI query, the timeout stops it getting stuck.
Instead of write-host I then output the message to a log file so I can see which servers are broken and fix them if needed.

I also don't use a guid I use the servers hostname.

I hope this makes sense and helps you.

$MyScript = {
              Get-WmiObject -ComputerName MyComputer -Class win32_operatingsystem
            }

$JobGUID = [system.Guid]::NewGuid()

$elapsedEventHandler = {
    param ([System.Object]$sender, [System.Timers.ElapsedEventArgs]$e)

    ($sender -as [System.Timers.Timer]).Stop()
    Unregister-Event -SourceIdentifier $JobGUID
    Write-Host "Job $JobGUID removed by force as it exceeded timeout!"
    Get-Job -Name $JobGUID | Remove-Job -Force
}

$timer = New-Object System.Timers.Timer -ArgumentList 3000 #just change the timeout here
Register-ObjectEvent -InputObject $timer -EventName Elapsed -Action $elapsedEventHandler -SourceIdentifier $JobGUID
$timer.Start()

Start-Job -ScriptBlock $MyScript -Name $JobGUID
剧终人散尽 2024-09-12 04:49:29

这是使用计时器的示例。我个人没有尝试过,但我认为它应该有效:

function Main
{
    # do main logic here
}

function Stop-Script
{
    Write-Host "Called Stop-Script."
    [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.CloseAsync()
}

$elapsedEventHandler = {
    param ([System.Object]$sender, [System.Timers.ElapsedEventArgs]$e)

    Write-Host "Event handler invoked."
    ($sender -as [System.Timers.Timer]).Stop()
    Unregister-Event -SourceIdentifier Timer.Elapsed
    Stop-Script
}

$timer = New-Object System.Timers.Timer -ArgumentList 2000 # setup the timer to fire the elapsed event after 2 seconds
Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier Timer.Elapsed -Action $elapsedEventHandler
$timer.Start()

Main

Here is an example of using a Timer. I haven't tried it personally, but I think it should work:

function Main
{
    # do main logic here
}

function Stop-Script
{
    Write-Host "Called Stop-Script."
    [System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.CloseAsync()
}

$elapsedEventHandler = {
    param ([System.Object]$sender, [System.Timers.ElapsedEventArgs]$e)

    Write-Host "Event handler invoked."
    ($sender -as [System.Timers.Timer]).Stop()
    Unregister-Event -SourceIdentifier Timer.Elapsed
    Stop-Script
}

$timer = New-Object System.Timers.Timer -ArgumentList 2000 # setup the timer to fire the elapsed event after 2 seconds
Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier Timer.Elapsed -Action $elapsedEventHandler
$timer.Start()

Main
稀香 2024-09-12 04:49:29

我想出了这个脚本。

  • Start-Transcript 记录所有操作并将其保存到文件中。
  • 将当前进程 ID 值存储在变量 $p 中,然后将其写入屏幕。
  • 将当前日期分配给 $startTime 变量。
  • 之后我再次分配它,并将当前日期的额外时间添加到 var $expiration 中。
  • updateTime 函数返回应用程序关闭之前还剩多少时间。并将其写入控制台。
  • 如果计时器超过到期时间,则开始循环并终止进程。
  • 就是这样。

代码:

Start-Transcript C:\Transcriptlog-Cleanup.txt #write log to this location
$p = Get-Process  -Id $pid | select -Expand id  # -Expand selcts the string from the object id out of the current proces.
Write-Host $p

$startTime = (Get-Date) # set start time
$startTime
$expiration = (Get-Date).AddSeconds(20) #program expires at this time
# you could change the expiration time by changing (Get-Date).AddSeconds(20) to (Get-Date).AddMinutes(10)or to hours whatever you like

#-----------------
#Timer update function setup
function UpdateTime
   {
    $LeftMinutes =   ($expiration) - (Get-Date) | Select -Expand minutes  # sets minutes left to left time
    $LeftSeconds =   ($expiration) - (Get-Date) | Select -Expand seconds  # sets seconds left to left time


    #Write time to console
    Write-Host "------------------------------------------------------------------" 
    Write-Host "Timer started at     :  "  $startTime
    Write-Host "Current time         :  "  (Get-Date)
    Write-Host "Timer ends at        :  "  $expiration
    Write-Host "Time on expire timer : "$LeftMinutes "Minutes" $LeftSeconds "Seconds"
    Write-Host "------------------------------------------------------------------" 
    }
#-----------------


do{   #start loop
    Write-Host "Working"#start doing other script stuff
    Start-Sleep -Milliseconds 5000  #add delay to reduce spam and processing power
    UpdateTime #call upadate function to print time
 }
until ($p.HasExited -or (Get-Date) -gt $expiration) #check exit time

Write-Host "done"
Stop-Transcript
if (-not $p.HasExited) { Stop-Process -ID $p -PassThru } # kill process after time expires

I came up with this script.

  • Start-Transcript to log all actions and save them to a file.
  • Store the current process ID value in the variable $p then write it to screen.
  • Assign the current date to the $startTime variable.
  • Afterwards I assign it again and add the extra time to the current date to the var $expiration.
  • The updateTime function return what time there is left before the application closes. And writes it to console.
  • Start looping and kill process if the timer exceeds the expiration time.
  • That's it.

Code:

Start-Transcript C:\Transcriptlog-Cleanup.txt #write log to this location
$p = Get-Process  -Id $pid | select -Expand id  # -Expand selcts the string from the object id out of the current proces.
Write-Host $p

$startTime = (Get-Date) # set start time
$startTime
$expiration = (Get-Date).AddSeconds(20) #program expires at this time
# you could change the expiration time by changing (Get-Date).AddSeconds(20) to (Get-Date).AddMinutes(10)or to hours whatever you like

#-----------------
#Timer update function setup
function UpdateTime
   {
    $LeftMinutes =   ($expiration) - (Get-Date) | Select -Expand minutes  # sets minutes left to left time
    $LeftSeconds =   ($expiration) - (Get-Date) | Select -Expand seconds  # sets seconds left to left time


    #Write time to console
    Write-Host "------------------------------------------------------------------" 
    Write-Host "Timer started at     :  "  $startTime
    Write-Host "Current time         :  "  (Get-Date)
    Write-Host "Timer ends at        :  "  $expiration
    Write-Host "Time on expire timer : "$LeftMinutes "Minutes" $LeftSeconds "Seconds"
    Write-Host "------------------------------------------------------------------" 
    }
#-----------------


do{   #start loop
    Write-Host "Working"#start doing other script stuff
    Start-Sleep -Milliseconds 5000  #add delay to reduce spam and processing power
    UpdateTime #call upadate function to print time
 }
until ($p.HasExited -or (Get-Date) -gt $expiration) #check exit time

Write-Host "done"
Stop-Transcript
if (-not $p.HasExited) { Stop-Process -ID $p -PassThru } # kill process after time expires
吻泪 2024-09-12 04:49:29

像这样的事情怎么样:

## SET YOUR TIME LIMIT
## IN THIS EXAMPLE 1 MINUTE, BUT YOU CAN ALSO USE HOURS/DAYS
# $TimeSpan = New-TimeSpan -Days 1 -Hours 2 -Minutes 30
$TimeSpan = New-TimeSpan -Minutes 1
$EndTime = (Get-Date).AddMinutes($TimeSpan.TotalMinutes).ToString("HH:mm")

## START TIMED LOOP
cls
do
{
## START YOUR SCRIPT
Write-Warning "Test-Job 1...2...3..."
Start-Sleep 3
Write-Warning "End Time = $EndTime`n"
}
until ($EndTime -eq (Get-Date -Format HH:mm))

## TIME REACHED AND END SCRIPT
Write-Host "End Time reached!" -ForegroundColor Green

当使用小时或天作为计时器时,请确保调整 $TimeSpan.TotalMinutes
以及 HH:mm 格式,因为这不利于在示例中使用天。

How about something like this:

## SET YOUR TIME LIMIT
## IN THIS EXAMPLE 1 MINUTE, BUT YOU CAN ALSO USE HOURS/DAYS
# $TimeSpan = New-TimeSpan -Days 1 -Hours 2 -Minutes 30
$TimeSpan = New-TimeSpan -Minutes 1
$EndTime = (Get-Date).AddMinutes($TimeSpan.TotalMinutes).ToString("HH:mm")

## START TIMED LOOP
cls
do
{
## START YOUR SCRIPT
Write-Warning "Test-Job 1...2...3..."
Start-Sleep 3
Write-Warning "End Time = $EndTime`n"
}
until ($EndTime -eq (Get-Date -Format HH:mm))

## TIME REACHED AND END SCRIPT
Write-Host "End Time reached!" -ForegroundColor Green

When using hours or days as a timer, make sure you adjust the $TimeSpan.TotalMinutes
and the HH:mm format, since this does not facilitate the use of days in the example.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文