将写入进度添加到 Get-Job/Wait-Job

Add Write-Progress to Get-Job/Wait-Job

我正在使用以下代码显示超时为 120 秒的 PowerShell 作业的结果。我想通过合并 Write-Progress(基于完成的作业数)来增强此代码。我尝试使用 this example 作为参考,但是,当我尝试合并该代码时,进度条会短暂显示 所有作业都已完成后。

    $Jobs = @()
    $ForceStoppedIds = @{}
    
    $Jobs += Get-Job
    $Jobs | Wait-Job -Timeout 120 | Out-Null
    $Jobs | ?{$_.State -eq 'Running'} | Stop-Job -PassThru | %{$ForceStoppedIds[$_.Id] = $true}
    
    foreach ($Job in $Jobs) {
    
        $Name = $Job.Name
        $Output = (Get-Job -Name $Name | Receive-Job)
    
        if ($ForceStoppedIds.Contains($Job.Id)) {
    
            Write-Output "$($Name) - Device unable to process request within 2 minutes"
    
        } else {
    
            Write-Output $Output
    
        }
    
    }

Wait-Job -Timeout 120 将阻塞线程直到指定超时或所有作业完成,因此不可能同时显示进度和等待它们。我可以想到 2 个替代方案,第一个是创建一个 proxy function of Wait-Job following the indications in ,另一个是定义你自己的函数,它的工作与 Wait-Job 类似,但不是阻塞线程,您可以添加一个循环,该循环将 运行 基于 2 个条件:

  • 经过的时间小于或等于我们作为参数传递给函数的超时(为此我们可以使用 Diagnostics.Stopwatch)。
  • 并且,作业仍然是 运行$jobs ArrayList 仍然存在)。

注意下面的函数在大多数情况下应该有效,但是仅用于演示目的,不应依赖。 还值得一提的是,此函数只能用作管道函数,因为它依赖于 automatic variable $input.

首先,我们定义了一个新函数,可用于显示进度以及基于超时等待我们的作业:

using namespace System.Collections.Generic
using namespace System.Diagnostics
using namespace System.Threading

function Wait-JobWithProgress {
    [cmdletbinding()]
    param(
        [parameter(Mandatory, ValueFromPipeline)]
        [object[]] $InputObject,

        [parameter()]
        [int] $TimeOut = [int]::MaxValue
    )

    begin {
        $timer = [Stopwatch]::StartNew()
        $jobs = [List[object]]::new()
    }
    process {
        foreach($job in $InputObject) {
            $jobs.Add($job)
        }
    }
    end {
        $total = $jobs.Count; $completed = 0
        do {
            $progress = @{
                Activity = 'Waiting for Jobs'
                Status = 'Remaining Jobs {0} of {1}' -f
                    ($jobs.State -eq 'Running').Count, $total
                PercentComplete = $completed / $total * 100
            }
            Write-Progress @progress

            $id = [WaitHandle]::WaitAny($jobs.Finished, 200)
            if($id -ne 258) {
                # output this job
                $jobs[$id]
                # remove this job
                $jobs.RemoveAt($id)
                $completed++
            }
        } while($timer.Elapsed.Seconds -le $TimeOut -and $jobs)

        # Stop the jobs not yet Completed and remove them
        $jobs | Stop-Job -PassThru | ForEach-Object {
            Write-Warning ("Job [#{0} - {1}] did not complete on time and was removed..." -f $_.Id, $_.Name)
            Remove-Job $_
        }
        Write-Progress @progress -Completed
    }
}

然后为了测试它,我们可以创建一些带有随机计时器的作业:

0..10 | ForEach-Object {
    Start-Job {
        Start-Sleep (Get-Random -Maximum 15)
        [pscustomobject]@{
            Job    = $using:_
            Result = "Hello from Job# $using:_"
        }
    }
} | Wait-JobWithProgress -TimeOut 10 |
Receive-Job -AutoRemoveJob -Wait | Format-Table -AutoSize