如何在管道功能中使用Write-Progress?

时间:2013-07-11 23:42:37

标签: powershell

我正在尝试在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"

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

3 个答案:

答案 0 :(得分:6)

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

如果管道参数被正确声明,那么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次: / p>

$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

答案 1 :(得分: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

答案 2 :(得分:1)

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

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

  

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

使用Write-ProgressEx开关 - 增量。

cmdLet商店TotalCurrent。它会自动计算PercentCompleteSecondsRemaining

  

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

  1. 如果开发人员提供TotalCurrent,则会自动执行cmdlet Write-ProgressEx calcaulte -PercentComplete。如果指定了switch -Increment,则cmdlet增量Current

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

  3. 参见示例https://github.com/mazzy-ax/Write-ProgressEx/tree/master/samples

    <小时/> 更多详情

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

    cmdlet:

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

    注意:使用多线程时,cmdlet不安全。