Powershell command timeout

2019-08-13 11:55发布

问题:

I am trying to execute a function or a scriptblock in powershell and set a timeout for the execution.

Basically I have the following (translated into pseudocode):

function query{
    #query remote system for something
}

$computerList = Get-Content "C:\scripts\computers.txt"

foreach ($computer in $computerList){
    $result = query
    #do something with $result
}

The query can range from a WMI query using Get-WmiObject to a HTTP request and the script has to run in a mixed environment, which includes Windows and Unix machines which do not all have a HTTP interface. Some of the queries will therefore necessarily hang or take a VERY long time to return. In my quest for optimization I have written the following:

$blockofcode = {
    #query remote system for something
}

foreach ($computer in $computerList){
    $Job = Start-Job -ScriptBlock $blockofcode -ArgumentList $computer
    Wait-Job $Job.ID -Timeout 10 | out-null
    $result = Receive-Job $Job.ID
    #do something with result
}

But unfortunately jobs seem to carry a LOT of overhead. In my tests a query that executes in 1.066 seconds (according to timers inside $blockofcode) took 6.964 seconds to return a result when executed as a Job. Of course it works, but I would really like to reduce that overhead. I could also start all jobs together and then wait for them to finish, but the jobs can still hang or take ridiculous amounts to time to complete.

So, on to the question: is there any way to execute a statement, function, scriptblock or even a script with a timeout that does not comprise the kind of overhead that comes with jobs? If possible I would like to run the commands in parallel, but that is not a deal-breaker.

Any help or hints would be greatly appreciated!

EDIT: running powershell V3 in a mixed windows/unix environment

回答1:

I think you might want to investigate using Powershell runspaces:

http://learn-powershell.net/2012/05/13/using-background-runspaces-instead-of-psjobs-for-better-performance/



回答2:

Today, I ran across a similar question, and noticed that there wasn't an actual answer to this question. I created a simple PowerShell class, called TimedScript. This class provides the following functionality:

  • Method: Start() method to kick off the job, when you're ready
  • Method:GetResult() method, to retrieve the output of the script
  • Constructor: A constructor that takes two parameters:
    • ScriptBlock to execute
    • [int] timeout period, in milliseconds

It currently lacks:

  • Passing in arguments to the PowerShell ScriptBlock
  • Other useful features you think up

Class: TimedScript

class TimedScript {
  [System.Timers.Timer] $Timer = [System.Timers.Timer]::new()
  [powershell] $PowerShell
  [runspace] $Runspace = [runspacefactory]::CreateRunspace()
  [System.IAsyncResult] $IAsyncResult

  TimedScript([ScriptBlock] $ScriptBlock, [int] $Timeout) {    
    $this.PowerShell = [powershell]::Create()
    $this.PowerShell.AddScript($ScriptBlock)
    $this.PowerShell.Runspace = $this.Runspace

    $this.Timer.Interval = $Timeout

    Register-ObjectEvent -InputObject $this.Timer -EventName Elapsed -MessageData $this -Action ({
      $Job = $event.MessageData
      $Job.PowerShell.Stop()
      $Job.Runspace.Close()
      $Job.Timer.Enabled = $False
    })
  }

  ### Method: Call this when you want to start the job.
  [void] Start() {
    $this.Runspace.Open()
    $this.Timer.Start()
    $this.IAsyncResult = $this.PowerShell.BeginInvoke()
  }

  ### Method: Once the job has finished, call this to get the results
  [object[]] GetResult() {
    return $this.PowerShell.EndInvoke($this.IAsyncResult)
  }
}

Example Usage of TimedScript Class

# EXAMPLE: The timeout period is set longer than the execution time of the script, so this will succeed
$Job1 = [TimedScript]::new({ Start-Sleep -Seconds 2 }, 4000)

# EXAMPLE: This script will fail. Even though Get-Process returns quickly, the Start-Sleep call will cause it to be terminated by its Timer.
$Job2 = [TimedScript]::new({ Get-Process -Name s*; Start-Sleep -Seconds 3 }, 2000)

# EXAMPLE: This job will fail, because the timeout is less than the script execution time.
$Job3 = [TimedScript]::new({ Start-Sleep -Seconds 3 }, 1000)

$Job1.Start()
$Job2.Start()
$Job3.Start()

Code is also hosted on GitHub Gist.