DEV Community

Cover image for PowerShell Write-Host vs Write-Output: The Real Difference (and Why Your Logs Are Empty)
arnostorg
arnostorg

Posted on Edited on

PowerShell Write-Host vs Write-Output: The Real Difference (and Why Your Logs Are Empty)

You redirected a script to a log file, opened the log, and got… nothing. The console showed every status line while the script ran. The file was empty. Or you called a helper function, assigned the result, and $result was $null even though the function clearly "printed" a value.

That is not a broken redirect. That is Write-Host.

Write-Host writes for the person watching the console. Write-Output (and bare values) write objects into the success stream — the pipeline everyone else can capture, redirect, and log. Mix them up and your logs go quiet.

Everything below was run in PowerShell 7.4.6. The same stream rules apply in Windows PowerShell 5.1.

The shortest honest comparison

Write-Host Write-Output / bare value
Destination Host UI (information stream) Success stream / pipeline
$x = … captures it? No — $x is $null Yes
> log.txt / Out-File Empty file (host still prints) File has the text
Best for Progress, color, "still running…" Data a caller or log must keep

Microsoft Learn's Write-Host page says it plainly: primary purpose is host-display-only output. Write-Output sends objects to the success stream. about_Output_Streams maps stream 1 to Write-Output and stream 6 to Write-Information / Write-Host.

The empty-log bug

Here is the failure that starts most of these tickets:

& {
    Write-Host 'Starting job...'
    Write-Host 'Done.'
} > ./job.log

(Get-Item ./job.log).Length
# 0

Get-Content ./job.log -Raw
# (empty)
Enter fullscreen mode Exit fullscreen mode

The console still printed both lines. The file is zero bytes. Plain > only redirects the success stream (see about_Redirection). Write-Host never put anything there.

Swap in Write-Output:

& {
    Write-Output 'Starting job...'
    Write-Output 'Done.'
} > ./job2.log

Get-Content ./job2.log
Enter fullscreen mode Exit fullscreen mode
Starting job...
Done.
Enter fullscreen mode Exit fullscreen mode

Same shape with Out-File and the pipeline:

Write-Host 'hi-host-of' | Out-File ./log-host-of.txt
(Get-Item ./log-host-of.txt).Length
# 0

Write-Output 'hi-out-of' | Out-File ./log-out-of.txt
Get-Content ./log-out-of.txt
# hi-out-of
Enter fullscreen mode Exit fullscreen mode

Assignment and the pipeline

Capture makes the difference impossible to miss:

$x = Write-Host 'hi-host'
$null -eq $x
# True

$y = Write-Output 'hi-out'
$y
# hi-out

$z = 'hi-bare'   # bare value = same success stream as Write-Output
$z
# hi-bare
Enter fullscreen mode Exit fullscreen mode

Pipeline consumers see the same split:

(Write-Host 'hi' | Measure-Object).Count
# 0

(Write-Output 'hi' | Measure-Object).Count
# 1
Enter fullscreen mode Exit fullscreen mode

Write-Host painted hi on the screen. The next cmdlet received nothing on the success stream.

Functions that "print" return nothing useful

This is the quiet version of the empty-log bug — it shows up inside modules and helpers:

function Show-Bad {
    Write-Host 'Computing...'
    Write-Host 42
}

function Get-Good {
    Write-Host 'Computing status...'   # fine: human status
    Write-Output 42                    # data for the caller
}

function Get-Best {
    42                                 # bare value is enough
}

$a = Show-Bad
$null -eq $a
# True

$b = Get-Good
$b
# 42

$c = Get-Best
$c
# 42
Enter fullscreen mode Exit fullscreen mode

Show-Bad looked busy. The caller got $null. PSScriptAnalyzer's AvoidUsingWriteHost rule exists for exactly this: unless the verb is Show, prefer Write-Output / implicit output for data and Write-Verbose for optional chatter.

When Write-Host is the right tool

Keep Write-Host when a human watching the console is the consumer — colored prompts, step banners, "still scanning…" lines you do not want in the data pipeline:

Write-Host 'Scanning C:\Logs...' -ForegroundColor Cyan
$files = Get-ChildItem C:\Logs -File
Write-Host "Found $($files.Count) files." -ForegroundColor Green
$files | Select-Object Name, Length | Export-Csv .\report.csv -NoTypeInformation
Enter fullscreen mode Exit fullscreen mode

Status stayed on screen. The CSV only got file objects. That split is intentional.

Write-Information, stream 6, and a 7.x note

Since Windows PowerShell 5.0, Write-Host is a wrapper around Write-Information and lands on the information stream (stream 6). That is why you can capture it if you ask for stream 6 on purpose:

Write-Host 'hi-info' 6> ./log-info.txt
Get-Content ./log-info.txt
# hi-info
Enter fullscreen mode Exit fullscreen mode

Two gotchas that trip people on 7.x:

  1. Default $InformationPreference is SilentlyContinue. Write-Information stays quiet unless you set the preference or pass -InformationAction Continue. Write-Host still displays anyway — Microsoft Learn notes that $InformationPreference does not suppress Write-Host, except -InformationAction Ignore.
  2. Prefer Write-Verbose for optional diagnostics the user can turn on with -Verbose. Prefer bare values / Write-Output for anything a caller must keep.

Quick checklist

  • Building data a function, pipeline, log, or file must keep: bare value or Write-Output.
  • Banner / progress / colored console-only status: Write-Host (or Write-Information / Write-Verbose).
  • Empty log after > file or Out-File, but the console looked fine: search the script for Write-Host.
  • Function "printed" a number and the caller got $null: same root cause — host UI is not the success stream.
  • Need host text in a file on purpose: redirect stream 6 (6>), or stop using Write-Host for that data.

Practice the stream split hands-on

Reading about streams is easy; muscle memory under a broken log is harder. CMD Master's free interactive browser terminal for PowerShell, CMD, and Bash lets you run these capture and redirect experiments with no install, until empty logs stop surprising you.

What was the last script that looked busy on screen and wrote a zero-byte log?

Top comments (0)