Functions/Show-Progress.ps1

# Inspired by: https://www.powershellgallery.com/packages/Show-Progress/0.0.1
# Added [switch] parameter $PassThru to pass items through the pipeline
# Added [switch] parameter $SecondsRemaining
# Added [int] parameter $Id to enable multiple progress bars

function Show-Progress {
<#
.SYNOPSIS
    Show progress as items pass through a section of the pipline
.DESCRIPTION
    This function allows you to show progress from the pipeline.
    It's intentionally written for efficiency/speed so some compromises on readability were made
.PARAMETER InputObject
    The items on the pipeline being processed. Can also accept this input from the pipeline, which is how this parameter
    is normally used.
.PARAMETER Activity
    A decription of the activity being measured.
.PARAMETER UpdatePercentage
    The percentage of time to update the progress bar. Write-Progress is a slow cmdlet so this is used for performance
    reasons with larger data sets. Defaults to 1.
.PARAMETER PassThru
    Controls whether data is passed along the pipeline. Normally this switch should be used.
.PARAMETER Id
    Allows you to have multiple progress bars running simultaneously. Defaults to 1.
.PARAMETER SecondsRemaining
    Switch that will control the display of estimated number of seconds remaining based upon current progress.
.EXAMPLE
    # This runs through the numbers 1 through 1000 and displays a progress bar based on how many have gone through the pipeline
    1..1000 | Show-Progress
.EXAMPLE
    # Showing progress with a specific activity message and only updating at 10%, 20% etc
    1..10000 | Show-Progress -Activity "doin stuff" -UpdatePercentage 10
.EXAMPLE
    # Showing progress displaying estimated seconds remaining.
    1..50 | show-progress -PassThru -SecondsRemaining | foreach { start-sleep -Milliseconds (get-random -min 100 -max 1000) }
.EXAMPLE
    1..1000 | show-progress -SecondsRemaining -PassThru -Activity 'Performing activity' | foreach { start-sleep -mil (get-random -min 100 -max 1000)}
 
    Will display a progress bar at the top that will look similar to:
 
    Performing activity
        Working - 2%
        [ooo ]
        00:09:20 remaining.
#>


    [CmdletBinding(ConfirmImpact='None')]
    param (
        [Parameter(Mandatory,HelpMessage='Add help message for user', Position = 0, ValueFromPipeline)]
        [PSObject[]]
        $InputObject,

        [string] $Activity = 'Processing items',

        [ValidateRange(1, 100)]
        [int] $UpdatePercentage = 1,

        [Alias('PassThrough')]
        [switch] $PassThru,

        [int] $Id = 1,

        [switch] $SecondsRemaining
    )

    $count = 0
    [int] $totalItems = $Input.count
    write-verbose -Message "TotalItems in the pipeline: $TotalItems"
    $progressWritten = @()
    $StartTime = get-date

    # use a dot sourced scriptblock to loop despite its lower redability as its extremely fast
    $Input | . {
        process {
            # pass thru the input
            if ($PassThru) {
                $_
            }
            $count++
            [int] $percentComplete = ($Count / $totalItems * 100)
            $writeProgressSplat = @{
                Activity        = $Activity
                PercentComplete = $percentComplete
                Status          = "Working - $percentComplete%"
                Id              = $Id
            }
            if ($SecondsRemaining) {
                $SecondsElapsed = (get-date) - $StartTime
                $SecondsTogo = ($SecondsElapsed.TotalSeconds / $count) * ($totalItems - $count)
                $writeProgressSplat.SecondsRemaining = $SecondsTogo
            }
            if ($percentComplete -notin $progressWritten -and ($UpdatePercentage -eq 0 -or $percentComplete % $UpdatePercentage -eq 0)) {
                $progressWritten += $percentComplete
                Write-Progress @writeProgressSplat
            }
        }
    }

    # cleanup - if for some reason the function doesn't arrive at 100% percent completed. This will make the progress
    # bar disappear.
    $writeProgressSplat = @{
        Activity        = $Activity
        PercentComplete = 100
        Status          = 'Completed'
        Id              = $Id
        Completed       = $true
    }
    Write-Progress @writeProgressSplat

} # EndFunction Show-Progress