Skip to content

PowerShell Debugging

How to use PowerShell's built-in debugger with breakpoints, variable inspection, and stepping commands to identify and fix script errors efficiently.

Debugging is the process of identifying, isolating, and fixing bugs, logic errors, and unexpected state changes within a script. PowerShell provides a built-in interactive command-line debugger with breakpoints, variable monitors, and execution stepping, alongside deep integration with modern development environments like Visual Studio Code.

Interactive Breakpoints with Set-PSBreakpoint

A breakpoint is an intentional stopping point placed in a script where the PowerShell runtime engine pauses execution. Once paused, you enter the interactive debugger prompt ([DBG]: PS>), allowing you to inspect variable values, evaluate expressions, and step through code line by line.

The Set-PSBreakpoint cmdlet creates breakpoints based on lines, commands, or variable modifications.

Breakpoint Types

Type Parameter Description Example
Line Breakpoint -Line Pauses execution before a specific line number runs. Set-PSBreakpoint -Script "app.ps1" -Line 15
Command Breakpoint -Command Pauses execution whenever a specific cmdlet or function is called. Set-PSBreakpoint -Command "Stop-Service"
Variable Breakpoint -Variable Pauses execution when a target variable is read, written, or both. Set-PSBreakpoint -Variable "Total" -Mode Write

Example: Breaking on a Line Number

Consider a simple script that calculates a running total:

debug-target.ps1powershell
$total = 0

foreach ($i in 1..5) {
    $total += $i
    Write-Output "Running total: $total"
}

Write-Output "Final total: $total"

Set a breakpoint on line 4 and run the script:

Consolepowershell
Set-PSBreakpoint -Script ./debug-target.ps1 -Line 4
./debug-target.ps1
Outputplaintext
Hit Line breakpoint on 'C:\Scripts\debug-target.ps1:4'

At C:\Scripts\debug-target.ps1:4 char:5
+     $total += $i
+     ~~~~~~~~~~~~
[DBG]: PS C:\Scripts>>

Example: Breaking on a Variable

Consolepowershell
Set-PSBreakpoint -Script ./debug-target.ps1 -Variable "total" -Mode Write

This pauses execution whenever $total is modified.

Example: Breaking on a Command

Consolepowershell
Set-PSBreakpoint -Script ./debug-target.ps1 -Command "Write-Output"

This pauses execution immediately before every call to Write-Output within the script.

Managing Breakpoints

PowerShell includes a full cmdlet family for inspecting and controlling breakpoints:

Consolepowershell
Get-PSBreakpoint

Disable-PSBreakpoint -Id 0

Enable-PSBreakpoint -Id 0

Get-PSBreakpoint | Remove-PSBreakpoint

The Debugger Prompt and Stepping Commands

When execution hits a breakpoint, the terminal prompt changes to [DBG]: PS C:\...>>. At this prompt, you can type variable names directly to inspect their current in-memory values, or use single-letter navigation commands:

Command Action Description
s Step Into Executes the next statement, stepping inside child functions or scripts.
v Step Over Executes the next statement without descending into child functions.
o Step Out Executes the remainder of the current function and returns to the caller.
c Continue Resumes normal execution until the next breakpoint is encountered or the script finishes.
q Quit Stops the debugger and immediately terminates script execution.
k Call Stack Displays the current execution stack trace (Get-PSCallStack).
l List Lists the source code around the current line.
? Help Displays a list of available debugger commands.

Diagnostic Messaging: Write-Debug and $DebugPreference

The Write-Debug cmdlet writes diagnostic and troubleshooting messages to Stream 5 (the Debug Stream). By default, debug messages are ignored because the system preference variable $DebugPreference is set to 'SilentlyContinue'.

The $DebugPreference Settings

Value Behavior
SilentlyContinue (Default) Debug messages are suppressed; execution proceeds uninterrupted.
Continue Debug messages are displayed in yellow text on the console; execution does not pause.
Inquire Displays the debug message and pauses execution, prompting the user for instructions.
Stop Emits the debug message and raises a terminating runtime exception.
debug-preference-demo.ps1powershell
$DebugPreference = "Continue"

Write-Debug "Diagnostic check: Verifying target configuration payload."
Write-Output "Standard script output stream."
Consolepowershell
pwsh ./debug-preference-demo.ps1
Outputplaintext
DEBUG: Diagnostic check: Verifying target configuration payload.
Standard script output stream.

If $DebugPreference is set to "Inquire", PowerShell presents an interactive decision menu:

Outputplaintext
DEBUG: Diagnostic check: Verifying target configuration payload.

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"):

Selecting [S] Suspend pauses the script and drops you into a nested debugger prompt where you can inspect local variables before continuing.

The -Debug Common Parameter

Instead of hardcoding $DebugPreference into your script files, scripts and functions that include the [CmdletBinding()] attribute automatically inherit the -Debug common parameter.

Passing -Debug at invocation temporarily overrides $DebugPreference to Inquire for the duration of that specific command, without permanently altering your session's global settings:

test-service-runner.ps1powershell
[CmdletBinding()]
param (
    [string]$ServiceName = "Spooler"
)

Write-Debug "Querying status for service: $ServiceName"
$service = Get-Service -Name $ServiceName

