Runspaces/Runspaces.ps1
|
#============================================================================== # Jobs Phase 6 IT3.1 - Job-based IPC for runspace tasks # A pack that carries ts_JobToken / ts_JobPipe runs through a JOB WRAPPER: # the runspace pulls the token from Explorer's Jobs pipe (it is the same # process, so the pipe server accepts it), publishes $global:SRxJobChild, # routes ProcessRules' rule callbacks to its Job, runs the ruleset script # unchanged with the ORIGINAL pack, reports every error as a 'problem' and # ends with 'complete' (ok / failed). Explorer learns everything through # the Job - the legacy callbacks (OnStatus / OnBegin / OnEnd / OnError / # OnStart / OnStop) are no longer registered by Explorer. # A pack without a token runs exactly as before (legacy path). # OnEnd / OnError callbacks now also receive the pack object (3rd arg) so # the host can map a finished / timed-out runspace to its Job. #============================================================================== class Runspaces { $Caller = $null # ---- Jobs Phase 6 IT3.1 static [string] $JobModuleName = 'SRxCore' # module that defines SRxBasePipeChildAPI static [boolean] $FailOnErrors = $true # non-terminating errors of a task = failure static [int] $MaxReportedErrors = 5 hidden $_jobScriptBlock = $null #-------------------------------------- # Private properties #-------------------------------------- hidden $_host = $Host hidden [System.Windows.Forms.Timer]$_timer = $null hidden [System.Management.Automation.ScriptBlock]$_closeCallback = $null hidden [System.Management.Automation.ScriptBlock]$_beginCallback = $null hidden [System.Management.Automation.ScriptBlock]$_endCallback = $null hidden [System.Management.Automation.ScriptBlock]$_errorCallback = $null hidden [System.Management.Automation.ScriptBlock]$_startCallback = $null hidden [System.Management.Automation.ScriptBlock]$_statusCallback = $null hidden [System.Management.Automation.ScriptBlock]$_stopCallback = $null hidden [string]$_messageText = "Saving {0} filters..." hidden $_progressId = $null hidden $_running = $false hidden [int]$_startedCount = 0 hidden [int]$_totalCount = 0 hidden [int]$_completedCount = 0 hidden [string]$_commandFile = "" hidden [int]$_ticks = 0 hidden $_runspacepool = $null hidden $_runspaces = $null hidden [System.Collections.ArrayList]$_allObjects = $null hidden [boolean]$_timedOutTasks = $false hidden [string]$_scriptFile = $null hidden $_scriptBlock = $null hidden [System.Collections.ArrayList] $_filterNodes = $null hidden $_userModules = $null hidden $_userSnapins = $null hidden $_userVariables = $null hidden $_userFunctions = $null #-------------------------------------- # Public properties #-------------------------------------- [PSObject]$Parameter = $null [boolean]$ImportVariables = $false [boolean]$ImportModules = $false [boolean]$ImportFunctions = $false [int]$Throttle = 20 [int]$SleepTimer = 200 [int]$RunspaceTimeout = 0 [boolean]$NoCloseOnTimeout = $false [int]$MaxQueue = $null [boolean]$AppendLog = $false [string]$LogFile = $null [boolean]$Quiet = $false #-------------------------------------- # Constructor #-------------------------------------- Runspaces( [string]$ScriptFile) { $this._scriptFile = $ScriptFile } #-------------------------------------- # Private methods #-------------------------------------- hidden [void] waitForJobs() { if( -not $this._timer) { $this._timer = [System.Windows.Forms.Timer]::new() $this._timer.Interval = 5000 $this._timer.Tag = $this $this._timer.add_tick( { param($source, $e) $_this = $source.Tag $_this._ticks++ $more = $_this.getRunspaceData()# $false ) if( -not $more ) { $_this.endJobs() } else { Write-Host "^" -NoNewline } }) } #Write-Host "starting DoEvents timer ..." $this._timer.start() $this._ticks = 0 } hidden [void] endJobs() { if($this._timer) { $this._timer.stop() } $this._ticks = 0 $this._running = $false #if (-not $this.Quiet) { # Write-Progress -Id $this._progressId -Activity "Running Query" -Status "Starting threads" -Completed #} $this.statusUpdate($true) $this._totalCount = 0 $this._completedCount = 0 $this._startedCount = 0 $this._allObjects.Clear() if( $this._stopCallback) { $this._stopCallback.Invoke($this) } } hidden [void] load() { #No max queue specified? Estimate one. if( -not $this.MaxQueue ) { if($this.RunspaceTimeout -ne 0){ $this.MaxQueue = $this.Throttle } else{ $this.MaxQueue = $this.Throttle * 3 } } $this._progressId = Get-Random TR "Throttle: '$($this.Throttle)' SleepTimer '$($this.SleepTimer)' runSpaceTimeout '$($this.RunspaceTimeout)' maxQueue '$($this.MaxQueue)' logFile '$($this.LogFile)'" #If they want to import variables or modules, create a clean runspace, get loaded items, use those to exclude items if ($this.ImportVariables -or $this.ImportModules -or $this.ImportFunctions) { $StandardUserEnv = [powershell]::Create().addscript({ #Get modules, snapins, functions in this clean runspace $Modules = Get-Module | Select-Object -ExpandProperty Name # NOTE: -snapins removed from support in core 6 & 7 #$Snapins = $null #if( $PSVersionTable.PSVersion.Major -eq 5 ) { $Snapins = Get-PSSnapin | Select-Object -ExpandProperty Name #} $Functions = Get-ChildItem function:\ | Select-Object -ExpandProperty Name #Get variables in this clean runspace #Called last to get vars like $? into session $Variables = Get-Variable | Select-Object -ExpandProperty Name #Return a hashtable where we can access each. @{ Variables = $Variables Modules = $Modules Snapins = $Snapins Functions = $Functions } },$true).invoke()[0] if ($this.ImportVariables) { #Exclude common parameters, bound parameters, and automatic variables Function _temp {[cmdletbinding(SupportsShouldProcess=$True)] param() } $VariablesToExclude = @( (Get-Command _temp | Select-Object -ExpandProperty parameters).Keys + $PSBoundParameters.Keys + $StandardUserEnv.Variables ) Write-Verbose "Excluding variables $( ($VariablesToExclude | Sort-Object ) -join ", ")" # we don't use 'Get-Variable -Exclude', because it uses regexps. # One of the veriables that we pass is '$?'. # There could be other variables with such problems. # Scope 2 required if we move to a real module $this._userVariables = @( Get-Variable | Where-Object { -not ($VariablesToExclude -contains $_.Name) } ) Write-Verbose "Found variables to import: $( ($this._userVariables | Select-Object -expandproperty Name | Sort-Object ) -join ", " | Out-String).`n" } if ($this.ImportModules) { $this._userModules = @( Get-Module | Where-Object {$StandardUserEnv.Modules -notcontains $_.Name -and (Test-Path $_.Path -ErrorAction SilentlyContinue)} | Select-Object -ExpandProperty Path ) #if( $PSVersionTable.PSVersion.Major -eq 5 ) { $this._userSnapins = @( Get-PSSnapin | Select-Object -ExpandProperty Name | Where-Object {$StandardUserEnv.Snapins -notcontains $_ } ) #} } if($this.ImportFunctions) { $this._userFunctions = @( Get-ChildItem function:\ | Where-Object { $StandardUserEnv.Functions -notcontains $_.Name } ) } } } hidden [void] init() { $scriptText = $(Get-Content $this._scriptFile | out-string) $this._scriptBlock = [scriptblock]::Create( $scriptText ) $this._jobScriptBlock = [scriptblock]::Create( [Runspaces]::JobWrapperText($scriptText) ) # Jobs Phase 6 IT3.1 #TR "`$ScriptBlock: $($this._scriptBlock | Out-String)" TR "Creating runspace pool and session states" #If specified, add variables and modules/snapins to session state $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() if($this.ImportVariables -and $this._userVariables.count -gt 0) { foreach($Variable in $this._userVariables) { $sessionstate.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Variable.Name, $Variable.Value, $null) ) } } if ($this.ImportModules) { if($this._userModules.count -gt 0) { foreach($ModulePath in $this._userModules) { TR " > ModulePath = $ModulePath" $sessionstate.ImportPSModule($ModulePath) } } #if( $PSVersionTable.PSVersion.Major -eq 5 ) { if($this._userSnapins.count -gt 0) { foreach($PSSnapin in $this._userSnapins) { [void]$sessionstate.ImportPSSnapIn($PSSnapin, [ref]$null) } } #} } if($this.ImportFunctions -and $this._userFunctions.count -gt 0) { foreach ($FunctionDef in $this._userFunctions) { $sessionstate.Commands.Add((New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $FunctionDef.Name,$FunctionDef.ScriptBlock)) } } #Create runspace pool $this._runspacepool = [runspacefactory]::CreateRunspacePool(1, $this.Throttle, $sessionstate, $this._host) $this._runspacepool.Open() TR "Creating empty collection to hold runspace jobs" $this._runspaces = [System.Collections.ArrayList]::new() #New-Object System.Collections.ArrayList <# #If inputObject is bound get a total count and set bound to true $bound = $PSBoundParameters.keys -contains "InputObject" if(-not $bound) { [System.Collections.ArrayList]$allObjects = @() } #> $this._allObjects = [System.Collections.ArrayList]::new() #Set up log file if specified if( $this.LogFile -and (-not (Test-Path $this.LogFile) -or $this.AppendLog -eq $false)){ New-Item -ItemType file -Path $this.LogFile -Force | Out-Null ("" | Select-Object -Property Date, Action, Runtime, Status, Details | ConvertTo-Csv -NoTypeInformation -Delimiter ";")[0] | Out-File $this.LogFile } #write initial log entry $log = "" | Select-Object -Property Date, Action, Runtime, Status, Details $log.Date = Get-Date $log.Action = "Batch processing started" $log.Runtime = $null $log.Status = "Started" $log.Details = $null if($this.LogFile) { ($log | convertto-csv -Delimiter ";" -NoTypeInformation)[1] | Out-File $this.LogFile -Append } $this._timedOutTasks = $false #counts for progress $this._totalCount = 0 $this._completedCount = 0 $this._startedCount = 0 $this.statusUpdate($true) } hidden [void] addRunspaceJobs($pack) { [void]$this._allObjects.add( $pack ) #counts for progress $this._totalCount += $this._allObjects.count foreach($object in $this._allObjects) { #region add scripts to runspace pool #Create the powershell instance, set verbose if needed, supply the scriptblock and parameters $powershell = [powershell]::Create() <# if ($VerbosePreference -eq 'Continue') { [void]$powerShell.AddScript({$VerbosePreference = 'Continue'}) } #> #begin Jobs Phase 6 IT3.1 - a Job task runs through the job wrapper $isJobTask = $false try { $isJobTask = -not [string]::IsNullOrEmpty([string]$object.ts_JobToken) } catch { $isJobTask = $false } if ($isJobTask -and $null -ne $this._jobScriptBlock) { [void]$powershell.AddScript($this._jobScriptBlock).AddArgument($object) } else { [void]$powershell.AddScript($this._scriptBlock).AddArgument($object) # legacy (no Job) } #end Jobs Phase 6 IT3.1 if ($this.Parameter) { [void]$powershell.AddArgument($this.Parameter) } <# # $Using support from Boe Prox if ($UsingVariableData) { Foreach($UsingVariable in $UsingVariableData) { Write-Verbose "Adding $($UsingVariable.Name) with value: $($UsingVariable.Value)" [void]$PowerShell.AddArgument($UsingVariable.Value) } } #> #Add the runspace into the powershell instance $powershell.RunspacePool = $this._runspacepool #Create a temporary collection for each runspace $temp = "" | Select-Object PowerShell, StartTime, object, Runspace $temp.PowerShell = $powershell $temp.StartTime = Get-Date $temp.object = $object #Save the handle output when calling BeginInvoke() that will be used later to end the runspace $temp.Runspace = $powershell.BeginInvoke() $this._startedCount++ $this._commandFile += "$($temp.object.ts_RuleSet) " if( $this._beginCallback ) { $this._beginCallback.Invoke($this, $temp.object.ts_RuleSet) } #Add the temp tracking info to $runspaces collection TR ( "Adding {0} to collection at {1}" -f $temp.object, $temp.starttime.tostring() ) $this._runspaces.Add($temp) | Out-Null #loop through existing runspaces one time $this.getRunspaceData()# $false ) #If we have more running than max queue (used to control timeout accuracy) #Script scope resolves odd PowerShell 2 issue $firstRun = $true while ($this._runspaces.count -ge $this.MaxQueue) { #give verbose output if($firstRun) { TR "$($this._runspaces.count) items running - exceeded $($this.MaxQueue) limit." } $firstRun = $false #run get-runspace data and sleep for a short while $this.getRunspaceData()# $false ) Start-Sleep -Milliseconds $this.SleepTimer } #endregion add scripts to runspace pool } $this._allObjects.Clear() } hidden [void] beginJobs($pack) { if( -not $this._running) { $this._running = $true if( $this._startCallback) { $this._startCallback.Invoke($this) } } #Use Try/Finally to catch Ctrl+C and clean up. try { $this.addRunspaceJobs($pack) TR ( "Finish processing the remaining runspace jobs: {0}" -f ( @($this._runspaces | Where-Object {$Null -ne $_.Runspace}).Count) ) $this.waitForJobs() } finally { <# #Close the runspace pool, unless we specified no close on timeout and something timed out if ( ($this._timedOutTasks -eq $false) -or ( ($this._timedOutTasks -eq $true) -and ($this.NoCloseOnTimeout -eq $false) ) ) { Write-Verbose "Closing the runspace pool" $this._runspacepool.close() } #collect garbage [gc]::Collect() #> } #$this._running = $false #if( $this._stopCallback) { $this._stopCallback.Invoke($this) } } hidden [void] statusUpdate( [boolean]$completed) { if (-not $this.Quiet) { if( -not $completed) { Write-Progress -Id $this._progressId -Activity "Running Query" -Status "Starting threads"` -CurrentOperation "$($this._startedCount) threads defined - $($this._totalCount) input objects - $($this._completedCount) input objects processed"` -PercentComplete $( Try { $($this._completedCount) / $($this._totalCount) * 100 } Catch {0} ) } else { Write-Progress -Id $this._progressId -Activity "Running Query" -Status "Starting threads" -Completed } } if( $this._statusCallback) { #[string]$ruleName = "Saving $($this._startedCount) filters..." #"Executing rule: $($args[0])" #write-host ("Creating shortcut for {0} with path {1} in folder {2}..." -f $_.Key, $_.Value[0], $_.Value[1]) [string]$messageText = $this._messageText -f $this._commandFile #$($this._startedCount) if($completed) { $messageText = "Ready" } #[string]$counter = "$($this._completedCount)" #"$($args[1])" [int]$cnt = $($($this._completedCount)*10 + $this._ticks) [string]$counter = "$cnt" #"$($args[1])" #[string]$maxcount = "$($this._totalCount)" #"$($args[2])"\ [int]$maxcnt = $($($this._totalCount)*10) [string]$maxcount = "$maxcnt" #"$($args[2])"\ if( $this._completedCount -gt $this._totalCount ) { [int]$maxcnt = $($($this._completedCount)*10) #$maxcount = "$($this._completedCount)" $maxcount = "$maxcnt" } if($cnt -ge $maxcnt) { $counter = "$maxcnt" } [string]$message = "$messageText,$counter,$maxcount" # [LEGACY-IPC] Jobs Phase 6 IT3.1: Explorer no longer registers OnStatus - Job # tasks report to their Job; kept for any other caller of this class if($this._statusCallback) { $this._statusCallback.Invoke($this, $message) } } } hidden [boolean] getRunspaceData() { # [boolean]$wait = $false) { #[cmdletbinding()] #param( [switch]$Wait ) #loop through runspaces #if $wait is specified, keep looping until all complete $more = $false # Do { #set more to false for tracking completion $more = $false #Progress bar if we have inputobject count (bound parameter) $this.statusUpdate($false) #run through each runspace. $this._commandFile = "" foreach($runspace in $this._runspaces) { #get the duration - inaccurate $currentdate = Get-Date $runtime = $currentdate - $runspace.startTime $runMin = [math]::Round( $runtime.totalminutes ,2 ) #set up log object $log = "" | Select-Object Date, Action, Runtime, Status, Details $log.Action = "Removing:'$($runspace.object)'" $log.Date = $currentdate $log.Runtime = "$runMin minutes" #If runspace completed, end invoke, dispose, recycle, counter++ If ($runspace.Runspace.isCompleted) { $this._completedCount++ #check if there were errors if($runspace.powershell.Streams.Error.Count -gt 0) { #set the logging info and move the file to completed $log.status = "CompletedWithErrors" Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] foreach($ErrorRecord in $runspace.powershell.Streams.Error) { Write-Error -ErrorRecord $ErrorRecord } } else { #add logging details and cleanup $log.status = "Completed" Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] } #everything is logged, clean up the runspace $runspace.powershell.EndInvoke($runspace.Runspace) $runspace.powershell.dispose() $runspace.Runspace = $null $runspace.powershell = $null if( $this._endCallback ) { $this._endCallback.Invoke($this, $runspace.object.ts_RuleSet, $runspace.object) } # IT3.1: + pack } #If runtime exceeds max, dispose the runspace elseIf ( $this.RunspaceTimeout -ne 0 -and $runtime.totalseconds -gt $this.RunspaceTimeout) { $this._completedCount++ $this._timedOutTasks = $true #add logging details and cleanup $log.status = "TimedOut" Write-Verbose ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] Write-Error "Runspace timed out at $($runtime.totalseconds) seconds for the object:`n$($runspace.object | out-string)" #Depending on how it hangs, we could still get stuck here as dispose calls a synchronous method on the powershell instance if (!$this.NoCloseOnTimeout) { $runspace.powershell.dispose() } $runspace.Runspace = $null $runspace.powershell = $null if( $this._errorCallback ) { $this._errorCallback.Invoke($this, $runspace.object.ts_RuleSet, $runspace.object) } # IT3.1: + pack } #If runspace isn't null set more to true elseIf ($null -ne $runspace.Runspace ) { $log = $null $more = $true if( $null -ne $runspace.object.ts_RuleSet) { $this._commandFile += "$($runspace.object.ts_RuleSet) "} } #log the results if a log file was indicated if($this.LogFile -and $log) { ($log | ConvertTo-Csv -Delimiter ";" -NoTypeInformation)[1] | out-file $this.LogFile -append } } #Clean out unused runspace jobs $temphash = $this._runspaces.clone() $temphash | Where-Object { $Null -eq $_.runspace } | ForEach-Object { $this._runspaces.remove($_) } #sleep for a bit if we will loop again # if($wait){ Start-Sleep -milliseconds $this.SleepTimer } #Loop again only if -wait parameter and there are more runspaces to process # } while ($more -and $wait) return $more #End of runspace function } hidden [void] removeFilter( $filterNode) { $selection = ($($this._filterNodes) | Where-Object { -not (($($_.xPath) -eq $($filterNode.xPath)) -and (($_.Scope) -eq $($filterNode.Scope) -and (($_.optype) -eq $($filterNode.optype)))) }) $this._filterNodes = [System.Collections.ArrayList]::new() if( $selection ) { $this._filterNodes += $selection } } hidden [void] addFilter( $filterNode) { $this.removeFilter( $filterNode) $this._filterNodes += $filterNode } hidden [void] updateFilter($pack) { $this._filterNodes = $pack.filterNodes#.Clone() <# Write-Host "<<<<<<<<<<<<<<<<<<< updateFilter >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" Write-Host ">>>>>BEFORE: filterNodes:" Write-Host "-------------------------" Write-Host ($this._filterNodes | FT | Out-String) #> [System.Collections.Stack]$nodesStack = $pack.nodesStack.Clone() while( $($nodesStack.Count) -gt 1 ) { $nodesStack.Pop() } $termNode = $nodesStack.Peek() [string]$operation = $($termNode.Operation) [int]$optype = $($termNode.optype) <# Write-Host ">>>>> termNode---------------" Write-Host ($termNode | FT | Out-String) Write-Host "-----------------------------" #> if ($operation -eq "add") { #if( $null -ne $term) { $details = @{ xPath = $termNode.xPath Scope = $termNode.Scope optype = $optype Filter = $termNode.Attributes.Clone() } $object = New-Object PSObject -Property $details $this.addFilter($object) #} } elseif($operation -eq "remove") { $this.removeFilter($termNode) } else { } <# Write-Host ">>>>>AFTER: filterNodes:" Write-Host "------------------------" Write-Host ($this._filterNodes | FT | Out-String) Write-Host ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" #> } #-------------------------------------- # Jobs Phase 6 IT3.1 - job wrapper # Text of the scriptblock a Job task runs on a pool runspace. The # ruleset script is embedded unchanged as $__inner and called with the # ORIGINAL pack the Job hands over (Command of the pulled envelope). # SRxBasePipeChildAPI is created inside the module scope of # [Runspaces]::JobModuleName (PowerShell classes of a module are not # visible to scripts), falling back to the global type (tests). #-------------------------------------- static [string] JobWrapperText([string]$scriptText) { $head = @' param($__pack) $__ok = $true $__c = $null $__errBase = $Error.Count try { # IT3.1 r3: never run against Explorer's OWN $global:SRxEnv. ImportVariables hands the # pool a reference to it; its script methods belong to Explorer's runspace, so every # call from this thread is marshalled into Explorer's busy UI runspace and breaks both # sides ("'Test-Path' / 'New-Object' is not recognized"). The ruleset script builds a # runspace-local one (ExecuteRulesetJob.ps1 -> Initialize-SRxEnv), as a child process does. Remove-Variable -Name SRxEnv -Scope Global -Force -ErrorAction Ignore # IT3.1 r4: that Initialize-SRxEnv must not touch the console (title / buffer size): # $Host of a pool runspace is Explorer's own console. -NoHostUI by default in THIS runspace. if ($null -eq $global:PSDefaultParameterValues) { $global:PSDefaultParameterValues = @{} } $global:PSDefaultParameterValues['Initialize-SRxEnv:NoHostUI'] = $true # the SRxCore that exports the CURRENT Job functions (not any other loaded copy) $__mod = $null $__cmd = Get-Command Connect-SRxJobsChild -ErrorAction Ignore if ($null -ne $__cmd) { $__mod = $__cmd.Module } if ($null -eq $__mod) { $__mod = Get-Module -Name '__MODULE__' -ErrorAction Ignore | Sort-Object Version -Descending | Select-Object -First 1 } if ($null -ne $__mod) { $__c = & $__mod { [SRxBasePipeChildAPI]::new() } } else { $__c = New-Object -TypeName 'SRxBasePipeChildAPI' } # older class copies have no NoSync: then no sync worker is started anyway in a runspace if ($null -ne $__c.PSObject.Properties['NoSync']) { $__c.NoSync = $true } if (-not $__c.Pull([string]$__pack.ts_JobToken, [string]$__pack.ts_JobPipe)) { throw ('Job token not pulled: ' + $__c.LastError) } $global:SRxJobChild = $__c $global:__onRuleCallback = $__c.RuleCallback() $global:__onRuleCompletedCallback = $__c.RuleCompletedCallback() [void]$__c.Hello() # legacy pipe-Shell / pipe-Explorer client calls of the script -> this Job (same as Job windows) if (Get-Command Install-SRxJobChannels -ErrorAction Ignore) { try { Install-SRxJobChannels } catch { [void]$__c.Post('log', ('Install-SRxJobChannels: ' + $_.Exception.Message)) } } $__task = $__c.Command if ($null -eq $__task) { $__task = $__pack } # IT3.1 r5/r6: the task's own SRxEnv is built HERE (the ruleset script's # Initialize-SRxEnv then finds it); its changes are diffed and sent to # Explorer through the Job ('env' before 'complete'). The SRxEnv object is # passed explicitly (a class method may see another runspace's globals). $__syncEnv = ($null -ne $__c.PSObject.Methods['CaptureEnvBaseline']) -and [bool]$__c.SyncEnv if ($__syncEnv -and $null -eq $global:SRxEnv) { if (Get-Command Initialize-SRxEnv -ErrorAction Ignore) { $__sid = $null try { $__sid = $__task.ts_SourceID } catch { $__sid = $null } try { Initialize-SRxEnv -SourceID $__sid } # -NoHostUI via PSDefaultParameterValues (r4) catch { [void]$__c.Post('problem', ('Initialize-SRxEnv failed: ' + $_.Exception.Message)) } } else { [void]$__c.Post('problem', 'Initialize-SRxEnv is not available in the runspace - SRxEnv changes cannot be reported') } } if ($__syncEnv) { if ($null -eq $global:SRxEnv) { [void]$__c.Post('problem', 'No SRxEnv in the runspace after Initialize-SRxEnv') } else { $__c.CaptureEnvBaseline($global:SRxEnv) } } $__errBase = $Error.Count # errors of the ruleset script only $__inner = { '@ $tail = @' } $null = & $__inner $__task } catch { $__ok = $false if ($null -ne $__c -and $__c.Ready) { [void]$__c.Post('problem', ('Task failed: ' + $_.Exception.Message)) } else { throw } } finally { if ($null -ne $__c -and $__c.Ready) { $__new = $Error.Count - $__errBase if ($__new -gt 0) { $__n = [Math]::Min($__new, __MAXERR__) for ($__i = $__n - 1; $__i -ge 0; $__i--) { [void]$__c.Post('problem', ('Error: ' + [string]$Error[$__i])) } if ($__new -gt $__n) { [void]$__c.Post('problem', ('... and {0} more error(s)' -f ($__new - $__n))) } if (__FAILONERR__) { $__ok = $false } } # IT3.1 r6: 'env' from THIS runspace's SRxEnv, before 'complete' (also on failure) if ($null -ne $__c.PSObject.Methods['PublishEnvChanges'] -and [bool]$__c.SyncEnv) { try { [void]$__c.PublishEnvChanges($global:SRxEnv) } catch { [void]$__c.Post('problem', ('SRxEnv report failed: ' + $_.Exception.Message)) } } [void]$__c.Complete($__ok) } $global:SRxJobChild = $null $global:__onRuleCallback = $null $global:__onRuleCompletedCallback = $null } '@ $h = $head.Replace('__MODULE__', [Runspaces]::JobModuleName) $t = $tail.Replace('__MAXERR__', [string][Runspaces]::MaxReportedErrors) if ([Runspaces]::FailOnErrors) { $t = $t.Replace('__FAILONERR__', '$true') } else { $t = $t.Replace('__FAILONERR__', '$false') } return ($h + "`n" + $scriptText + "`n" + $t) } #-------------------------------------- # Public methods #-------------------------------------- [void] MessageText($formattedString) { $this._messageText = $formattedString } [void] OnStart( [System.Management.Automation.ScriptBlock]$onStartCallback ) { $this._startCallback = $onStartCallback } [void] OnStatus( [System.Management.Automation.ScriptBlock]$onStatusCallback ) { $this._statusCallback = $onStatusCallback } [void] OnBegin( [System.Management.Automation.ScriptBlock]$onBeginCallback ) { $this._beginCallback = $onBeginCallback } [void] OnEnd( [System.Management.Automation.ScriptBlock]$onEndCallback ) { $this._endCallback = $onEndCallback } [void] OnError( [System.Management.Automation.ScriptBlock]$onErrorCallback ) { $this._errorCallback = $onErrorCallback } [void] OnStop( [System.Management.Automation.ScriptBlock]$onStopCallback ) { $this._stopCallback = $onStopCallback } [void] OnClose( [System.Management.Automation.ScriptBlock]$onCloseCallback ) { $this._closeCallback = $onCloseCallback } [void] Open (){ $this.load() $this.init() } [boolean]Running() { return $this._running } [System.Collections.ArrayList] AddPack($pack) { #$this.updateFilter($pack) $this.beginJobs($pack) return $null #$this._filterNodes } [void] Clear() { $this.endJobs() #if( $this._stopCallback) { $this._stopCallback.Invoke($this) } } [void] Close (){ $this.endJobs() #Close the runspace pool, unless we specified no close on timeout and something timed out if ( ($this._timedOutTasks -eq $false) -or ( ($this._timedOutTasks -eq $true) -and ($this.NoCloseOnTimeout -eq $false) ) ) { Write-Verbose "Closing the runspace pool" $this._runspacepool.close() } #collect garbage [gc]::Collect() # if( $this._closeCallback) { $this._closeCallback.Invoke($this) } } } |