Wednesday, May 11, 2016

Simple Progress Bar

I use the Powershell progress bar (write-progress) quite often. I put it into scripts that have long execution time (like cycling through all 100,000 objects in our GAL. I put it into place when manipulating data from multiple sources. I try to show some progress everywhere things happen that take more than a few seconds.

Problem being, the progress bar requires some setup. You need to know how many objects you are going to touch. You need to maintain an index of what object you are currently on. That I why I started working on these two 'skins' for the progress bar.

My first function uses a global variable that it maintains. This first copy takes the maximum count of items you want to review, then using a the global variable, tracks which item it's currently displaying. [0 .. Item Count]

Function WP {
 [CmdletBinding()] param(
  [Parameter(Mandatory=$true,ValueFromPipeline=$true)][Int]$ArrayItemCount,
  [Parameter()][String]$JobName="Counter"
 )
 $OCount = $ArrayItemCount
 $envVar = get-Variable -Name $JobName -Scope Global -ErrorAction SilentlyContinue -ValueOnly
 if ($EnvVar -eq $null) { 
  #Global Variable doesn't exist, create one called based on $JobName
  $Env_WPIndex = 0
  New-Variable -Name $JobName -Scope Global -Value 0 #-Visibility Private
 } else {
  #Use current global variable value.
  $env_WPIndex = [double]$EnvVar
 }
 #display basic progress bar
 Write-Progress -Activity $JobName -Status $([string]$Env_WPIndex + ":"+[string]$ocount) -PercentComplete (($Env_WPIndex / $oCOunt)*100) 
 $env_WPIndex = $env_wpIndex + 1
 if ($env_wpIndex -lt $OCount) { 
  #if less then max object count, increment the global variable by one
  Set-Variable -Name $JobName -Scope Global -ErrorAction SilentlyContinue -Value $env_WPIndex
 } else {
  #if already greater than max, remove the global variable from machine. 
  Set-Variable -Name $JobName -Scope Global -Value $null # -ErrorAction SilentlyContinue
 }
}



For example:

$Services = get-service
$Services | %{wp $Services.count; write-host $_.name}

The problem I've found with Function WP is sometimes the global variable doesn't get reset at the end of the previous run. This causes your next progress bar to 'wrap around' (started at 11 goes to 100, then back to 11 again). I added the JobName field so that it could spin up a new global for each different iteration if you wish.

The second script is much simpler, but requires you send a copy of the entire array. You pass the function a copy of your array, plus what item you're currently on, and it displays a progress bar based of it's location in the array.

Function WP2 {
 [CmdletBinding()] param(
  [Parameter(Mandatory=$true,ValueFromPipeline=$true)][array]$Array,
  [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $Item
 )
 #Find Index of current item in Array
 $Index = [array]::IndexOf($array, $item)
 #Count items in array
 $ocount = $array.count
 
 Write-Progress -activity "Counter" -Status $([string]$Index + ":"+[string]$ocount) -PercentComplete (($Index/$OCount)*100)
}

For example:

$Services = get-service
$Services | %{wp2 $Services $_ ; write-host $_.name}



No comments:

Post a Comment