Use-Progress.ps1

<#
.SYNOPSIS
    Display progress bar for processing array of objects.
.EXAMPLE
    PS C:\>Use-Progress -InputObjects @(1..10) -Activity "Processing Parent Objects" -ScriptBlock {
        $Parent = $args[0]
        Use-Progress -InputObjects @(1..200) -Activity "Processing Child Objects" -ScriptBlock {
            $Child = $args[0]
            Write-Host "Child $Child of Parent $Parent."
            Start-Sleep -Milliseconds 50
        }
    }
    Display progress bar for processing array of objects.
.INPUTS
    System.Object[]
#>

function Use-Progress {
    [CmdletBinding()]
    param
    (
        # Array of objects to loop through.
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [object[]] $InputObjects,
        # Specifies the first line of text in the heading above the status bar. This text describes the activity whose progress is being reported.
        [Parameter(Mandatory=$true)]
        [string] $Activity,
        # Script block to execute for each object in array.
        [Parameter(Mandatory=$true)]
        [scriptblock] $ScriptBlock
    )

    begin {
        [System.Collections.Generic.List[object]] $listObjects = New-Object System.Collections.Generic.List[object]
    }

    process {
        $listObjects.AddRange($InputObjects)
    }

    end {
        if ($listObjects.Count -gt 0) { [object[]] $InputObjects = $listObjects.ToArray() }
        [int] $Id = 0
        if (!(Get-Variable stackProgressId -ErrorAction SilentlyContinue)) { New-Variable -Name stackProgressId -Scope Script -Value (New-Object System.Collections.Generic.Stack[int]) }
        while ($stackProgressId.Contains($Id)) { $Id += 1 }
        [hashtable] $paramWriteProgress = @{
            Id = $Id
            Activity = $Activity
        }
        if ($stackProgressId.Count -gt 0) { $paramWriteProgress['ParentId'] = $stackProgressId.Peek() }
        [int] $SecondsRemaining = -1
        [int] $total = $InputObjects.Count

        try {
            $stackProgressId.Push($Id)
            [System.Diagnostics.Stopwatch] $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
            for ($iObject = 0; $iObject -lt $total; $iObject++)
            {
                [timespan] $TimeElapsed = $stopwatch.Elapsed
                $PercentComplete = $iObject/$total
                if ($PercentComplete -gt 0) { $SecondsRemaining = $TimeElapsed.TotalSeconds/$PercentComplete-$TimeElapsed.TotalSeconds }
                Write-Progress -CurrentOperation $InputObjects[$iObject] -Status ("{0:P0} Completed ({1} of {2}) in {3:c}" -f $PercentComplete,$iObject,$total,$TimeElapsed.Subtract($TimeElapsed.Ticks % [TimeSpan]::TicksPerSecond)) -PercentComplete ($PercentComplete*100) -SecondsRemaining $SecondsRemaining @paramWriteProgress
                Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $InputObjects[$iObject]
            }
            Write-Progress -Id $Id -Activity $Activity -Completed
            $stopwatch.Stop()
        }
        finally {
            [void] $stackProgressId.Pop()
        }
    }
}