PowerShell Write-Host vs Write-Output (and echo, Verbose, Error)

PowerShell Write-Host vs Write-Output comes down to where the text goes. Write-Output sends data down the pipeline, so you can save it or pass it on, while Write-Host only displays text on the screen:

$fromOutput = Write-Output 'Sent to the pipeline'
$fromHost = Write-Host 'Shown on screen only'

"`$fromOutput holds: '$fromOutput'"
"`$fromHost holds:   '$fromHost'"

Output:

Shown on screen only
$fromOutput holds: 'Sent to the pipeline'
$fromHost holds:   ''

Below I compare both with echo, Out-Host, Write-Information, Write-Verbose and Write-Error. Every example ran in PowerShell 7.6 and Windows PowerShell 5.1, with the same results.

Why it matters in functions

Everything a function sends to the pipeline becomes its return value. Write-Host messages stay out of it, so the caller gets only the data:

function Get-OrderTotal {
    Write-Host 'Adding up the orders...'
    Write-Output (249.99 + 89.50)
}

$total = Get-OrderTotal
"The function returned: $total"

Output:

Adding up the orders...
The function returned: 339.49
PowerShell Write-Host vs Write-Output inside a function
The progress message appears on screen, and the function returns only the total (PowerShell 7)

Switch that message to Write-Output, and it becomes part of the result:

function Get-OrderTotal {
    Write-Output 'Adding up the orders...'
    Write-Output (249.99 + 89.50)
}

$total = Get-OrderTotal
"The function returned $($total.Count) values: $($total -join ' | ')"

Output:

The function returned 2 values: Adding up the orders... | 339.49

Now $total is an array of two values, and any math on it breaks. This is the most common bug caused by mixing the two up.

Write-Output works with the pipeline

Only Write-Output passes objects to the next command. Write-Host prints its values and passes nothing along:

$a = Write-Output 'web01', 'web02', 'sql01' | Measure-Object
$b = Write-Host 'web01', 'web02', 'sql01' | Measure-Object
"Items that reached Measure-Object: Write-Output $($a.Count), Write-Host $($b.Count)"

Output:

web01 web02 sql01
Items that reached Measure-Object: Write-Output 3, Write-Host 0

In practice you rarely need to type Write-Output. A value on its own line, like “Backup complete”, is sent to the pipeline the same way.

echo is Write-Output

echo and write are built-in aliases for Write-Output, not for Write-Host:

Get-Alias -Name echo, write | ForEach-Object { '{0,-6} -> {1}' -f $_.Name, $_.Definition }

Output:

echo   -> Write-Output
write  -> Write-Output

So echo behaves exactly like Write-Output, including in functions. Use the full name in scripts that others will read.

Colors and formatting need Write-Host

Write-Host is the right choice for messages meant only for the person watching. It supports colors and -NoNewline, which Write-Output doesn’t:

Write-Host 'Checking servers...'
Write-Host 'web01  ' -NoNewline; Write-Host 'PASS' -ForegroundColor Green
Write-Host 'web02  ' -NoNewline; Write-Host 'PASS' -ForegroundColor Green
Write-Host 'sql01  ' -NoNewline; Write-Host 'FAIL' -ForegroundColor White -BackgroundColor DarkRed

Output:

Checking servers...
web01  PASS
web02  PASS
sql01  FAIL
PowerShell Write-Host with ForegroundColor, BackgroundColor and NoNewline
Green PASS and red FAIL markers, built with -NoNewline and colors (PowerShell 7)

The colors show in the console but not when the text is captured or logged.

Write-Host can still be captured

Since PowerShell 5.0, Write-Host writes to the information stream, number 6. Redirect that stream with 6>&1 and you can save the messages:

$messages = & { Write-Host 'Deploying build 2026.09.29' } 6>&1

"Captured type: $($messages[0].GetType().Name)"
"Message text:  $($messages[0].MessageData)"
"Tags:          $($messages[0].Tags -join ', ')"

Output:

Captured type: InformationRecord
Message text:  Deploying build 2026.09.29
Tags:          PSHOST

Each message comes back as an InformationRecord with the tag PSHOST. That’s how logging and CI tools collect Write-Host output.

Write-Information and Write-Verbose

These two are for messages that should be hidden until someone asks for them. Write-Information shows with -InformationAction Continue, and Write-Verbose shows with -Verbose:

function Invoke-Backup {
    [CmdletBinding()]
    param()
    Write-Information 'Backing up C:\Data'
    Write-Verbose 'Found 1,204 files'
    'Backup complete'
}

'--- Default ---'
Invoke-Backup
'--- With -InformationAction Continue ---'
Invoke-Backup -InformationAction Continue
'--- With -Verbose ---'
Invoke-Backup -Verbose

Output:

--- Default ---
Backup complete
--- With -InformationAction Continue ---
Backing up C:\Data
Backup complete
--- With -Verbose ---
VERBOSE: Found 1,204 files
Backup complete
PowerShell Write-Information and Write-Verbose shown only when requested
The same function run three times, showing the extra messages only on request (Windows PowerShell 5.1)

Both need [CmdletBinding()] on the function for those parameters to work. Use Write-Verbose for troubleshooting details, and Write-Information for status messages a caller might want to capture.

Write-Error: reporting a problem

Write-Error writes to the error stream, number 2. By default the script keeps going after it, which is called a non-terminating error:

$out = & {
    Write-Error -Message 'web03 did not respond'
    'The script kept going'
} 2>&1

"Item 1 is a(n) $($out[0].GetType().Name): $($out[0])"
"Item 2: $($out[1])"

Output:

Item 1 is a(n) ErrorRecord: web03 did not respond
Item 2: The script kept going

Callers can hide these with -ErrorAction SilentlyContinue or collect them with -ErrorVariable. Use throw instead when the script shouldn’t continue. PowerShell error variables covers the details.

Out-Hos