Timeout for Runspace?

This topic contains 0 replies, has 1 voice, and was last updated by Profile photo of Forums Archives Forums Archives 5 years, 6 months ago.

  • Author
    Posts
  • #6157

    by cookie.monster at 2012-11-25 11:25:17

    Hi all,

    First, my apologies for butchering any terminology or concepts – this is a bit above my head : )

    I'm using a Runspacepool to help parallelize a domain-wide computer query. Whenever the query nears the end, there are usually ~ 3 runspaces still running, which prevents my script from running various cleanup and post-query functions.

    Is there a built in way to set a timeout for a runspace invokation? Or would I need to build this myself?

    Script is using the concepts from Boe Prox's solution here

    Boe builds an array of objects to keep track of runspace details for each computer queried ($runspaces). I added the startTime property so I can track runtime later on:

    #Create a temporary collection for each runspace
    $temp = "" | Select-Object PowerShell,Runspace,Computer,startTime
    $Temp.Computer = $Computer
    $temp.PowerShell = $powershell
    $temp.startTime = Get-Date

    #Save the handle output when calling BeginInvoke() that will be used later to end the runspace
    $temp.Runspace = $powershell.BeginInvoke()
    $runspaces.Add($temp) | Out-Null

    Get-RunspaceData @runspacehash

    In Get-RunspaceData he includes this:

    If ($runspace.Runspace.isCompleted) {
    $runspace.powershell.EndInvoke($runspace.Runspace)
    $runspace.powershell.dispose()
    $runspace.Runspace = $null
    $runspace.powershell = $null
    }

    I assume the cleanup in here does not end whatever is running in the runspace. Which method(s) would I use to forcefully stop whatever is running? I plan to use the startTime to get the runtime and end the runspace if the runtime is greater than X minutes... Could I just remove the endinvoke() and let dispose run?

    Your insight would be greatly appreciated!

    by cookie.monster at 2012-11-25 17:59:40

    I ended up using what I mentioned above. If you have any ideas on making this more efficient it would be greatly appreciated!

    Foreach-parallel – credit for the meat of this to Tome Tanasovski
    Run-Parallel – credit for the meat of this to Boe Prox

    Tried to describe what's going on here, but the topic is over my head : )

    by proxb at 2012-11-27 18:19:12

    Sorry for the late response to this. I've been on vacation for the week and am just now getting a change to work on this.
    You can use timeouts with runspaces by taking the object that is outputted when using BeginInvoke() and using the Wait() method and supplying a parameter for milliseconds, but it comes at a cost of blocking the thread for any other operations while it waits for the timeout to occur or the operation completes. The workaround for this is to create another runspace within the runspaces for the runspacepool that can handle the blocking wait for the timeout to occur. I came up with an example below that shows the use of a child runspace being created under the parent background runspaces for the runspacepool. Hopefully this makes sense, if not just let me know and I will try to re-look at my explanation and show it a different way. 🙂

    I would also use the Dispose() method to close anything that times out, otherwise using EndInvoke() will cause the thread to hang until it finally finishes (if it ever finishes). A great example would be to create a While ($True){} loop in your scriptblock and then trying to run EndInvoke() to try and close the runspace.

    The example below shows how you can use child runspaces to accomplish this.

    Param (
    $Timeout = 3000,
    $Throttle = 5
    )
    Function Get-RunspaceData {
    [cmdletbinding()]
    param(
    [switch]$Wait
    )
    Do {
    $more = $false
    Foreach($runspace in $runspaces) {
    If ($runspace.Runspace.isCompleted) {
    $runspace.powershell.EndInvoke($runspace.Runspace)
    $runspace.powershell.dispose()
    $runspace.Runspace = $null
    $runspace.powershell = $null
    $Script:i++
    } ElseIf ($runspace.Runspace -ne $null) {
    $more = $true
    }
    }
    If ($more -AND $PSBoundParameters['Wait']) {
    Start-Sleep -Milliseconds 100
    }
    #Clean out unused runspace jobs
    $temphash = $runspaces.clone()
    $temphash | Where {
    $_.runspace -eq $Null
    } | ForEach {
    Write-Verbose ("Removing {0}" -f $_.computer)
    $Runspaces.remove($_)
    }
    } while ($more -AND $PSBoundParameters['Wait'])
    }
    $Scriptblock = {
    Param (
    $Timeout,
    $Iteration
    )
    $runspace = [runspacefactory]::CreateRunspace()
    $runspace.Open()
    $powershell =
    ::Create().AddScript({
    Switch ((Get-Random -InputObject 1,2)) {
    1 {Start-Sleep -Seconds 5}
    2 {$Null}
    }
    })
    $handle = $powershell.BeginInvoke()
    < #
    This will block the thread until either the timeout has reached or the operation has finished.
    Also requires the apartment state to be STA
    #>
    $return = $handle.AsyncWaitHandle.WaitOne($Timeout)
    If ($return) {
    Write-Host ("{0}: No timeout occurred!" -f $iteration) -ForegroundColor Green -BackgroundColor Black
    $powershell.Dispose()
    } Else {
    Write-Host ("{0}: Timeout occurred!" -f $iteration) -ForegroundColor Red -BackgroundColor Black
    $powershell.EndInvoke($handle)
    $powershell.Dispose()
    }
    }
    Write-Verbose ("Creating runspace pool and session states")
    $sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
    $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host)
    $runspacepool.ApartmentState = "MTA"
    $runspacepool.Open()
    Write-Verbose ("Creating empty collection to hold runspace jobs")
    $Script:runspaces = New-Object System.Collections.ArrayList
    1..25 | ForEach {
    #Create the powershell instance and supply the scriptblock with the other parameters
    $powershell =
    ::Create().AddScript($ScriptBlock).AddArgument($Timeout).AddArgument($_)

    #Add the runspace into the powershell instance
    $powershell.RunspacePool = $runspacepool

    #Create a temporary collection for each runspace
    $temp = "" | Select-Object PowerShell,Runspace,Computer
    $Temp.Computer = $_
    $temp.PowerShell = $powershell

    #Save the handle output when calling BeginInvoke() that will be used later to end the runspace
    $temp.Runspace = $powershell.BeginInvoke()

    $runspaces.Add($temp) | Out-Null
    Get-RunspaceData
    }
    Get-RunspaceData -wait

    Hope this helps!

    by cookie.monster at 2012-11-29 05:08:48

    Hi Boe,

    Thanks for the follow up, will check it out! Runspaces within runspaces, sounds fun : )

    by cookie.monster at 2012-12-02 18:15:06

    I tried to make a generic wrapper for this method of tracking timeouts. Either I'm doing it wrong, or the performance hit defeats the purpose (performance).

    I submitted a connect.microsoft.com suggestion to implement anything that would provide a way to add a timeout on a runspace (or any runspace in a runspacepool). Perhaps a property of a runspace that tracks the time it began execution, or a timeout argument for the creation of the runspace or runspacepool. I assume any changes like this wouldn't show up for a long time, if ever.

    In case I'm just doing it wrong, here is the code I tried:

    Function Invoke-Parallel {
    < #
    .SYNOPSIS
    Function to control parallel processing using runspaces

    .PARAMETER inputObject
    Run script against specified objects. Accepts values from pipeline or parameter.

    If you use the pipeline, you lose the progress bar for this. The pipeline is also slower than defining the inputObject explicitly.

    .PARAMETER ScriptFile
    File to run against all computers. Must include parameter to take in a computername. Example: C:\script.ps1

    .PARAMETER ScriptBlock
    Scriptblock to run against all computers. The parameter $_ is added to the first line to allow behavior similar to foreach(){}.

    .PARAMETER Throttle
    Maximum number of threads open at a single time. Default: 20

    .PARAMETER SleepTimer
    Milliseconds to sleep after checking for completed runspaces. Default: 200 (milliseconds)

    .PARAMETER Timeout
    Maximum time in minutes a single thread can run. If execution of your scriptblock takes longer than this, it is disposed. Default: 3 (minutes)

    .EXAMPLE
    Each example uses Test-ForPacs.ps1 which includes the following code:
    param($computer)

    if(test-connection $computer -count 1 -quiet -BufferSize 16){
    $object = [pscustomobject] @{
    Computer=$computer;
    Available=1;
    Kodak=$(
    if((test-path "\\$computer\c$\users\public\desktop\Kodak Direct View Pacs.url") -or (test-path "\\$computer\c$\documents and settings\all users

    \desktop\Kodak Direct View Pacs.url") ){"1"}else{"0"}
    )
    }
    }
    else{
    $object = [pscustomobject] @{
    Computer=$computer;
    Available=0;
    Kodak="NA"
    }
    }

    $object

    .EXAMPLE
    Invoke-Parallel -scriptfile C:\public\Test-ForPacs.ps1 -inputObject $(get-content C:\pcs.txt) -maxRunTime 1

    Pulls list of PCs from C:\pcs.txt,
    Runs Test-ForPacs against each
    If any query takes longer than 1 minute, it is disposed

    .EXAMPLE
    c-is-ts-91, c-is-ts-95 | Invoke-Parallel -scriptfile C:\public\Test-ForPacs.ps1

    Runs against c-is-ts-91, c-is-ts-95
    Runs Test-ForPacs against each

    .FUNCTIONALITY
    PowerShell Language

    .NOTES
    Credit to Boe Prox
    http://learn-powershell.net/2012/05/10/ ... owershell/
    http://gallery.technet.microsoft.com/sc ... fb#content
    #>

    [cmdletbinding()]
    Param (
    [parameter(ValueFromPipeline = $True,ValueFromPipeLineByPropertyName = $True)]
    [Alias('CN','__Server','IPAddress','Server','ComputerName')]
    [psobject]$inputObject,

    $Timeout = 3,

    $sleepTimer = 100,

    $Throttle = 5,

    [ValidateScript({test-path $_ -pathtype leaf})]
    $logpath = "C:\temp\ivp.txt",

    [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')]
    [ValidateScript({test-path $_ -pathtype leaf})]
    $scriptFile,

    [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')]
    [System.Management.Automation.ScriptBlock]$scriptBlock
    )

    Begin {

    Function Get-RunspaceData {
    [cmdletbinding()]
    param(
    [switch]$Wait
    )
    Do {
    $more = $false

    #If we have total count from a bound parameter, write progress
    if($bound){
    Write-Progress -Activity "Running Query"`
    -Status "Starting threads"`
    -CurrentOperation "$count threads defined - $totalCount input objects - $completedCount input objects processed"`
    -PercentComplete ($completedCount / $totalcount * 100)
    }

    #loop through each runspace
    Foreach($runspace in $runspaces) {

    #If the runspace is complete, dispose of it
    If ($runspace.Runspace.isCompleted) {
    $runspace.powershell.EndInvoke($runspace.Runspace)
    $runspace.powershell.dispose()
    $runspace.Runspace = $null
    $runspace.powershell = $null
    $global:completedCount++
    }

    #If the runspace is not complete, set more to true
    ElseIf ($runspace.Runspace -ne $null) {
    $more = $true
    }
    }

    #if more is set, and wait parameter was specified, sleep before looping again
    If ($more -AND $PSBoundParameters['Wait']) {
    Start-Sleep -Milliseconds $sleepTimer
    }

    #Clean out unused runspace jobs
    $temphash = $runspaces.clone()
    $temphash | Where { $_.runspace -eq $Null } | ForEach {
    Write-Verbose ("Removing {0}" -f $_.computer)
    $Runspaces.remove($_)
    }

    #Loop again if more and wait are true
    } while ($more -AND $PSBoundParameters['Wait'])
    }

    #Define the script block that will control timeouts
    $ManagementScriptblock = {
    Param (
    $Timeout,
    $Iteration,
    $script,
    $logpath
    )

    #Open a runspace for the actual script to run in
    $runspace = [runspacefactory]::CreateRunspace()
    $runspace.Open()
    $powershell =
    ::Create().AddScript($script).AddArgument($iteration)
    $handle = $powershell.BeginInvoke()
    < #
    This will block the thread until either the timeout has reached or the operation has finished.
    Also requires the apartment state to be STA
    #>
    $return = $handle.AsyncWaitHandle.WaitOne($Timeout)

    #Handle timeouts
    If ($return) {
    add-Content $logPath ("{0};Completed" -f $iteration)
    $powershell.Dispose()
    } Else {
    add-Content $logPath ("{0};Timeout" -f $iteration)
    $powershell.EndInvoke($handle)
    $powershell.Dispose()
    }
    }

    #Build the scriptblock depending on the parameter used
    switch ($PSCmdlet.ParameterSetName){
    'ScriptBlock' {$ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param(`$_)`r`n" + $Scriptblock.ToString())}
    'ScriptFile' {$scriptblock = [scriptblock]::Create($(get-content $scriptFile | out-string))}
    Default {Write-Error ("Must provide ScriptBlock or ScriptFile"); Return}
    }

    #Create runspacepool
    Write-Verbose ("Creating runspace pool and session states")
    $sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
    $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host)
    $runspacepool.ApartmentState = "MTA"
    $runspacepool.Open()

    Write-Verbose ("Creating empty collection to hold runspace jobs")
    $Script:runspaces = New-Object System.Collections.ArrayList

    #initialize counters, convert timeout to milliseconds
    $startedCount = 0
    $global:completedCount = 0
    $timeout = $timeout * 60 * 1000
    Set-Content $logpath ""

    #if inputobject is bound, get the totalcount for progress tracking
    if($PSBoundParameters.ContainsKey("inputObject")){
    $bound = $true
    $totalCount = $inputObject.count
    }

    }

    Process {
    #Define code to run
    $run = @'
    Write-Verbose "Running Process Block against '$inputObject'"
    #Create the powershell instance and supply the scriptblock with the other parameters
    $powershell =
    ::Create().AddScript($ManagementScriptblock).AddArgument($Timeout).AddArgument($inputObject).AddArgument($scriptblock).addargument($logpath)

    #Add the runspace into the powershell instance
    $powershell.RunspacePool = $runspacepool

    #Create a temporary collection for each runspace
    $temp = "" | Select-Object PowerShell,Runspace,Computer
    $Temp.Computer = $_
    $temp.PowerShell = $powershell

    #Save the handle output when calling BeginInvoke() that will be used later to end the runspace
    $temp.Runspace = $powershell.BeginInvoke()

    $runspaces.Add($temp) | Out-Null
    Get-RunspaceData
    '@
    #Run the here string. Put it in a foreach loop if it didn't come from the pipeline
    if($bound){
    $run = $run -replace "inputObject", "object"
    foreach($object in $inputObject){
    Invoke-Expression -command $run
    }
    }

    else{
    Invoke-Expression -command $run
    }

    }

    #All runspaces have been queued up. Loop through runspaces until we are done
    End {
    Get-RunspaceData -wait
    }
    }

    by proxb at 2012-12-02 19:26:17

    That stinks about the performance. I am looking at another way of accomplishing this using a similar method but this time without nested runspaces, but I don't know if the performance will be much better. I'm currently developing the code to test it out and will post it here when I have it working. Hopefully it works like I want it to but I will post back either way on what I find.

    by cookie.monster at 2012-12-04 04:48:51

    Cool!

    I'm going to try modifying it to run in batches – rather than loop through all input objects immediately, loop through some block of objects (e.g. throttle * 2 + 5, some variable number of objects based on keeping track of what has been queued up, what has completed, and the throttle) that will allow the runspacepool to keep running at capacity, but prevent the start time collected in that block from drifting too far from the actual execution of the runspace.

    *edit:

    I added a while loop before the code that adds a runspace to the runspacepool. It checks to see if the number number of runspaces added to the pool but not completed is greater or equal to a 'buffer'. Not sure what an optimal buffer would be. I have it set to throttle * 2 + 1, to ensure there is always a runspace ready to execute (e.g. if all runspaces in the pool complete in one pass, I want it to immediately start running the next, rather than having to wait for more to be queued up).

    With this method, there will be a balance between an accurate timeout, and optimal performance (i.e. keeping the runspacepool at max utilization).

    Function Invoke-Parallel {
    < #
    .SYNOPSIS
    Function to control parallel processing using runspaces

    .PARAMETER ScriptFile
    File to run against all computers. Must include parameter to take in a computername. Example: C:\script.ps1

    .PARAMETER ScriptBlock
    Scriptblock to run against all computers. The parameter $_ is added to the first line to allow behavior similar to foreach(){}.

    .PARAMETER inputObject
    Run script against specified objects.

    .PARAMETER Throttle
    Maximum number of threads open at a single time. Default: 20

    .PARAMETER SleepTimer
    Milliseconds to sleep after checking for completed runspaces. Default: 200 (milliseconds)

    .PARAMETER Timeout
    Maximum time in minutes a single thread can run. If execution of your scriptblock takes longer than this, it is disposed. Default: 0 (minutes)

    WARNING: Depending on your usage, this timeout may not be entirely accurate. Please read the warning on this webpage:

    http://gallery.technet.microsoft.com/Ru ... l-377fd430

    .EXAMPLE
    Each example uses Test-ForPacs.ps1 which includes the following code:
    param($computer)

    if(test-connection $computer -count 1 -quiet -BufferSize 16){
    $object = [pscustomobject] @{
    Computer=$computer;
    Available=1;
    Kodak=$(
    if((test-path "\\$computer\c$\users\public\desktop\Kodak Direct View Pacs.url") -or (test-path "\\$computer\c$\documents and settings\all users

    \desktop\Kodak Direct View Pacs.url") ){"1"}else{"0"}
    )
    }
    }
    else{
    $object = [pscustomobject] @{
    Computer=$computer;
    Available=0;
    Kodak="NA"
    }
    }

    $object

    .EXAMPLE
    Invoke-Parallel -scriptfile C:\public\Test-ForPacs.ps1 -inputobject $(get-content C:\pcs.txt) -timeout 5 -throttle 10

    Pulls list of PCs from C:\pcs.txt,
    Runs Test-ForPacs against each
    If any query takes longer than 5 minutes, it is disposed
    Only run 10 threads at a time

    .EXAMPLE
    Invoke-Parallel -scriptfile C:\public\Test-ForPacs.ps1 -inputobject c-is-ts-91, c-is-ts-95

    Runs against c-is-ts-91, c-is-ts-95 (-computername)
    Runs Test-ForPacs against each

    .FUNCTIONALITY
    PowerShell Language

    .NOTES
    Credit to Boe Prox
    http://learn-powershell.net/2012/05/10/ ... owershell/
    http://gallery.technet.microsoft.com/sc ... fb#content

    This function may have been updated. See http://gallery.technet.microsoft.com/Ru ... l-377fd430 for details.
    #>
    [cmdletbinding()]
    Param (
    [Parameter(Mandatory=$false,position=0,ParameterSetName='ScriptBlock')]
    [System.Management.Automation.ScriptBlock]$ScriptBlock,

    [Parameter(Mandatory=$false,ParameterSetName='ScriptFile')]
    [ValidateScript({test-path $_ -pathtype leaf})]
    $scriptFile,

    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [Alias('CN','__Server','IPAddress','Server','ComputerName')]
    [PSObject]$InputObject,

    [parameter()]
    [int]$Throttle = 20,

    $SleepTimer = 200,

    $Timeout = 0
    )
    Begin {

    #Function that will be used to process runspace jobs
    Function Get-RunspaceData {
    [cmdletbinding()]
    param( [switch]$Wait )

    Do {
    #set more to false
    $more = $false

    #Progress bar if we have inputobject count (bound parameter)
    if($bound){
    Write-Progress -Activity "Running Query"`
    -Status "Starting threads"`
    -CurrentOperation "$startedCount threads defined - $totalCount input objects - $completedCount input objects processed"`
    -PercentComplete ($completedCount / $totalCount * 100)
    }

    #run through each runspace.
    Foreach($runspace in $runspaces) {

    #get the duration - inaccurate
    $runtime = (get-date) - $runspace.startTime

    #If runspace completed, end invoke, dispose, recycle, counter++
    If ($runspace.Runspace.isCompleted) {
    $runspace.powershell.EndInvoke($runspace.Runspace)
    $runspace.powershell.dispose()
    $runspace.Runspace = $null
    $runspace.powershell = $null
    $script:completedCount++
    }

    #If runtime exceeds max, dispose the runspace
    ElseIf ( $timeout -ne 0 -and ( (get-date) - $runspace.startTime ).totalMinutes -gt $timeout) {
    $runspace.powershell.dispose()
    $runspace.Runspace = $null
    $runspace.powershell = $null
    $script:completedCount++
    Write-Warning "$($runspace.object) timed out"
    }

    #If runspace isn't null set more to true
    ElseIf ($runspace.Runspace -ne $null) {
    $more = $true
    }
    }

    #After looping through runspaces, if more and wait, sleep
    If ($more -AND $PSBoundParameters['Wait']) {
    Start-Sleep -Milliseconds $SleepTimer
    }

    #Clean out unused runspace jobs
    $temphash = $runspaces.clone()
    $temphash | Where { $_.runspace -eq $Null } | ForEach {
    Write-Verbose ("Removing {0}" -f $_.object)
    $Runspaces.remove($_)
    }

    #Stop this loop only when $more if false and wait
    } while ($more -AND $PSBoundParameters['Wait'])

    #End of runspace function
    }

    #Build the scriptblock depending on the parameter used
    switch ($PSCmdlet.ParameterSetName){
    'ScriptBlock' {$ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock("param(`$_)`r`n" + $Scriptblock.ToString())}
    'ScriptFile' {$scriptblock = [scriptblock]::Create($(get-content $scriptFile | out-string))}
    Default {Write-Error ("Must provide ScriptBlock or ScriptFile"); Return}
    }

    #Create runspace pool
    Write-Verbose ("Creating runspace pool and session states")
    $sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
    $runspacepool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $sessionstate, $Host)
    $runspacepool.Open()

    Write-Verbose ("Creating empty collection to hold runspace jobs")
    $Script:runspaces = New-Object System.Collections.ArrayList

    #initialize counters
    $script:startedCount = 0
    $script:completedCount = 0

    #If inputObject is bound get a total count and set bound to true
    $bound = $false
    if( $PSBoundParameters.ContainsKey("inputObject") ){
    $bound = $true
    $totalCount = $inputObject.count
    }

    < #
    Calculate a maximum number of runspaces to allow queued up

    ($Throttle * 2) + 1 ensures the pool always has at least double the throttle + 1
    This means $throttle + 1 items will be queued up and their actual start time will drift from the startTime we create
    For better timeout accuracy or performance, pick an appropriate maxQueue below or define it to fit your needs
    #>
    #PERFORMANCE - don't worry about timeout accuracy, throw 10x the throttle in the queue
    #$maxQueue = $throttle * 10
    #ACCURACY - sacrifice performance for an accurate timeout. Don't keep anything in the pool beyond the throttle
    #$maxQueue = $throttle
    #BALANCE - performance and reasonable timeout accuracy
    $maxQueue = ($Throttle * 2) + 1

    }

    Process {

    $run = @'
    #Create the powershell instance and supply the scriptblock with the other parameters
    $powershell =
    ::Create().AddScript($ScriptBlock).AddArgument($inputobject)

    #Add the runspace into the powershell instance
    $powershell.RunspacePool = $runspacepool

    #Create a temporary collection for each runspace
    $temp = "" | Select-Object PowerShell,Runspace,object,StartTime
    $temp.object = $inputObject
    $temp.PowerShell = $powershell
    $temp.StartTime = get-date

    #Save the handle output when calling BeginInvoke() that will be used later to end the runspace
    $temp.Runspace = $powershell.BeginInvoke()
    Write-Verbose ("Adding {0} collection" -f $temp.object)
    $runspaces.Add($temp) | Out-Null
    $startedCount++

    Write-Verbose ("Checking status of runspace jobs")
    Get-RunspaceData
    '@

    #Calculate number of running runspaces
    $runningCount = $startedCount - $completedCount

    #If we have more running that necessary, run get-runspace data and sleep for a short whilt
    while ($runningCount -ge $maxQueue)
    {
    Write-Verbose "'$runningCount' items running - exceeded '$maxQueue' limit."
    Get-RunspaceData
    Start-Sleep -milliseconds $sleepTimer

    #recalculate number of running runspaces
    $runningCount = $startedCount - $completedCount
    }

    #Run the here string. Put it in a foreach loop if it didn't come from the pipeline
    if($bound){
    $run = $run -replace 'inputObject', 'object'
    foreach($object in $inputObject){
    Invoke-Expression -command $run
    }
    }
    else{
    Invoke-Expression -command $run
    }

    }

    End {
    Write-Verbose ("Finish processing the remaining runspace jobs: {0}" -f (@(($runspaces | Where {$_.Runspace -ne $Null}).Count)))

    Get-RunspaceData -wait

    Write-Verbose ("Closing the runspace pool")
    $runspacepool.close()
    }
    }

    by shuvalov at 2013-03-05 12:02:32

    After playing with both options I see possible solution in using synchronized variable like [HashTable]::Synchronized() and write runspace start time to it at begin of scriptblock execution.

    I also tried to identify current runspace out of storing all runspaces array list in runspace itself, but didn't found any way. So just using 1 more variable to store runspace numbers.

    Here is example:

    function Invoke-RunspaceJob
    {
    < #
    .NOTES
    Author: Alexey Shuvalov
    DateCreated: 05.03.2013
    DateModified: 05.03.2013
    Version: 1.0 - Initial Release

    .SYNOPSIS
    Asynchronously performs an operation for each item in a collection of input objects.

    .DESCRIPTION
    Asynchronously performs an operation for each item in a collection of input objects,
    creating powershell runspaces to perform operations.
    Each runspace execution can be limited by timeout in minutes (shared for all runspace).
    Host runspace variables can be shared with child runspaces by specifieng their names.

    .PARAMETER InputObject
    Specifies the input objects. Invoke-RunspaceJob runs the script block or operation statement on each input object.
    Enter a variable that contains the objects, or type a command or expression that gets the objects.

    .PARAMETER ScriptBlock
    Specifies the operation that is performed on each input object. Enter a script block that describes the operation.

    .PARAMETER ThrottleLimit
    Specifies the maximum number of concurrent runspaces that can be run by this command. If you omit this parameter, the default value 32 is used.

    .PARAMETER Timeout
    Determines the maximum wait time for each background runspace job, in minutes. The timing starts when scriptblock execution begins.

    .PARAMETER ShowProgress
    Shows progress bar.

    .PARAMETER SharedVariables
    Specifies the host variables by their names, that will be avaliable in background runspaces. If a variable is Synchronized like
    [HashTable]::Synchronized(@{}) all changes will be synchronized between all runspaces.

    .EXAMPLE
    PS C:\> 'comp1', 'comp2', 'comp3' | Invoke-RunspaceJob {Start-LongScript -Identity $_}

    Description
    -----------
    Run instance of Start-LongScript for every object in array.

    .EXAMPLE
    PS C:\> 'comp1', 'comp2', 'comp3' | Invoke-RunspaceJob {Start-LongScript -Identity $_} -ThrottleLimit 50 -Timeout 2

    Description
    -----------
    Run instance of Start-LongScript for every object in array, with maximum 50 concurent thread and timeout 2 min for each scriptblock.

    .EXAMPLE
    PS C:\> Invoke-RunspaceJob {Start-LongScript -Identity $_ -Credential $Cred} -SharedVariables Cred -ShowProgress -InputObject ('comp1', 'comp2', 'comp3')

    Description
    -----------
    Run instance of Start-LongScript for every object in array. Share host variable $Cred with all runspace instances and show progress bar.
    #>
    [CmdletBinding()]
    Param
    (
    [Parameter(Mandatory=$true,
    ValueFromPipeline=$true,
    Position=1)]
    [ValidateNotNullOrEmpty()]
    [PSObject[]]
    $InputObject,

    [Parameter(Mandatory=$true,
    Position=0)]
    [ValidateNotNullOrEmpty()]
    [System.Management.Automation.ScriptBlock]
    $ScriptBlock,

    [Parameter(Position=2)]
    [Int32]
    $ThrottleLimit = 32,

    [Parameter(Position=3)]
    [Int32]
    $Timeout,

    [Parameter(Position=5)]
    [switch]
    $ShowProgress,

    [Parameter(Position=4)]
    [ValidateScript({$_ | ForEach-Object -Process {Get-Variable -Name $_}})]
    [string[]]
    $SharedVariables
    )

    Begin
    {
    #region Creating initial variables
    $runspacetimers = [HashTable]::Synchronized(@{})
    $SharedVariables += 'runspacetimers'
    $runspaces = New-Object -TypeName System.Collections.ArrayList
    $bgRunspaceCounter = 0
    #endregion Creating initial variables

    #region Creating initial session state and runspace pool
    Write-Verbose -Message "Creating initial session state"
    $iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
    foreach ($ExternalVariable in $SharedVariables)
    {
    Write-Verbose -Message ('Adding variable ${0} to initial session state' -f $ExternalVariable)
    $iss.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $ExternalVariable, (Get-Variable -Name $ExternalVariable -ValueOnly), ''))
    }
    Write-Verbose "Creating runspace pool with Throttle Limit $ThrottleLimit"
    $rp = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $ThrottleLimit, $iss, $Host)
    $rp.Open()
    #endregion Creating initial session state and runspace pool

    #region Append timeout tracking code at the begining of scriptblock
    $ScriptStart = {
    [CmdletBinding()]
    Param
    (
    [Parameter(Mandatory=$true,
    ValueFromPipeline=$true,
    Position=0)]
    $_,

    [Parameter(Position=1)]
    [ValidateNotNullOrEmpty()]
    [int]
    $bgRunspaceID
    )
    $runspacetimers.$bgRunspaceID = Get-Date
    }

    $ScriptBlock = [System.Management.Automation.ScriptBlock]::Create($ScriptStart.ToString() + $ScriptBlock.ToString())
    #endregion Append timeout tracking code at the begining of scriptblock

    #region Runspace status tracking and result retrieval function
    function Get-Result
    {
    [CmdletBinding()]
    Param
    (
    [switch]$Wait
    )
    do
    {
    $More = $false
    foreach ($runspace in $runspaces)
    {
    $StartTime = $runspacetimers.($runspace.ID)
    if ($runspace.Handle.isCompleted)
    {
    Write-Verbose -Message ('Thread done for {0}' -f $runspace.IObject)
    $runspace.PowerShell.EndInvoke($runspace.Handle)
    $runspace.PowerShell.Dispose()
    $runspace.PowerShell = $null
    $runspace.Handle = $null
    }
    elseif ($runspace.Handle -ne $null)
    {
    $More = $true
    }
    if ($Timeout -and $StartTime)
    {
    if ((New-TimeSpan -Start $StartTime).TotalMinutes -ge $Timeout)
    {
    Write-Warning -Message ('Timeout {0}' -f $runspace.IObject)
    $runspace.PowerShell.Dispose()
    $runspace.PowerShell = $null
    $runspace.Handle = $null
    }
    }
    }
    if ($More -and $PSBoundParameters['Wait'])
    {
    Start-Sleep -Milliseconds 100
    }
    foreach ($threat in $runspaces.Clone())
    {
    if ( -not $threat.handle)
    {
    Write-Verbose -Message ('Removing {0}' -f $threat.IObject)
    $runspaces.Remove($threat)
    }
    }
    if ($ShowProgress)
    {
    $ProgressSplatting = @{
    Activity = 'Working'
    Status = 'Proccesing threads'
    CurrentOperation = '{0} of {1} total threads done' -f ($bgRunspaceCounter - $runspaces.Count), $bgRunspaceCounter
    PercentComplete = ($bgRunspaceCounter - $runspaces.Count) / $bgRunspaceCounter * 100
    }
    Write-Progress @ProgressSplatting
    }
    }
    while ($More -and $PSBoundParameters['Wait'])
    }
    #endregion Runspace status tracking and result retrieval function
    }
    Process
    {
    foreach ($Object in $InputObject)
    {
    $bgRunspaceCounter++
    $psCMD = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock).AddParameter('bgRunspaceID',$bgRunspaceCounter).AddArgument($Object)
    $psCMD.RunspacePool = $rp

    Write-Verbose -Message ('Starting {0}' -f $Object)
    [void]$runspaces.Add(@{
    Handle = $psCMD.BeginInvoke()
    PowerShell = $psCMD
    IObject = $Object
    ID = $bgRunspaceCounter
    })
    Get-Result
    }
    }
    End
    {
    Get-Result -Wait
    if ($ShowProgress)
    {
    Write-Progress -Activity 'Working' -Status 'Done' -Completed
    }
    Write-Verbose -Message "Closing runspace pool"
    $rp.Close()
    $rp.Dispose()
    }
    }

You must be logged in to reply to this topic.