Skip to content

Instantly share code, notes, and snippets.

@mavaddat
Last active April 8, 2023 22:11
Show Gist options
  • Save mavaddat/814c3a2b8750fd8f659c72d28903cf96 to your computer and use it in GitHub Desktop.
Save mavaddat/814c3a2b8750fd8f659c72d28903cf96 to your computer and use it in GitHub Desktop.
Write Progress Template for PowerShell
$Script:Timeout = 30
# Suppose we need to do work on a list of files, and we want to show the user a progress bar
$files = Get-ChildItem -File
[scriptblock]$task = {
[CmdletBinding()]
param (
[Parameter()]
[System.IO.FileSystemInfo]
$File
)
Get-Content -Path ($File.FullName) | Select-String -Pattern '(?<=foo)bar' -AllMatches | ForEach-Object { $_.Line } | Select-Object -Unique | Measure-Object | Select-Object -ExpandProperty Count
}
# First calculate the time to complete one task in the list
$largestFile = $files | Sort-Object -Property Length -Descending | Select-Object -First 1
$start = [datetime]::Now
# Execute the task on the largest file
$task.Invoke($largestFile)
$ticksPerByteEstimate = ([datetime]::Now - $start).Ticks / $largestFile.Length # We will use this to estimate the time remaining for the entire list
$totalBytes = $files | ForEach-Object { $_.Length } | Measure-Object -Sum | Select-Object -ExpandProperty Sum
$estimate = [pscustomobject]@{
TicksPerByte = $ticksPerByteEstimate;
BytesProcessed = $largestFile.Length;
TotalTicksExpected = $totalBytes * $ticksPerByteEstimate;
}
<# Do the tasks here ... #>
# Example
foreach($file in $files){
# Set up progress for the current job
$bytesToProcess = $file.Length
expectedTicksForJob = $bytesToProcess * $estimate.TicksPerByte
$job = Start-Job -ScriptBlock $task -ArgumentList $file -Name "Counting $($file.Name)"
$ticksTakenForJob = 0
$percentCompleteTotal = 100 * $estimate.BytesProcessed * $estimate.TicksPerByte / $estimate.TotalTicksExpected
Write-Progress -Activity "Counting occurrences" -Status "Processing $($file.Name)" -Id 0 -PercentComplete ([math]::Round($percentCompleteTotal)) -SecondsRemaining $secondsRemaining ([timespan]::FromTicks($estimate.TotalTicksExpected * (100 - $percentCompleteTotal)))
while($job.State -ne [System.Management.Automation.JobState]::Running -and $ticksTakenForJob -lt $expectedTicksForJob){
Start-Sleep -Milliseconds 100
$ticksTakenForJob = ([datetime]::Now - $job.PSBeginTime).Ticks
$percentAdd = 100 * $ticksTakenForJob / $expectedTicksForJob
$secondsRemaining = [timespan]::FromTicks($estimate.TotalTicksExpected - $ticksTakenForJob).TotalSeconds
Write-Progress -Activity "Counting occurrences" -Status "Processing $($file.Name)" -Id 0 -PercentComplete ([math]::Round($percentCompleteTotal + $percentAdd)) -SecondsRemaining $secondsRemaining
Write-Progress -Activity "Job" -Id 1 -PercentComplete ([math]::Round($percentAdd)) -SecondsRemaining ([timespan]::FromTicks($expectedTicksForJob - $ticksTakenForJob).TotalSeconds) -ParentId 0
}
# Wait for the job to finish
$job | Wait-Job -Timeout ($Script:Timeout + 0.05 * [timespan]::FromTicks($expectedTicksForJob).TotalSeconds)
Write-Progress -Activity "Job" -Id 1 -Completed
# Take the weighted sum of the time taken for the current job and the time taken for previous jobs (note: bytes for current job gets mathematically canceled by division)
$estimate.TicksPerByte = (($job.PSEndTime - $job.PSBeginTime).Ticks) / $totalBytes + $estimate.TicksPerByte * ($estimate.BytesProcessed / $totalBytes)
$estimate.BytesProcessed += $file.Length
$estimate.TotalTicksExpected = $totalBytes * $estimate.TicksPerByte
}
$end = [datetime]::Now
# Then use that to calculate the time remaining for the entire list
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment