Public/Progress/Write-BetterProgress.ps1
# Module: Orbit.Tools # Function: Helper # Author: David Eberhardt # Updated: 01-NOV-2021 # Status: Live function Write-BetterProgress { <# .SYNOPSIS Wrapper for Write-Progress to improve consistency with output .DESCRIPTION This function improves upon Write-Progress to display more consistent and meaningful progress bars .PARAMETER Id Required. Synonymous with Write-Progress Parameter Id Id to bind it to. Could have been omitted, but for consistent input it should be clearly stated. .PARAMETER ParentId Optional. Synonymous with Write-Progress Parameter ParentId Parent Id to bind it to. If not provided will be assumed one less than the ID provided. For Example: Id 2 will result in the Parent ID being calculated as ParentId 1 .PARAMETER Activity Required. Synonymous with Write-Progress Parameter Activity When called in Functions, $MyInvocation.MyCommand is a good activity. When called in ID higher than 0, the activity should display what is being worked on (Loop item, etc.) .PARAMETER Status Required. Synonymous with Write-Progress Parameter Status Message to display as a Status. This can be the current operation if this level of granularity is enough .PARAMETER Step Required. Current Step to display progress for Calculates Write-Progress Parameter PercentComplete with (StepNumber / StepTotal ) * 100 .PARAMETER Of Required. Total Steps to calculate from Calculates Write-Progress Parameter PercentComplete with (StepNumber / StepTotal ) * 100 If not provided or received as 0, will be assumed as 100 to avoid running into errors .PARAMETER CurrentOperation Optional. Synonymous with Write-Progress Parameter CurrentOperation Provides more granularity over the Status if required. .EXAMPLE Write-BetterProgress -Id 0 -Activity $MyInvocation.MyCommand -Step $i -of 10 Assumes running an a foreach loop of 'foreach ($i in (1..10)) {Write-BetterProgress -Id 0...}' Displays the Progress for ID 0 - with the activity set to the calling command (useful when used in a Function) .EXAMPLE Write-BetterProgress -Id 0 -Activity $MyInvocation.MyCommand -Status "Step $i" -Step $i -of 10 Assumes running an a foreach loop of 'foreach ($i in (1..10)) {Write-BetterProgress -Id 0...}' Displays the Progress for ID 0 - with the activity set to the calling command (useful when used in a Function) Displays the Status .EXAMPLE Write-BetterProgress -Id 0 -Activity $MyInvocation.MyCommand -CurrentOperation "Task X" -Step $i -of 10 Assumes running an a foreach loop of 'foreach ($i in (1..10)) {Write-BetterProgress -Id 0...}' Displays the Progress for ID 0 - with the activity set to the calling command (useful when used in a Function) Displays the Current Operation .EXAMPLE Write-BetterProgress -Id 0 -Activity $MyInvocation.MyCommand -Status "Step $i" -CurrentOperation "Task X" -Step $i -of 10 Assumes running an a foreach loop of 'foreach ($i in (1..10)) {Write-BetterProgress -Id 0...}' Displays the Progress for ID 0 - with the activity set to the calling command (useful when used in a Function) Displays the Status and the Current Operation .EXAMPLE Write-BetterProgress -Id 0 -Activity $MyInvocation.MyCommand -Status "" -CurrentOperation "" -Step $i -of 10 Assumes running an a foreach loop of 'foreach ($i in (1..10)) {Write-BetterProgress -Id 0...}' Displays the Progress for ID 0 - with the activity set to the calling command (useful when used in a Function) Contrary to Write-Progress, allows passing of empty strings to the omit displays of Status and/or Current Operation .EXAMPLE Write-BetterProgress -Id 1 -Activity "Processing Item #$i" -Status "Step $i - Substep $j" -Step $j -of 10 Assumes running an a foreach loop of 'foreach ($j in (1..10)) {Write-BetterProgress -Id 1...}' Displays the Progress for ID 1 - with the activity set to the calling command (useful when used in a Function) NOTE: The ParentId is set to 0 automatically (one less than the ID provided, unless Parameter ParentId is used) .EXAMPLE Write-BetterProgress -Id 2 -ParentId 1 -Activity 'Looping through Activities' -CurrentOperation 'Displaying Level 3' -Status "Step $i - Substep $j - iteration $k" -Step $k -of 10 Assumes running an a foreach loop of 'foreach ($k in (1..10)) {Write-BetterProgress -Id 2...}' Displays the Progress for ID 2 - with the Parent ID set to 1 (this is calculated to 1 anyway, but can be overridden) CurrentOperation is optional and will display another line below if needed for more granularity. .INPUTS System.String .OUTPUTS System.Progress .NOTES Inspired by Adam Betrams wonderful take on 'A Better Way to Use Write-Progress' https://adamtheautomator.com/write-progress/ This wrapper functions supports all parameters except Completed. Please use the following to cleanly complete your progress bar (swap ID and Activity as required): Write-Progress -Id 0 -Activity $MyInvocation.MyCommand -Completed NOTE: Run this BEFORE Write-Output or RETURN commands as some terminals suffer from a bleed-through effect that super-imposes the Progress over the output: https://github.com/microsoft/vscode/issues/118661 .COMPONENT SupportingFunction .FUNCTIONALITY Uses Write-Progress without its inherent clunkyness, improving on consistency .LINK https://github.com/DEberhardt/Orbit/tree/main/docs/Orbit.Tools/Write-BetterProgress.md .LINK https://github.com/DEberhardt/Orbit/tree/main/docs/about/about_Supporting_Functions.md .LINK https://github.com/DEberhardt/Orbit/tree/main/docs/ #> [CmdletBinding()] [OutputType([String])] param ( [Parameter (Mandatory)] [int]$ID, [Parameter ()] [int]$ParentId, [Parameter (Mandatory)] [string]$Activity, [Parameter ()] [AllowNull()] [string]$Status, [Parameter ()] [AllowNull()] [string]$CurrentOperation, [Parameter (Mandatory)] [int]$Step, [Parameter ()] [int]$Of ) #param begin { #Show-OrbitFunctionStatus -Level Live #Write-Verbose -Message "[BEGIN ] $($MyInvocation.MyCommand)" # Setting Preference Variables according to Upstream settings if (-not $PSBoundParameters['Verbose']) { $VerbosePreference = $PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference') } if (-not $PSBoundParameters['Confirm']) { $ConfirmPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ConfirmPreference') } if (-not $PSBoundParameters['WhatIf']) { $WhatIfPreference = $PSCmdlet.SessionState.PSVariable.GetValue('WhatIfPreference') } if (-not $PSBoundParameters['Debug']) { $DebugPreference = $PSCmdlet.SessionState.PSVariable.GetValue('DebugPreference') } else { $DebugPreference = 'Continue' } if ( $PSBoundParameters['InformationAction']) { $InformationPreference = $PSCmdlet.SessionState.PSVariable.GetValue('InformationAction') } else { $InformationPreference = 'Continue' } } #begin process { #Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)" if ($Of -eq 0 -or -not $PSBoundParameters['of']) { $Of = 20 } else { #Transitioning from Step 1 starting at 0 to starting at 1 (needs one extra step to avoid calculation errors) #This allows calling the function with '-Step ($Step++)' #$Of++ } $WriteProgressParams = @{ Activity = $Activity PercentComplete = (($($Step - 1) / $Of) * 100) ID = $ID } if ($ID -gt 0) { $MyParentID = if ($PSBoundParameters['ParentId']) { $ParentId } else { $($ID - 1) } $WriteProgressParams.ParentId = $MyParentID } if ($PSBoundParameters['Status']) { if ( $null -ne $Status -and $Status -ne '' ) { $WriteProgressParams.Status = $Status } } if ($PSBoundParameters['CurrentOperation']) { if ( $null -ne $CurrentOperation -and $CurrentOperation -ne '' ) { $WriteProgressParams.CurrentOperation = $CurrentOperation } } try { Write-Progress @WriteProgressParams -ErrorAction Stop } catch { Write-Debug ' Progress: Catching PercentComplete Error by increasing highest step by 1. Check code' if ($PSBoundParameters['Debug'] -or $DebugPreference -eq 'Continue') { "Function: $($MyInvocation.MyCommand.Name) - Parameters (WriteProgress)", ( $WriteProgressParams | Format-Table -AutoSize | Out-String).Trim() | Write-Debug } $Of++ $WriteProgressParams.PercentComplete = (($($Step - 1) / $Of) * 100) try { Write-Progress @WriteProgressParams -ErrorAction Stop } catch { Write-Error -Message "Write-Progress failed at Step $Step of $Of - Check Activity '$Activity', Status '$Status' and optionally Current Operation '$CurrentOperation'" } } Write-Debug " Progress: Step $Step of $Of - $([math]::Round(($WriteProgressParams.PercentComplete)))% complete: $Activity" $VerboseMessage = "$Activity" + $(if ($WriteProgressParams['Status']) { " - $Status" }) + $(if ($WriteProgressParams['CurrentOperation']) { " - $CurrentOperation" }) Write-Verbose $VerboseMessage } #process end { #Write-Verbose -Message "[END ] $($MyInvocation.MyCommand)" } #end } #Write-BetterProgress |