Skip to content

🩹 [Patch]: PowerShell Write-Verbose and Write-Debug #10

@MariusStorhaug

Description

@MariusStorhaug

Describe the change

Below are clear, concise guidelines for using Write-Debug and Write-Verbose in PowerShell, distilled from industry best practices and practical reasoning:


1. Write-Verbose: For "What" and "High-Level Progress"

Use Write-Verbose to log:

  • What the code is doing (e.g., steps, milestones, progress).
  • Non-sensitive operational details (e.g., "Connecting to database...", "Processing 100 items...").
  • User-triggered insights for troubleshooting or auditing workflows.

Example:

Write-Verbose "Starting data import from $FilePath"  
Write-Verbose "Validating user permissions for $UserName"  

Why?

  • Enabled via -Verbose switch or $VerbosePreference.
  • Ideal for users/administrators who need context without technical depth.

2. Write-Debug: For "Why" and "Diagnostic Details"

Use Write-Debug to expose:

  • Internal state (e.g., variable values, conditions, loop counters).
  • Diagnostic clues for developers (e.g., "Value of $x is 42", "Skipping item due to condition X").
  • Critical decision points where execution flow may break.

Example:

Write-Debug "Parameter validation failed: $ErrorDetails"  
Write-Debug "Retry count: $Retry (Max: $MaxRetries)"  

Why?

  • Enabled via -Debug switch or $DebugPreference = 'Continue'.
  • Pauses execution (by default) to allow developer inspection of state.
  • Targets script authors/maintainers during debugging.

3. Key Differences & Rules of Thumb

Aspect Write-Verbose Write-Debug
Audience End-users, admins, support teams Developers, maintainers
Content What is happening Why something happened (diagnostics)
Execution Impact Non-blocking Pauses execution (when -Debug used)
Use Case Logging, progress tracking Deep troubleshooting, variable dumps
Default Visibility Hidden (requires -Verbose) Hidden (requires -Debug)

4. When to Avoid Both

  • Never expose secrets (passwords, keys) via either stream.
  • Avoid trivial messages (e.g., "Loop iteration 1", "Function started") unless they add value.
  • Prefer Write-Error/Write-Warning for actual errors/warnings.

5. Best Practices

  • Use [CmdletBinding()] in functions to enable -Verbose/-Debug support.
  • Leverage pipeline: Pipe verbose/debug output to logs during automation.
  • Conditional checks: For performance-critical code:
    if ($PSCmdlet.MyInvocation.BoundParameters["Verbose"]) { ... }
  • Consistency: Adopt a team standard (e.g., "Verbose for milestones, Debug for state dumps").

Example Implementation

function Get-SystemInfo {
    [CmdletBinding()]
    param()
    Write-Verbose "Initializing system scan..."
    $os = Get-CimInstance Win32_OperatingSystem
    Write-Debug "OS details: $($os | Out-String)"
    
    Write-Verbose "Calculating uptime..."
    $uptime = (Get-Date) - $os.LastBootUpTime
    Write-Debug "Uptime calculated: $($uptime.TotalHours) hours"
    
    Write-Verbose "Scan complete."
}

Summary

  • Write-Verbose = User-focused progress tracker.
  • Write-Debug = Developer-focused diagnostics tool.
  • Rule: If you’re explaining behavior, use Verbose. If you’re exposing state, use Debug.

Adhering to these guidelines ensures your scripts are maintainable, user-friendly, and debuggable while avoiding noise.


Best Practices: Using Write-Verbose vs. Write-Debug in PowerShell Scripts

Understanding Verbose vs. Debug Output

PowerShell provides two streams for optional diagnostic output: Verbose and Debug. Both are suppressed by default, and only show up when enabled (via script parameters or preference variables). However, they serve different purposes:

  • Write-Verbose writes text to the verbose stream. These messages are meant for trace/log information about script execution (e.g. progress, decisions made, values of parameters). They do not display unless you run the script with the -Verbose switch (which sets $VerbosePreference='Continue'). In normal runs, they stay hidden, keeping output clean.
  • Write-Debug writes text to the debug stream. These messages are intended for debugging and troubleshooting deeper issues. By default they are also hidden ($DebugPreference='SilentlyContinue'), but if you run with the -Debug switch, PowerShell sets $DebugPreference='Inquire'. This means it not only displays debug messages, but also pauses execution at each Write-Debug call and prompts you for action (entering an interactive debug mode). In other words, Write-Debug can act like a breakpoint in your script when -Debug is used.

Understanding this difference is key to using them effectively. In summary, use Write-Verbose for optional runtime logs and Write-Debug for developer-centric debugging. Below are guidelines on when to choose each.

When to Use Write-Verbose

