PowerShell Verbose Output: Add Debug Information to Scripts

Scripts that run silently are great for automation — until something goes wrong and you have no information about what the script was doing. PowerShell verbose output solves this elegantly: Write-Verbose messages are invisible by default but appear instantly when you add -Verbose to the call. Your script stays quiet in production and becomes diagnostic on demand without changing a single line of logic code.
Quick Answer / TL;DR
Add [CmdletBinding()] to your function, then use Write-Verbose 'message' throughout. Run the function with -Verbose to see the messages. Without [CmdletBinding()], -Verbose has no effect.
Write-Verbose Basics
Write-Verbose writes to the verbose output stream (stream 4), not the standard output or error streams. Messages appear prefixed with VERBOSE: in cyan text. They are controlled by the $VerbosePreference variable, which defaults to SilentlyContinue — meaning all verbose messages are discarded unless explicitly enabled.
function Get-ServerStatus {
[CmdletBinding()]
param([string]$ComputerName)
Write-Verbose "Connecting to $ComputerName"
$services = Get-Service -ComputerName $ComputerName -ErrorAction Stop
Write-Verbose "Retrieved $($services.Count) services"
$stopped = $services | Where-Object Status -ne Running
Write-Verbose "Found $($stopped.Count) stopped services"
$stopped
}
Enable Verbose with -Verbose Flag
When a function has [CmdletBinding()], it automatically gains a -Verbose switch parameter. Pass -Verbose when calling the function to enable verbose output for that invocation only. This is the preferred approach because it limits verbose chatter to when you specifically request it.
# Normal run — no verbose output
Get-ServerStatus -ComputerName server01
# Verbose run — see all Write-Verbose messages
Get-ServerStatus -ComputerName server01 -Verbose
VERBOSE: Connecting to server01
VERBOSE: Retrieved 142 services
VERBOSE: Found 3 stopped services
$VerbosePreference Variable
$VerbosePreference controls the default behavior for all verbose output in the current session. Setting it to Continue enables all verbose messages without needing -Verbose on every call. This is useful when debugging a complex script with many function calls. Reset to SilentlyContinue when done to restore quiet behavior.
# Enable verbose globally for debugging session
$VerbosePreference = 'Continue'
# Now all Write-Verbose messages show without -Verbose flag
Get-ServerStatus -ComputerName server01
# Restore quiet mode
$VerbosePreference = 'SilentlyContinue'
Require CmdletBinding for -Verbose Parameter
Without [CmdletBinding()] at the top of a function, the -Verbose switch does nothing — Write-Verbose messages are suppressed regardless. This is the most common reason verbose output seems broken. Adding [CmdletBinding()] promotes the function to an advanced function with full common parameter support including -Verbose, -Debug, -ErrorAction, and -WhatIf.
# Without [CmdletBinding()] — -Verbose is ignored
function Bad-Example {
param([string]$Name)
Write-Verbose "Processing $Name" # never shows
}
# With [CmdletBinding()] — -Verbose works correctly
function Good-Example {
[CmdletBinding()]
param([string]$Name)
Write-Verbose "Processing $Name" # shows when -Verbose is passed
}
Good-Example -Name 'test' -Verbose
Write-Debug for Developer Info
Write-Debug behaves similarly to Write-Verbose but targets the debug output stream (stream 5) and is controlled by $DebugPreference and the -Debug common parameter. Use Write-Debug for low-level implementation details that developers need but operators do not. When $DebugPreference is Inquire, PowerShell pauses and prompts at each debug message — useful for step-through debugging.
function Process-Data {
[CmdletBinding()]
param($InputData)
Write-Verbose "Starting processing of $($InputData.Count) items"
foreach ($item in $InputData) {
Write-Debug "Item value: $item, Type: $($item.GetType().Name)"
# process...
}
Write-Verbose 'Processing complete'
}
# Show debug messages
Process-Data -InputData @(1,2,3) -Debug
Verbose in Module Functions
When a module function is called with -Verbose, the verbose preference propagates into the function automatically. If your module function calls other advanced functions internally, they also respect the verbose preference that was set by the caller — verbose output flows through the entire call stack cleanly.
# In your module .psm1 file
function Invoke-DeployStep {
[CmdletBinding()]
param(
[string]$AppName,
[string]$Environment
)
Write-Verbose "[$AppName] Starting deployment to $Environment"
Copy-FilesToTarget -AppName $AppName -Verbose:$VerbosePreference # propagate
Register-Service -AppName $AppName -Verbose:$VerbosePreference
Write-Verbose "[$AppName] Deployment complete"
}
Common Errors and Fixes
- Write-Verbose only shows if CmdletBinding() declared in function. If you add
Write-Verboseto a basic function (no[CmdletBinding()]) and call it with-Verbose, the-Verboseswitch is treated as an unknown parameter and throws an error, or if the function has no param block, it is silently ignored. Always pairWrite-Verbosewith[CmdletBinding()]. - Setting $VerbosePreference affects all commands in session. Setting
$VerbosePreference = 'Continue'globally turns on verbose output for every cmdlet in the session, including built-in cmdlets likeCopy-Item. This floods the console. Prefer using-Verboseon individual calls or wrap the debug section in a try/finally that resets the preference.
Related Cmdlets / See Also
Wrapping Up
Add [CmdletBinding()] and Write-Verbose to every function you write from the start — the cost is two lines, the benefit is on-demand diagnostic output without touching the code when things go wrong. Use Write-Debug for implementation-level details and reserve Write-Error and Write-Warning for conditions the caller needs to handle.


