Menu

Virtual Geek

Tales from real IT system administrators world and non-production environment

PowerShell Pro Tip: How to Overwrite and Update the Same Line ProgressBar

When writing long-running PowerShell scripts, displaying real-time feedback keeps users engaged and reassures them that the script hasn't frozen. However, flooding the console with hundreds of lines of text makes it unreadable, and using Clear-Host causes an annoying screen flicker.

The ideal solution? Update the console output right on the same line. Here are three effective ways to achieve dynamic, in-place updates in PowerShell. I have tested this on PowerShell version 7 and 5 console.

Powewrshell-script-for-custom-progressbar

Method 1: The Carriage Return (`r) Trick

The simplest way to overwrite text is by using the special carriage return escape sequence (r) combined with the -NoNewline parameter of Write-Host. The `r (backtick + r) character forces the console cursor back to the beginning of the current line without moving down, allowing the next string to overwrite the existing text.

Basic Progress Counter

for ($i = 0; $i -le 100; $i++) {
    # `r moves the cursor to the start; -NoNewline prevents a new line jump
    Write-Host -NoNewline "`rProgress: $i% complete" 
    Start-Sleep -Milliseconds 50 
}
Write-Host "`rProgress: 100% complete. Done."

 

The carriage return character moves the cursor to the beginning of the current line, allowing subsequent output to overwrite the previous content. This works well for single-line updates, such as status or individual process information. Note: The string being output must be long enough to overwrite the previous output, or you can use padding (as shown in the else block) to ensure any residual characters from a longer previous line are cleared. 

$i = 0
while ($true) {
    $processes = Get-Process
    $count = $processes.Count
    $activity = "Monitoring Processes: $count active"
    
    # You can update the status or percent complete
    Write-Progress -Activity $activity -Status "Total CPU Time (s): $(($processes | Measure-Object -Property CPU -Sum).Sum)" -PercentComplete (($i % 100) + 1)
    
    Start-Sleep -Milliseconds 500
    $i++
}

The Padding Gotcha

If your new string is shorter than the previous one, residual characters will remain on the screen. To prevent this, you can pad your string with trailing spaces to ensure the entire console line width is cleanly overwritten:

for ($i = 0; $i -le 100; $i++) {
    $output = "Progress: $i% complete"
    # Pad the string to match the current window width
    Write-Host -NoNewline "`r$($output.PadRight($Host.UI.RawUI.WindowSize.Width))"
    Start-Sleep -Milliseconds 50
}

Another way: 

while ($true) {
    # Get a specific process (e.g., 'powershell') and format its CPU usage and name
    $process = Get-Process -Name powershell -ErrorAction SilentlyContinue
    if ($process) {
        $output = "CPU: $($process.CPU.ToString('F2')) - Name: $($process.Name)"
        # Use `r to return the cursor to the start of the line, and -NoNewline to prevent a new line
        Write-Host -NoNewline "`r$output"
    } else {
        Write-Host -NoNewline "`rProcess not found or ended.    " # Pad with spaces to overwrite previous output if needed
    }
    Start-Sleep -Milliseconds 500
}


Note: This method works perfectly in standard terminal windows, but may exhibit unexpected behavior in older environments like the PowerShell ISE.

Method 2: The Native Write-Progress Cmdlet

If you want a professional, built-in user interface without manually calculating text strings and spaces, PowerShell offers the Write-Progress cmdlet. This creates a dedicated progress bar at the top of the terminal, keeping your main console output completely clean.

for ($i = 0; $i -le 100; $i++) {
    $output = "Progress: $i% complete"
    # Pad the string to match the current window width
    Write-Host -NoNewline "`r$($output.PadRight($Host.UI.RawUI.WindowSize.Width))"
    Start-Sleep -Milliseconds 50
}

The progress bar automatically vanishes once the script finishes, making it the most user-friendly approach for tracking data-heavy operations.

The Write-Progress cmdlet is specifically designed for displaying progress and status updates in a dedicated progress bar area of the console. It is less intrusive than repeatedly overwriting the main screen output and handles the display logic automatically. 

$i = 0 
while ($true) {
    $processes = Get-Process
    $count = $processes.Count
    $activity = "Monitoring Processes: $count active"
    
    # You can update the status or percent complete
    Write-Progress -Activity $activity -Status "Total CPU Time (s): $(($processes | Measure-Object -Property CPU -Sum).Sum)" -PercentComplete (($i % 100) + 1)
    
    Start-Sleep -Milliseconds 500
    $i++
}

You would stop this with Ctrl+C. This method uses a separate visual element and doesn't interfere with your main command output area. 

Method 3: Advanced Cursor Positioning with .NET

For complex scripts where you need to update multiple specific lines at once (like a live dashboard), you can bypass standard cmdlets and manipulate the terminal cursor directly using the .NET [Console] class.

Clear-Host
# Save the original cursor position
$origPos = $Host.UI.RawUI.CursorPosition
while ($true) {
    $processes = Get-Process | Select-Object -First 5 Id, Name, CPU, WS
    
    # Reset the cursor to the saved starting point
    [Console]::SetCursorPosition($origPos.X, $origPos.Y)
    
    Write-Host "Top 5 Processes (updated every 1s):"
    $processes | Format-Table -AutoSize
    
    Start-Sleep -Seconds 1
}

For more advanced control over screen position (e.g., updating a specific set of lines), you can use the .NET [Console] class to set the cursor position. This approach is more complex but offers greater flexibility for multi-line dynamic output. 

Clear-Host
# Save the original cursor position
$origPos = $Host.UI.RawUI.CursorPosition
while ($true) {
    $processes = Get-Process | Select-Object -First 5 Id, Name, CPU, WS
    [Console]::SetCursorPosition($origPos.X, $origPos.Y)
    Write-Host "Top 5 Processes (updated every 1s):"
    $processes | Format-Table -AutoSize
    
    Start-Sleep -Seconds 1
    # To avoid constant scrolling, you might need to manage screen buffer size or clear the specific area
}

This last method requires careful management of screen real estate and works best in the standard PowerShell console window, not necessarily in the PowerShell ISE or some third-party terminals. 

Bonus: Putting It Together (Live Text Spinner)

You can combine the carriage return technique with a basic array to build a smooth, live loading indicator that monitors time and percentage simultaneously:

$totalSeconds = 360
$updateIntervalSeconds = 1          # spinner refresh
$progressIntervalSeconds = 60       # 1 minute per progress update

$spinner = @('/', '|', '-', '\')
$spinnerIndex = 0

$startTime = Get-Date
$endTime = $startTime.AddSeconds($totalSeconds)

while ((Get-Date) -lt $endTime) {

    $elapsedSeconds = (Get-Date) - $startTime
    $elapsedSeconds = [int]$elapsedSeconds.TotalSeconds
    # Calculate progress in minutes
    $progressPercent = [math]::Min(
        [math]::Floor(($elapsedSeconds / $totalSeconds) * 100),
        100
    )
    $elapsedMinutes = [math]::Floor($elapsedSeconds / 60)
    $spinChar = $spinner[$spinnerIndex % $spinner.Count]
    $spinnerIndex++
    Write-Host -NoNewline "`r$spinChar Running... $elapsedMinutes minute(s) elapsed | Progress: $progressPercent%"
    Start-Sleep -Seconds $updateIntervalSeconds
}
Write-Host "`r✔ Completed: 6 minutes elapsed | Progress: 100%        

Summary

  • Use `r for quick, single-line status updates.

  • Use Write-Progress for standard, professional installation bars.

  • Use [Console]::SetCursorPosition for multi-line monitoring dashboards.

Useful Articles
Installing, importing and using any module in powershell
Microsoft PowerShell: Check Windows license activation status
Find next available free drive letter using PowerShell
Copy Files with PowerShell Remoting WINRM Protocol
Powershell Find application window state minimized or maximized
How to Install and Use Microsoft PowerShell on Linux
Configure PowerShell remoting between Windows and Linux
Get-PSRepository WARNING Unable to find module repositories
Invoke-WebRequest : The underlying connection was closed: An unexpected error occurred on a send
Creating an internal PowerShell module repository
How to sign PowerShell ps1 scripts
PowerShell Convert MAC address to Link-local address IPv6
PowerShell fix repair The trust relationship between this workstation and the primary domain failed
Resovled issue with PowerShell - Trust relationship Rejoin computers in domain without restart

Go Back

Comment

Protected by Mathcha

Blog Search

Page Views

1 5 1 4 6 0 3 2

Archive

Follow me on Blogarama