Skip to content
This repository was archived by the owner on Jan 21, 2021. It is now read-only.

Commit 9b365e8

Browse files
author
Jon Cave
committed
Continuously collect output from background threads
The PowerShell.BeginInvoke<TInput, TOutput>(PSDataCollection<TInput>, PSDataCollection<TOutput>) method[1] is used to collect output from each job into a buffer. This can be read whilst the jobs are still running. Being able to return partial results is particularly useful for long running background threads, such as Invoke-UserHunter -Poll. PowerShell 2.0 doesn't play nicely with generic methods. The technique described in [2] is used to allow this version of BeginInvoke() to be used. [1] https://msdn.microsoft.com/en-us/library/dd182440(v=vs.85).aspx [2] http://www.leeholmes.com/blog/2007/06/19/invoking-generic-methods-on-non-generic-classes-in-powershell/
1 parent fda4563 commit 9b365e8

File tree

1 file changed

+27
-34
lines changed

1 file changed

+27
-34
lines changed

Recon/PowerView.ps1

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9277,11 +9277,16 @@ function Invoke-ThreadedFunction {
92779277
$Pool = [runspacefactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host)
92789278
$Pool.Open()
92799279

9280-
$Jobs = @()
9281-
$PS = @()
9282-
$Wait = @()
9280+
$method = $null
9281+
ForEach ($m in [PowerShell].GetMethods() | Where-Object { $_.Name -eq "BeginInvoke" }) {
9282+
$methodParameters = $m.GetParameters()
9283+
if (($methodParameters.Count -eq 2) -and $methodParameters[0].Name -eq "input" -and $methodParameters[1].Name -eq "output") {
9284+
$method = $m.MakeGenericMethod([Object], [Object])
9285+
break
9286+
}
9287+
}
92839288

9284-
$Counter = 0
9289+
$Jobs = @()
92859290
}
92869291

92879292
process {
@@ -9297,54 +9302,42 @@ function Invoke-ThreadedFunction {
92979302
}
92989303

92999304
# create a "powershell pipeline runner"
9300-
$PS += [powershell]::create()
9305+
$p = [powershell]::create()
93019306

9302-
$PS[$Counter].runspacepool = $Pool
9307+
$p.runspacepool = $Pool
93039308

93049309
# add the script block + arguments
9305-
$Null = $PS[$Counter].AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
9310+
$Null = $p.AddScript($ScriptBlock).AddParameter('ComputerName', $Computer)
93069311
if($ScriptParameters) {
93079312
ForEach ($Param in $ScriptParameters.GetEnumerator()) {
9308-
$Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value)
9313+
$Null = $p.AddParameter($Param.Name, $Param.Value)
93099314
}
93109315
}
93119316

9312-
# start job
9313-
$Jobs += $PS[$Counter].BeginInvoke();
9317+
$o = New-Object Management.Automation.PSDataCollection[Object]
93149318

9315-
# store wait handles for WaitForAll call
9316-
$Wait += $Jobs[$Counter].AsyncWaitHandle
9319+
$Jobs += @{
9320+
PS = $p
9321+
Output = $o
9322+
Result = $method.Invoke($p, @($null, [Management.Automation.PSDataCollection[Object]]$o))
9323+
}
93179324
}
9318-
$Counter = $Counter + 1
93199325
}
93209326
}
93219327

93229328
end {
9329+
Write-Verbose "Waiting for threads to finish..."
93239330

9324-
Write-Verbose "Waiting for scanning threads to finish..."
9325-
9326-
$WaitTimeout = Get-Date
9327-
9328-
# set a 60 second timeout for the scanning threads
9329-
while ($($Jobs | Where-Object {$_.IsCompleted -eq $False}).count -gt 0 -and $($($(Get-Date) - $WaitTimeout).totalSeconds) -lt 60) {
9330-
Start-Sleep -MilliSeconds 500
9331+
Do {
9332+
ForEach ($Job in $Jobs) {
9333+
$Job.Output.ReadAll()
93319334
}
9335+
} While (($Jobs | Where-Object { ! $_.Result.IsCompleted }).Count -gt 0)
93329336

9333-
# end async call
9334-
for ($y = 0; $y -lt $Counter; $y++) {
9335-
9336-
try {
9337-
# complete async job
9338-
$PS[$y].EndInvoke($Jobs[$y])
9339-
9340-
} catch {
9341-
Write-Warning "error: $_"
9342-
}
9343-
finally {
9344-
$PS[$y].Dispose()
9345-
}
9337+
ForEach ($Job in $Jobs) {
9338+
$Job.PS.Dispose()
93469339
}
9347-
9340+
93489341
$Pool.Dispose()
93499342
Write-Verbose "All threads completed!"
93509343
}

0 commit comments

Comments
 (0)