PowerShell Verbose Output: Add Debug Information to Scripts

PowerShell Verbose Output: Add Debug Information to Scripts

PowerShell Tips Editor 4 min read
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-Verbose to a basic function (no [CmdletBinding()]) and call it with -Verbose, the -Verbose switch is treated as an unknown parameter and throws an error, or if the function has no param block, it is silently ignored. Always pair Write-Verbose with [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 like Copy-Item. This floods the console. Prefer using -Verbose on 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.

Send-Item -To