Public/Progress.ps1
|
function New-RichProgressBar { <# .SYNOPSIS Creates a styled progress bar string. .DESCRIPTION Generates a string representing a progress bar based on a percentage. .PARAMETER Percentage The completion percentage (0-100). .PARAMETER Width The width of the progress bar in characters. Defaults to 40. .PARAMETER CompletedStyle The style for the completed portion of the bar. Defaults to "bold green". .PARAMETER RemainingStyle The style for the remaining portion of the bar. Defaults to "white". #> param( [double]$Percentage, [int]$Width = 40, [string]$CompletedStyle = "bold green", [string]$RemainingStyle = "white" ) $completedWidth = [int]($Width * ($Percentage / 100.0)) if ($completedWidth -gt $Width) { $completedWidth = $Width } $remainingWidth = $Width - $completedWidth $completed = "━" * $completedWidth $remaining = "━" * $remainingWidth $styledCompleted = Format-RichText -Text $completed -Style $CompletedStyle $styledRemaining = Format-RichText -Text $remaining -Style $RemainingStyle return "$styledCompleted$styledRemaining" } function Start-RichProgress { <# .SYNOPSIS Runs a script block with live progress bars. .DESCRIPTION Executes a script block and provides a background thread to render live progress bars for tasks added via Add-RichProgressTask. .PARAMETER ScriptBlock The script block to execute. .PARAMETER RefreshRate The refresh rate for the progress display in milliseconds. Defaults to 10. .EXAMPLE Start-RichProgress { $id = Add-RichProgressTask -Description "Downloading" -Total 100 for ($i = 0; $i -le 100; $i += 10) { Update-RichProgress -Id $id -Completed $i Start-Sleep -Milliseconds 200 } } #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [scriptblock]$ScriptBlock, [Parameter(Mandatory = $false)] [int]$RefreshRate = 10 ) # Progress state $script:RichProgressTasks = @{} $script:RichProgressCounter = 0 function global:Add-RichProgressTask { <# .SYNOPSIS Adds a new task to the live progress display. .DESCRIPTION Creates a task with a description and total value, returning a task ID for updates. .PARAMETER Description The description of the task. .PARAMETER Total The total value representing 100% completion. Defaults to 100. .PARAMETER Completed The initial completed value. Defaults to 0. #> param( [string]$Description, [double]$Total = 100, [double]$Completed = 0 ) $id = $script:RichProgressCounter++ $script:RichProgressTasks[$id] = @{ Description = $Description Total = $Total Completed = $Completed StartTime = [DateTime]::Now } return $id } function global:Update-RichProgress { <# .SYNOPSIS Updates the progress of a task. .DESCRIPTION Updates a task's completion status by either advancing the current value or setting it to a specific value. .PARAMETER Id The ID of the task to update. .PARAMETER Advance The amount to add to the current completed value. .PARAMETER Completed The new absolute completed value. If specified, Advance is ignored. #> param( [int]$Id, [double]$Advance = 0, [double]$Completed = -1 ) if ($script:RichProgressTasks.ContainsKey($Id)) { if ($Completed -ge 0) { $script:RichProgressTasks[$Id].Completed = $Completed } else { $script:RichProgressTasks[$Id].Completed += $Advance } } } # Background thread for rendering $runspace = [runspacefactory]::CreateRunspace() $runspace.Open() # We need to share the tasks hashtable. In PowerShell, we can use a synchronized hashtable. $syncTasks = [hashtable]::Synchronized($script:RichProgressTasks) $powershell = [powershell]::Create().AddScript({ param($tasks) [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 [Console]::Write("$([char]27)[?25l") # Hide cursor try { while ($true) { $output = "" $taskIds = $tasks.Keys | Sort-Object if ($taskIds.Count -gt 0) { foreach ($id in $taskIds) { $task = $tasks[$id] $percent = ($task.Completed / $task.Total) * 100 if ($percent -gt 100) { $percent = 100 } # Simple bar construction (avoiding function calls for speed/scope) $width = 30 $done = [int]($width * ($percent / 100.0)) $rem = $width - $done $bar = "$([char]27)[32m" + ("━" * $done) + "$([char]27)[0m" + ("━" * $rem) $desc = $task.Description.PadRight(20).Substring(0, 20) $output += "`r$([char]27)[K$desc $bar $([math]::Round($percent, 1))%`n" } # Move cursor back up for next refresh $up = "$([char]27)[" + $taskIds.Count + "A" [Console]::Write("`r$output$up") } [System.Threading.Thread]::Sleep(100) } } catch {} finally { [Console]::Write("$([char]27)[?25h") # Show cursor } }).AddArgument($syncTasks) $powershell.Runspace = $runspace $handle = $powershell.BeginInvoke() try { &$ScriptBlock } finally { $powershell.Stop() $runspace.Close() $powershell.Dispose() $runspace.Dispose() # Move cursor past the progress bars $count = $script:RichProgressTasks.Count for ($i = 0; $i -lt $count; $i++) { Write-Host "" } } } |