functions/Write-AnsiProgress.ps1

function Write-ANSIProgress {
    [cmdletbinding()]
    [alias('wap')]
    [OutputType('None', 'String')]

    param (
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            HelpMessage = 'Enter a percentage in decimal value like .25'
        )]
        [ValidateScript({ $_ -gt 0 -and $_ -le 1 })]
        [double]$PercentComplete,

        [Parameter(HelpMessage = 'Specify an ANSI escape sequence or a PSStyle setting for the progress bar color.')]
        [string]$ProgressColor = "$([char]27)[38;5;51m",

        [Parameter(HelpMessage = 'Specify what shape to use for the progress bar.')]
        [ValidateSet('Box', 'Block', 'Circle')]
        [string]$BarSymbol = 'Box',

        [Parameter(HelpMessage = 'Specify the cursor position')]
        [System.Management.Automation.Host.Coordinates]$Position = $host.UI.RawUI.CursorPosition,

        [Parameter(HelpMessage = 'Write to the host not the pipeline')]
        [switch]$ToHost
    )

    begin {
        #tags are used for categorizing the command
        #cmdTags = ansi,scripting
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)"
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Running under PowerShell version $($PSVersionTable.PSVersion)"

        #Validate the progress color. The normal parameter validation techniques don't like the regex pattern
        if ( -not [regex]::IsMatch($ProgressColor, "$([char]27)\[\d+[\d;]+m")) {
            throw 'You entered an invalid ANSI escape sequence'
        }
        #the closing ANSI sequence
        $end = "$([char]27)[0m"

        #a length of 50 is the max or 100%
        $max = 50
        switch ($BarSymbol) {
            'box' { $block = [char]9632 }
            'block' { $block = [char]9608 }
            'circle' { $block = [char]9679 }
        }

        #these values will be used to set the progress bar position
        $x = $position.x
        #modified July 12, 2024 to not add an extra line
        $y = $position.y #+ 1
        #may need to insert a line in case we are at the bottom of a console or terminal screen
        if ($env:WT_SESSION -and ($position.y + 1 -ge $host.UI.RawUI.BufferSize.Height)) {
            #Write-Output "`n"
            Write-Host "`n"
        }
    } #begin

    process {
        if ($host.Name -match 'PowerShell ISE') {
            Write-Warning 'This command will not work in the PowerShell ISE.'
            #bail out
            return
        }
        Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] $PercentComplete %"
        [int]$len = $max * $PercentComplete
        #get current cursor position if in Windows Terminal
        if ($env:WT_SESSION -and ($position.y + 1 -ge $host.UI.RawUI.BufferSize.Height)) {
            $y = $host.UI.RawUI.CursorPosition.Y - 1
        }
        [System.Console]::SetCursorPosition($x, $y)
        #align and pad the percentage value
        $pct = "$($percentComplete*100)%".PadLeft(4)
        #Modified June 27, 2024 to write to the host so progress isn't part of any command output
        if ($ToHost) {
            Write-Host "$pct $ProgressColor$($($Block.ToString())*$len)$End"
        }
        else {
            Write-Output "$pct $ProgressColor$($($Block.ToString())*$len)$End"
        }
    } #process

    end {
        Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)"
    } #end

} #close Write-ANSIProgress

#EOF