Write-Debug "Service state retrieved: $($service.Status)"
Write-Output "Target service $ServiceName is $($service.Status)."

where:

  • [CmdletBinding()] registers the script as an advanced function, enabling common parameters like -Debug and -Verbose.
  • Write-Debug statements isolate diagnostic information to Stream 5.
Consolepowershell
pwsh ./test-service-runner.ps1 -Debug
Outputplaintext
DEBUG: Querying status for service: Spooler

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): Y
DEBUG: Service state retrieved: Running

Confirm
Continue with this operation?
[Y] Yes  [A] Yes to All  [H] Halt Command  [S] Suspend  [?] Help (default is "Y"): Y
Target service Spooler is Running.

Practical Example: Stepping Through Storage Calculations

The following script contains a logic check on drive storage thresholds. We will set a command-line breakpoint, inspect variables dynamically at the prompt, and step through execution:

evaluate-disk-thresholds.ps1powershell
$Volumes = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3"

foreach ($vol in $Volumes) {
    $DeviceName = $vol.DeviceID
    $TotalGB    = [math]::Round($vol.Size / 1GB, 2)
    $FreeGB     = [math]::Round($vol.FreeSpace / 1GB, 2)
    $UsedRatio  = [math]::Round((($vol.Size - $vol.FreeSpace) / $vol.Size) * 100, 2)
    
    $RequiresAlert = ($UsedRatio -ge 80.0)

    [PSCustomObject]@{
        Drive        = $DeviceName
        TotalGB      = $TotalGB
        FreeGB       = $FreeGB
        PercentUsed  = $UsedRatio
        AlertFlag    = $RequiresAlert
    }
}

where:

  • Get-CimInstance enumerates fixed local storage partitions.
  • $UsedRatio calculates disk consumption as a percentage.
  • $RequiresAlert flags whether capacity exceeds the 80% threshold.

Set a line breakpoint on line 9 ($RequiresAlert = ...) and invoke the script:

Consolepowershell
Set-PSBreakpoint -Script ./evaluate-disk-thresholds.ps1 -Line 9

./evaluate-disk-thresholds.ps1
Outputplaintext
Entering debug mode. Use h or ? for help.

Hit Line breakpoint on 'C:\Scripts\evaluate-disk-thresholds.ps1:9'

At C:\Scripts\evaluate-disk-thresholds.ps1:9 char:5
+     $RequiresAlert = ($UsedRatio -ge 80.0)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[DBG]: PS C:\Scripts>> $UsedRatio
83.5
[DBG]: PS C:\Scripts>> $DeviceName
C:
[DBG]: PS C:\Scripts>> s
At C:\Scripts\evaluate-disk-thresholds.ps1:11 char:5
+     [PSCustomObject]@{
+     ~~~~~~~~~~~~~~~~~~
[DBG]: PS C:\Scripts>> $RequiresAlert
True
[DBG]: PS C:\Scripts>> c

where:

  • The engine pauses execution on line 9 before $RequiresAlert is evaluated.
  • Typing $UsedRatio and $DeviceName at the [DBG]: PS>> prompt inspects their live memory values (83.5 and C:).
  • Entering s steps forward one statement.
  • Evaluating $RequiresAlert confirms it was set to $true.
  • Entering c resumes execution.
Outputplaintext
Drive TotalGB FreeGB PercentUsed AlertFlag
----- ------- ------ ----------- ---------
C:     255.40  42.15        83.5      True
D:    1024.00 512.80        49.9     False

Debugging in Visual Studio Code

Visual Studio Code paired with the official PowerShell Extension provides a full graphical debugging environment, replacing command-line commands with a visual interface.

Setting Breakpoints in the Editor

  1. Open your .ps1 script file in VS Code.
  2. Click in the margin to the left of any line number, or press F9 on the desired line.
  3. A red dot appears, marking an active breakpoint.
       1  param ([string]$Path)
  ●    2  $files = Get-ChildItem -Path $Path    <-- Active Breakpoint (F9)
       3  $totalBytes = 0

Starting a Debug Session

Press F5 (or select Run > Start Debugging) to launch the currently open script in debug mode. When the breakpoint is hit, code execution pauses, the active line is highlighted in yellow, and the debug toolbar appears.

The Debugging Controls

Visual Action Keyboard Shortcut Function
Continue F5 Resumes execution until the next breakpoint.
Step Over F10 Advances execution by one statement without stepping into function calls.
Step Into F11 Steps inside the function or cmdlet call on the current line.
Step Out Shift + F11 Steps out of the current function back to the calling script.
Restart Ctrl + Shift + F5 Stops and restarts the debugging session from line 1.
Stop Shift + F5 Terminates the debugger and cancels execution.

Visual Panes

While paused at a breakpoint, VS Code exposes several diagnostic panes in the Run & Debug view:

  • Variables: Displays all currently active Local, Script, and Global variables and their properties in real time.
  • Watch: Allows you to enter custom expressions (e.g., $files.Count or $totalBytes / 1MB) to track as values change.
  • Call Stack: Displays the nested hierarchy of functions and scripts that led to the current line.
  • Debug Console: An interactive REPL prompt where you can run arbitrary PowerShell commands inside the paused execution context. Useful for testing a fix before applying it to the script.
Navigation