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:
$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:
Set-PSBreakpoint -Script ./debug-target.ps1 -Line 4
./debug-target.ps1Hit 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
Set-PSBreakpoint -Script ./debug-target.ps1 -Variable "total" -Mode WriteThis pauses execution whenever $total is modified.
Example: Breaking on a Command
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:
Get-PSBreakpoint
Disable-PSBreakpoint -Id 0
Enable-PSBreakpoint -Id 0
Get-PSBreakpoint | Remove-PSBreakpointThe 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. |
$DebugPreference = "Continue"
Write-Debug "Diagnostic check: Verifying target configuration payload."
Write-Output "Standard script output stream."pwsh ./debug-preference-demo.ps1DEBUG: Diagnostic check: Verifying target configuration payload.
Standard script output stream.If $DebugPreference is set to "Inquire", PowerShell presents an interactive decision menu:
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:
[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-Debugand-Verbose.Write-Debugstatements isolate diagnostic information to Stream 5.
pwsh ./test-service-runner.ps1 -DebugDEBUG: 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:
$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-CimInstanceenumerates fixed local storage partitions.$UsedRatiocalculates disk consumption as a percentage.$RequiresAlertflags whether capacity exceeds the 80% threshold.
Set a line breakpoint on line 9 ($RequiresAlert = ...) and invoke the script:
Set-PSBreakpoint -Script ./evaluate-disk-thresholds.ps1 -Line 9
./evaluate-disk-thresholds.ps1Entering 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>> cwhere:
- The engine pauses execution on line 9 before
$RequiresAlertis evaluated. - Typing
$UsedRatioand$DeviceNameat the[DBG]: PS>>prompt inspects their live memory values (83.5andC:). - Entering
ssteps forward one statement. - Evaluating
$RequiresAlertconfirms it was set to$true. - Entering
cresumes execution.
Drive TotalGB FreeGB PercentUsed AlertFlag
----- ------- ------ ----------- ---------
C: 255.40 42.15 83.5 True
D: 1024.00 512.80 49.9 FalseDebugging 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
- Open your
.ps1script file in VS Code. - Click in the margin to the left of any line number, or press
F9on the desired line. - A red dot appears, marking an active breakpoint.
1 param ([string]$Path)
● 2 $files = Get-ChildItem -Path $Path <-- Active Breakpoint (F9)
3 $totalBytes = 0Starting 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, andGlobalvariables and their properties in real time. - Watch: Allows you to enter custom expressions (e.g.,
$files.Countor$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.