- 🤯
Write-Progressbypasses normal output streams and writes directly to the host UI. - 🔄
$ProgressPreferenceonly determines if progress is shown—not where it's sent. - 📁 Redirecting or capturing standard output won’t catch
Write-Progressmessages. - 🧪 Good alternatives include using
Write-Output,Write-Verbose, orWrite-Information. - 🛠️ Custom host UI elements or conditional logic can make scripts stronger and easier to test.
Understanding $ProgressPreference and Redirecting Streams in PowerShell
If you’ve written a PowerShell script with a good progress bar using Write-Progress, only to find that the progress disappears when redirecting output to a file, you are right to notice this. It is not easy to capture PowerShell progress output. This is because PowerShell keeps host visual output separate from other output data. This guide will explain why $ProgressPreference does not work well with redirection. And then we will look at how PowerShell manages different output streams. We will also cover ways to redirect streams, fake progress, or capture output for scripts and automated tasks.
What Is $ProgressPreference in PowerShell?
PowerShell includes some built-in preference variables that affect script behavior. Among these, $ProgressPreference controls if Write-Progress shows anything. It tells PowerShell to show progress, hide it, or give an error when it sees progress messages.
Main Modes of $ProgressPreference
| Mode | Description |
|---|---|
Continue |
(Default) Shows progress indicators in the host interface. |
SilentlyContinue |
Hides all Write-Progress calls, showing nothing. |
Stop |
Gives an error when Write-Progress is called. |
Inquire |
Asks the user before running the progress call. |
Ignore |
Does not show or interact with progress in any way. |
You can change the mode at any time with a simple assignment:
$ProgressPreference = 'SilentlyContinue'
But keep this in mind: Even when Write-Progress messages are allowed (using Continue), you cannot redirect or capture them like other output or error messages.
PowerShell Streams: A Quick Look at How Output Is Handled
PowerShell is different because it uses many streams for various types of messages. Other shells do not do this. PowerShell lets you control exactly where each message goes.
Overview of PowerShell Streams
| Stream # | Name | Description |
|---|---|---|
| 1 | Output | Standard data results from pipeline commands. |
| 2 | Error | Exceptions, script errors, and stopping/non-stopping errors. |
| 3 | Warning | Warnings shown to the user with Write-Warning. |
| 4 | Verbose | More detailed output using Write-Verbose. |
| 5 | Debug | Diagnostic messages sent with Write-Debug. |
| 6 | Information | Rich data captured with Write-Information. |
| N/A | Progress | Special UI messages managed with Write-Progress, skipping the stream. |
The progress stream is not part of the regular numbered streams. It skips the normal output channels. Instead, it talks straight to the host interface using the .NET class System.Management.Automation.Host.PSHostUserInterface. This design lets progress messages show up clearly in the console. It keeps them out of logs or pipelines. But it also makes them hard to capture.
Why Redirecting Progress Output Doesn’t Work
Progress bars do not show up in redirected output. The main reason is that they are not part of any normal stream. You can capture output, error, and verbose messages with >, 2>, or *>. But progress messages are tied to the user interface.
Example
Script:
Write-Progress -Activity "Testing" -Status "50% Complete" -PercentComplete 50
Redirection:
.\myscript.ps1 > log.txt
You might expect the progress bar to show up in log.txt. But it does not. Nothing gets captured. This is because Write-Progress never sends anything to standard output or any other stream. PowerShell simply calls $Host.UI.WriteProgress() instead.
In short, redirection cannot capture progress messages. They are not part of a stream.
Practical Example: Testing Stream Redirection
Let's look at an example to see how PowerShell streams work when you redirect them.
Test Script
for ($i = 1; $i -le 5; $i++) {
Write-Progress -Activity "Processing" -PercentComplete ($i * 20)
Start-Sleep -Milliseconds 300
"Step $i complete"
}
Behavior Scenarios
A. Terminal Execution
.\script.ps1
- ✅ Progress display visible in the console.
- ✅
Step X completemessages display normally.
B. Redirect to File
.\script.ps1 > output.txt
- ❌ Progress bar absent.
- ✅ Only
Step X completemessages appear inoutput.txt.
C. Pipeline Capture
.\script.ps1 | Tee-Object -FilePath output.txt
- ❌ No progress bar captured.
- ✅ Text lines are written to both terminal and file.
This test shows that Write-Progress messages are only for seeing things. They are not data that goes into the PowerShell pipeline.
Common Misconceptions Among Developers
Many PowerShell users think you can redirect anything shown in the terminal. This is true for older command-line tools, like bash or cmd. But it is not true for PowerShell.
Misunderstood Assumptions
- ❌ "Write-Progress works like Write-Output"
- ❌ "Progress messages flow through stdout"
- ❌ "Redirection captures all visible console content"
- ❌ "All IDEs handle host output the same way"
Actually, you can only redirect content from standard streams. Progress visuals work in a different way.
For example, using Visual Studio Code can make this more confusing. Some hosts there might show progress in a different way, or not at all.
How to Fake or Capture PowerShell Progress Output
Standard Write-Progress visuals do not appear in logs or redirected output. So you will need to fake them. PowerShell has a few ways to do this:
1. Use Write-Verbose Instead
This works well if you run scripts with the -Verbose switch.
$ProgressPreference = 'SilentlyContinue'
Write-Verbose "Processing: 40% complete"
2. Output Custom Progress Messages
Plain text output works well in logs or output files.
for ($i = 1; $i -le 5; $i++) {
$percent = $i * 20
Write-Output "[Progress] $percent% complete"
Start-Sleep -Milliseconds 300
}
Log that to a file:
.\script.ps1 > progress.log
Now progress info is actually retained.
3. Use Write-Information
Structured information stream:
Write-Information "Step completed: $i/5" -InformationAction Continue
4. Build a Text-Based Progress Bar
Example:
for ($i = 1; $i -le 5; $i++) {
$bar = '#' * $i + '.' * (5 - $i)
Write-Output "[${bar}] $($i * 20)% complete"
Start-Sleep -Milliseconds 500
}
This creates something that looks like a progress bar. And you can easily capture it in logs.
When You Should Turn Off Progress Output
In many scripts, you do not need to see visual feedback. It can even be a problem.
Situations to Avoid Write-Progress
- Continuous Integration (CI/CD) environments
- Docker containers or headless Linux hosts
- Scheduled Tasks and background jobs
- Scripts that require full output logging
In these cases, hide progress with:
$ProgressPreference = 'SilentlyContinue'
Or apply it everywhere:
$PSDefaultParameterValues["*:ProgressPreference"] = "SilentlyContinue"
Faking Logging with Progress History
For long scripts, you might want a record of what happened. Then, it is better to log progress in an organized way.
Log-Friendly Format
for ($i = 1; $i -le 5; $i++) {
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$percent = $i * 20
Write-Output "[$timestamp] Progress: $percent% complete"
}
When you send this to a file, it works well for logs, alerts, or tracking.
Host UI vs. Pipeline: Why Progress Is Different
The PowerShell host shows things on screen using the PSHostUserInterface. This is how they are set up:
Write-Progress→$Host.UI.WriteProgress()Write-Output→ Sent through stream #1Write-Verbose→ Stream #4, only if$VerbosePreferenceallows
You can look at your host interface capabilities:
$Host.UI.RawUI
Complex cases might involve changing or adding to host interfaces for special display rules. But for most users, it is best to use stream-safe options.
Testing and Fixing Progress Behavior
Here are some ways to check and fix progress display issues:
Add Auxiliary Streams
Write-Progress ...
Write-Verbose "Processing halfway..." -Verbose
Use Transcripts
Start-Transcript -Path "debug-log.txt"
# your script code here...
Stop-Transcript
The transcript will record what the host does. This includes progress visuals that normally do not show up in logs.
PowerShell 7 and Cross-Platform Behavior
In PowerShell Core (version 6 and newer) and PowerShell 7, $ProgressPreference works the same way. But how it looks can be different.
Tips for Cross-Platform Scripting
- macOS and Linux terminals might not show interactive progress at all.
- Use organized logging (output, verbose, or information streams) for full compatibility.
- Use if/then logic to decide if showing progress is a good idea.
Example:
if ($IsWindows -and $Host.Name -like "*ConsoleHost*") {
Write-Progress ...
}
Best Practices for Capturing Progress in a Script
To make your scripts easy to move and predict:
✅ Use $ProgressPreference = 'SilentlyContinue' in automation
✅ Fall back to Write-Output for human-readable progress messages
✅ Avoid relying solely on Write-Progress for data visibility
✅ Test in all expected hosts: terminal, CI pipeline, IDE, etc.
Take Control of Your PowerShell Progress Output
PowerShell's stream model and host design keep visual user interface tasks separate from data processing on purpose. When you understand how this is set up, you can find a good balance. This means balancing how much your scripts interact with users and how much they automate.
Using stream-safe options instead of Write-Progress helps with:
- Better logging
- More consistent behavior across different systems
- Clearer information for fixing issues or for other team members
You might be making scripts for DevOps automation. Or you might be creating complex user processes. Either way, being smarter about progress output helps your PowerShell code. It makes sure the results are right, easy to read, and easy to keep up. This is true no matter where or how you run it.
Citations
- PowerShell Docs – about_Preference_Variables
- Stack Overflow – PowerShell: Write-Progress output to file?
- GitHub – PowerShell Issue: Progress shown in console but doesn’t appear in redirected output
- Microsoft Learn – about_Redirection
- Script Example from Devsolus – https://devsolus.com