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

若如初见. 提交于 2019-12-04 08:21:25

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
boeprox

Something like this should work too...

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

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

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.

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?
Jonathan Ramackers

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!