7

我正在尝试编写一个PowerShell接受管道输入的函数。我想使用 显示进度条Write-Progress,该进度条为管道中的每个项目增加。

例如:

function Write-PipelineProgress {
    [Cmdletbinding()]
    Param
    (
        [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] `
        [object[]] $Input,

        [string] $Activity = "Processing items"       
    )

    Begin { Write-Progress -Activity $Activity -Status "Preparing" }

    Process {
        # how do i determine how much progress we've made?
        $percentComplete = ... ?
        Write-Progress -Activity $Activity -Status "Working" -PercentComplete $percentComplete

        # return current item, so processing can continue
        $_
    }

    End { Write-Progress -Activity $Activity -Status "End" -Completed }
}

Get-ChildItem | Write-PipelineProgress -Activity "Listing files"

我如何确定进度(完成百分比)?

4

3 回答 3

7

您需要知道管道中的项目数以跟踪进度。

Powershell 3.0 可以让您计算管道中的内容,而无需执行任何工作,而无需访问管道参数是否正确声明的.Count属性。$Input这也意味着你可以去掉这些begin {} process {} end {}块,如果你愿意,只需一个简单的功能。

早期版本没有该Count属性,因此您首先必须遍历并捕获管道以获取计数然后再次处理,这不是那么有效,稍后我将展示。

Powershell V2 版本:

function Show-ProgressV2{
    param (
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [PSObject[]]$InputObject,
        [string]$Activity = "Processing items"
    )

    Begin {$PipeArray = @()}

    Process {$PipeArray+=$InputObject}

    End {
        [int]$TotItems = ($PipeArray).Count
        [int]$Count = 0

        $PipeArray|foreach {
            $_
            $Count++
            [int]$percentComplete = [int](($Count/$TotItems* 100))
            Write-Progress -Activity "$Activity" -PercentComplete "$percentComplete" -Status ("Working - " + $percentComplete + "%")
            }
        }
}    

Powershell V3 版本:

function Show-ProgressV3{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [PSObject[]]$InputObject,
        [string]$Activity = "Processing items"
    )

        [int]$TotItems = $Input.Count
        [int]$Count = 0

        $Input|foreach {
            $_
            $Count++
            [int]$percentComplete = ($Count/$TotItems* 100)
            Write-Progress -Activity $Activity -PercentComplete $percentComplete -Status ("Working - " + $percentComplete + "%")
        }
}

效率
将两者进行比较,V3 函数的速度大约快 5-6 倍,具体取决于管道的大小。

考虑以下预先过滤的文件列表,查找.jpg我家驱动器中的所有文件,选择前 200 个文件并排序和列出,在管道中使用该函数 3 次,根据您的评论:

$V2 = Measure-Command {Get-ChildItem -Filter *.jpg -Recurse `
| Show-ProgressV2 -Activity "Selecting" `
| Select-Object -First 200 `
| Show-ProgressV2 -Activity "Sorting" `
| Sort-Object -Property FullName `
| Show-ProgressV2 -Activity "Listing" `
| FL}

$V3 = Measure-Command {Get-ChildItem -filter *.jpg -Recurse `
| Show-ProgressV3 -Activity "Selecting" `
| Select-Object -First 200 `
| Show-ProgressV3 -Activity "Sorting" `
| Sort-Object -Property FullName `
| Show-ProgressV3 -Activity "Listing" `
| FL}

$V2  
$V3

这给了我以下时间:

PS C:\Users\Graham> C:\Users\Graham\Documents\Stack_ShowProgress_Pipeline.ps1


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 48
Milliseconds      : 360
Ticks             : 483607111
TotalDays         : 0.000559730452546296
TotalHours        : 0.0134335308611111
TotalMinutes      : 0.806011851666667
TotalSeconds      : 48.3607111
TotalMilliseconds : 48360.7111

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 8
Milliseconds      : 335
Ticks             : 83358374
TotalDays         : 9.6479599537037E-05
TotalHours        : 0.00231551038888889
TotalMinutes      : 0.138930623333333
TotalSeconds      : 8.3358374
TotalMilliseconds : 8335.8374
于 2013-07-12T22:42:54.250 回答
1

您必须决定如何显示进度。在这个例子中,我任意决定开始块占 5%,进程块占 90%,结束块占 5%。从那里您可以以任意方式显示中间百分比(例如,在开始块中从 0% 到 5%)(这就是本示例在开始块和结束块中所做的)。或者,您可以根据脚本实际完成的处理量(这是进程块所做的)来确定百分比。请注意,此示例或多或少是无意义的(但它会执行并显示进度条。)。另请注意,所编写的脚本不会在管道中工作,因此开始/处理/结束块并不是真正必要的,但它们不会受到伤害。

function progress-example {
  param( [string[]] $files)
  begin {
    # This code is somewhat arbitrarily considering the begin block to 
    # take 5% of the time of this cmdlet. The process block is 
    # considered to take 90% of the time, and the end block the last 5%

    write-progress -Activity "Progress Example" -status Beginning `
     -CurrentOperation "Initializing sorted script table" -PercentComplete 0

    # This could be any kind of initialization code; 
    # The code here is non-sense
    $mySortedScriptTable = @{}
    ls *.ps1 | % { $mySortedScriptTable[$_.name] = cat $_ | sort }
    sleep 2 # this code executes too quickly to see the progress bar so slow it down

    write-progress -Activity "Progress Example" -status Beginning `
      -CurrentOperation "Initializing script size table" -PercentComplete 3

    $mySortedScriptSizeTable = @{}
    ls *.ps1 | % { $mySortedScriptSizeTable[$_.name] = $_.Length }

    $totalSizeRequested = 0
    foreach ($file in $files) {
      $totalSizeRequested += $mySortedScriptSizeTable[$file]
    }    
    $numberCharsProcessed = 0
    sleep 2 # this code executes too quickly to see the progress bar so slow it down
    write-progress -Activity "Progress Example" -status Beginning `
      -CurrentOperation "Initialization complete" -PercentComplete 5
    sleep 2 # this code executes too quickly to see the progress bar so slow it down
  }

  process {
    foreach ($file in $files) {
      $thisFileSize = $mySortedScriptSizeTable[$file]

      # Process block takes 90% of the time to complete (90% is arbitrary)
      $percentProcess = 90 * ($numberCharsProcessed / $totalSizeRequested)
      write-progress -Activity "Progress Example" -status "Processing requested files" `
        -CurrentOperation "Starting $file" -PercentComplete (5 + $percentProcess)

      "File $file requested. Size = $thisFileSize. Sorted content follows"
      $mySortedScriptTable[$file]

      $numberCharsProcessed += $thisFileSize
      $percentProcess = 90 * ($numberCharsProcessed / $totalSizeRequested)
      write-progress -Activity "Progress Example" -status "Processing requested files" `
        -CurrentOperation "Starting sleep after $file" -PercentComplete (5 + $percentProcess)

      sleep 2 # slow it down for purposes of demo
    }
  }

  end {
    write-progress -Activity "Progress Example" -status "Final processing" `
      -CurrentOperation "Calculating the fun we had with this example" -PercentComplete (95)
    sleep 2
    write-progress -Activity "Progress Example" -status "Final processing" `
      -CurrentOperation "It's looking like we had mucho fun" -PercentComplete (97)
    sleep 2
    write-progress -Activity "Progress Example" -status "Final processing" `
      -CurrentOperation "Yes, a lot of fun was had" -PercentComplete (99)
    sleep 1
  }
}

progress script1.ps1, script2.ps1, script3.ps1
于 2013-07-12T00:31:28.660 回答
1

我发布了Write-ProgressEx cmdlet 以避免例程代码。试试看。

项目https://github.com/mazzy-ax/Write-ProgressEx


我想使用 Write-Progress 显示一个进度条,该进度条为管道中的每个项目增加。

使用 Write-ProgressEx 开关 -Increment。

cmdLet 存储TotalCurrent. 它会自动计算PercentCompleteSecondsRemaining

我如何确定进度(完成百分比)?

  1. 如果开发人员提供TotalCurrent. Current如果指定了开关 -Increment ,则cmdlet 增量。

  2. 开发者可以使用 Get-ProgressEx 来获取参数的实际值。

查看示例https://github.com/mazzy-ax/Write-ProgressEx/tree/master/samples


更多详情

Write-ProgressEx 扩展了标准 powershell cmdlet 的功能。它提供了一种使用 -PercentComplete 和 -SecondsRemaining 开关的简单方法。

cmdlet:

  • 与管道一起使用;
  • 适用于空活动字符串;
  • 自动显示总数;
  • 自动计算百分比;
  • 使用 [system.diagnostic.stopwatch] 计算剩余秒数;
  • 如果没有参数,则完成所有内部进程;
  • 将总数、当前值和实际参数存储到模块哈希表中;
  • 提供 get/set cmdlet 以访问实际参数

注意:该 cmdlet 对于多线程是不安全的。

于 2017-08-08T09:41:57.727 回答