Use Write-Verbose for informational messages about normal script operations that can help users or maintainers understand what the script is doing when needed (but aren’t necessary in default output). Some guidelines for using Write-Verbose:

  • Trace Script Flow and Progress: Emit messages for major steps or decision points in your script (e.g. "Connecting to server X", "Processed N items", "Operation completed successfully"). This makes it easier to follow the script’s flow when verbose mode is on. By including such messages at each significant step, you provide a built-in way to see what the script is doing without cluttering standard output.

  • Troubleshooting and Auditing: Rely on verbose output to diagnose issues. In day-to-day runs, the script will be quiet, but if a problem arises, running it with -Verbose will reveal the internal steps and data involved. This is a common practice in PowerShell – run with -Verbose when you need to troubleshoot, and run normally when you don’t.

  • Non-Intrusive Logging: Because verbose messages are hidden by default, you can add them generously without worrying about spamming the console or logs in normal operation. In fact, a well-written script “uses Write-Verbose a lot” to provide insight when needed. Think of verbose messages as optional log entries or “live documentation” of what your code is doing.

  • Example – Using Write-Verbose:

    function Update-Data {
        [CmdletBinding()]  # Enables -Verbose automatically
        param($Server, $File)
        Write-Verbose "Connecting to server $Server"
        # ... perform connection
        Write-Verbose "Importing data from $File"
        # ... perform import
        Write-Verbose "Operation completed."
    }

    If you run Update-Data -Server "Prod01" -File "data.csv" -Verbose, you might see output like:
    VERBOSE: Connecting to server Prod01
    VERBOSE: Importing data from data.csv
    VERBOSE: Operation completed.
    These messages won’t appear without -Verbose, so in normal runs the function would only output actual results or nothing at all.

Why Write-Verbose: It provides optional runtime information to the user or operator. This is ideal for scripts used in both automated and interactive scenarios – it keeps default output clean, but allows anyone running the script to opt-in to detailed logs when needed. Always favor Write-Verbose for general logging over something like Write-Host, because verbose messages go to a dedicated stream that can be enabled, captured, or ignored easily.

When to Use Write-Debug

Use Write-Debug for debugging messages that are primarily of interest to developers or advanced users diagnosing problems. These are deeper insights or checkpoints in code execution, used less frequently than verbose messages. Guidelines for Write-Debug:

  • Interactive Debugging Breakpoints: A Write-Debug call acts like a breakpoint when the script is run with -Debug. PowerShell will halt at that line and prompt the user (with options to continue, suspend, etc.). This is extremely useful during development or troubleshooting a script interactively – you can inspect variables, step through code, and then continue execution. For example, you might sprinkle a few Write-Debug "Checkpoint: variable X = $X" lines in a loop or complex function, so that if you run -Debug you can examine state at those points.

  • Extra-Verbose Logging (Developer Focused): Use Write-Debug for messages that are even more detailed or technical than verbose output – for instance, dumping full objects or internal data structures that typical users don’t need to see. These messages can be output without breaking execution if you adjust $DebugPreference, but by default they won’t appear at all unless debugging is enabled. Think of Write-Debug as a way to get “even deeper” logs when you explicitly want them. For example, after a complex calculation you might include Write-Debug "Calculated matrix: $(ConvertTo-Json $matrix)" so you can review the data structure when debugging.

  • Use Sparingly in Production Code: Because Write-Debug is tied to the interactive debugging mode, use it thoughtfully. It’s best for scenarios where a developer (or power user) might intentionally run the script with -Debug to diagnose an issue. It’s less suitable for routine logging – you wouldn’t generally turn on -Debug in daily runs or in automation, since it will pause for input by default. Many teams actually rely more on verbose logging and rarely use Write-Debug unless they plan to attach a debugger or step through manually. If you do include Write-Debug statements, ensure they serve a clear purpose for debugging, and document that they exist (so others know running with -Debug will trigger breakpoints).

  • Example – Using Write-Debug:

    function Get-Config {
        [CmdletBinding()]
        param($Path)
        Write-Verbose "Reading configuration from $Path"
        $config = Import-Clixml $Path
        Write-Debug "Raw config data: $($config | Out-String)"
        # ... process $config
    }

    In normal runs, Get-Config just reads the file. If run with -Verbose, it will print the "Reading configuration..." message. If run with -Debug, PowerShell will pause when it hits the Write-Debug line, showing the debug message and entering debug mode. This allows the operator to inspect $config (since we output its raw data in the debug message) before continuing. If you simply want to see the debug message without stopping, you could manually set $DebugPreference = "Continue" and run the function (this would print DEBUG: Raw config data: ... to the console without prompting). However, in most cases it's easier to reserve Write-Debug for interactive use.

