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

前端 未结 6 889
再見小時候
再見小時候 2021-01-05 15:36

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 an

相关标签:
6条回答
  • 2021-01-05 15:52

    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?
    
    0 讨论(0)
  • 2021-01-05 15:56

    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
    
    0 讨论(0)
  • 2021-01-05 16:04

    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.

    0 讨论(0)
  • 2021-01-05 16:09

    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
    
    0 讨论(0)
  • 2021-01-05 16:13

    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
    
    0 讨论(0)
  • 2021-01-05 16:14

    Something like this should work too...

    $job = Start-Job -Name "Job1" -ScriptBlock {Do {"Something"} Until ($False)}
    Start-Sleep -s 10
    Stop-Job $job
    
    0 讨论(0)
提交回复
热议问题