Why Write-Debug: It provides a built-in mechanism for interactive debugging in PowerShell. When developing or maintaining scripts, Write-Debug lets you mark spots to check, and if something goes wrong, you can rerun the script with -Debug to drop into a debug session at those spots. It’s like placing breakpoints via code. For everyday logging or user-facing detail, though, Write-Verbose is the better choice (since it won't interrupt execution).

Best Practices for CI/CD Pipeline Runs (Automation)

When running PowerShell scripts in CI/CD or other non-interactive automation, you need to be careful with debug vs verbose output:

  • Prefer Verbose Logging in CI/CD: In automated pipelines, you typically want to keep output minimal unless there’s a failure or need for diagnostics. Using Write-Verbose allows you to emit detailed logs that can be turned on when needed. For example, you might run your deployment script normally to keep logs clean, but if a pipeline job fails or you need more insight, you can re-run it with the -Verbose flag (or set an environment variable to enable verbose output) to get the detailed trace of what the script is doing. This is a standard practice: run verbose logs “on demand” for troubleshooting, but not always by default.
  • Avoid Interactive Debug Prompts: Do not use -Debug in a CI/CD run. The -Debug switch will set PowerShell to inquire mode and pause on each Write-Debug, expecting a person to respond. In a pipeline, there is no interactive user, so the script would hang indefinitely. As a rule, never enable full debug mode in unattended execution. Any Write-Debug statements in your code will simply be skipped over (since $DebugPreference stays SilentlyContinue), which is usually fine. Design your scripts such that all necessary logging for automation is done via Write-Verbose (and Write-Warning/Write-Error as appropriate), reserving Write-Debug for human troubleshooting sessions.
  • Capturing Verbose Output: Ensure your pipeline is set up to capture or display verbose output when enabled. Many CI systems will print verbose messages if the script is run with -Verbose. For instance, Azure Pipelines treats verbose and debug streams as “diagnostic logs” that show up when you enable debugging in the pipeline. Check your CI tool’s documentation – you might need to set a variable (like System.Debug=true in Azure DevOps) or run the script with a parameter to see verbose output. This way, developers can get detailed logs from pipeline runs without modifying the script.
  • Use Debug Messages Only for Development: It’s okay to include Write-Debug in your script for the benefit of developers, but consider them inactive in CI. They will not appear unless someone intentionally runs with debug mode (which we avoid in CI) – essentially, they serve as inline breakpoints or deep diagnostics for use on a local machine. If you find yourself needing the kind of information Write-Debug provides during an automated run, that’s a sign you should promote that information to a verbose message or explicit log output. In other words, pipeline logging should rely on verbose output and proper error handling, not on debug breaks.

Best Practices for Interactive/Local Use

When running scripts on a local machine or in an interactive session (e.g. a developer running the script in PowerShell console or ISE), you can leverage both verbose and debug output for a better experience:

  • Leverage -Verbose for Insight: Encourage your team to run scripts with -Verbose if they want to understand the script’s behavior or troubleshoot an issue. The verbose messages you added will act as a guided commentary of the script’s actions. This is especially helpful for new users of the script or when running a long complex process – they can see that “now we’re doing step X, now step Y,” etc., which provides reassurance and insight. It’s an optional comfort level for interactive use.
  • Use -Debug for Troubleshooting Code: When a developer needs to step through the script logic, they can run with the -Debug switch. As explained, this will pause at each Write-Debug call and allow the person to examine or modify variables, then continue or quit. This is a powerful way to troubleshoot live – essentially, your Write-Debug calls are predefined breakpoints. For example, running a script with -Debug might yield output like:
    DEBUG: Raw config data: {...}
    Confirm Debugging? [Y]es [A]ll [H]alt ... (this is the prompt on a Write-Debug line).
    The user can press Suspend to enter the debugger, inspect the environment (check variables, run commands), then type Continue to resume the script. This technique should be used when you suspect a logic error or need to inspect intermediate state that verbose logs alone aren’t explaining. It’s especially useful during development or when replicating an issue locally that was hard to diagnose just from logs.
  • Combine Verbose and Debug for Clarity: You can use both output types in a script. In fact, advanced functions support both -Verbose and -Debug simultaneously. For example, you might include many Write-Verbose messages for general steps, and a few targeted Write-Debug messages for critical internals. A developer can run with both -Verbose and -Debug switches at once – verbose messages will stream out continuously, and the script will pause at debug points. This gives a full picture: the verbose output shows the high-level flow, and the debug breakpoints let the dev inspect state at key moments. While this is powerful, it’s up to the developer’s discretion to use it. In normal interactive use, -Verbose alone is usually sufficient to understand script behavior, and -Debug is only invoked when deeper investigation is needed.
  • Tip: Enable CmdletBinding: Always define your scripts or functions with [CmdletBinding()] (and use a param() block) to automatically enable the -Verbose and -Debug common parameters. This way, you don’t have to manually code switches for verbose/debug; PowerShell will handle it. Then calls to Write-Verbose/Write-Debug will respect the user’s -Verbose/-Debug choice. In interactive scenarios, this means any user can easily toggle verbose or debug mode when running the script, without any extra work on your part.

Summary

Write-Verbose and Write-Debug are invaluable tools for building maintainable PowerShell scripts. As a simple rule of thumb: use Write-Verbose for optional informational output (logging the script’s normal operations), and use Write-Debug for optional debugging output (investigating issues during development). Verbose output is the go-to for running in both CI pipelines and everyday use with minimal risk – it provides detail on demand without disrupting execution. Debug output is there for the rare occasions you need to pause and inspect the inner workings interactively, but should be enabled intentionally and avoided in automated runs.

By following these guidelines, developers can instrument their scripts with useful messages that stay out of the way during normal operation, yet greatly assist in troubleshooting and debugging when the need arises. This leads to cleaner scripts, easier debugging, and more transparency in both automated and interactive environments.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions