Debugging in VS Code
A breakpoint pauses a script on a line in VS Code, so you can look at every variable, step through it one line at a time with F10, and watch an expression until a value surprises you; Set-PSBreakpoint does the same at the prompt.
Journeyman · PowerShell · Updated
After this lesson you can
- Pause a script at a breakpoint in VS Code and run it one line at a time with F10, F11 and F5
- Watch variables and expressions while a script is paused, to find the line where a value isn't what you expected
Key takeaways
- A breakpoint, the red dot beside a line number (F9), pauses the script before that line runs. F5 starts the script under the debugger.
- While it's paused, the Variables list and a hover over any variable show their values right now.
- F10 runs one line and pauses again; F11 goes into a function the line calls; F5 carries on to the next breakpoint; Shift+F5 stops.
- Add an expression to Watch, such as Test-Path $to, to see its answer at every step.
- Without VS Code, Set-PSBreakpoint -Script file -Line 9 pauses at the prompt, where you can type any variable's name.
The receipt mover runs without a single red line, and moves nothing. Every receipt "is already there", yet the backup drive is empty. There's no error to read: the script does exactly what you wrote, but not what you meant. That's a bug, and reading the code over and over is a slow way to find one. Instead, pause the script on a line and look at every variable, the way you'd pause a movie to see what's in the frame. VS Code's debugger does that, and developers use it every day, on scripts and on whole applications.
You need VS Code with the PowerShell extension from Writing Scripts in VS Code, and the mover from Where Output Goes: Write-Output, Write-Host and Friends. Run the practice-folder lines from Reading Errors, and -ErrorAction and empty the backup drive with Remove-Item backup-drive\*. Then make a bug to hunt: change line 9 of move-receipts.ps1 from if (Test-Path $to) { to this, as a tired you might have typed it:
if (Test-Path $Destination) {
.\move-receipts.ps1
Moving receipts to C:\Users\Sam\terminal-practice\mover\backup-drive
WARNING: 2026-10-03-groceries.pdf is already there. Left it in the inbox.
WARNING: 2026-10-05-gas.pdf is already there. Left it in the inbox.
WARNING: 2026-10-09-pharmacy.pdf is already there. Left it in the inbox.
Pretend you don't know what you changed. Where would you start?
A breakpoint: where to pause
Open the mover folder in VS Code and open move-receipts.ps1. The warning comes from line 10, so the decision is made on line 9. Click in the margin left of the number 9: a red dot appears. That's a breakpoint, a mark that says "pause before this line runs". Click it again to remove it, or put the cursor on a line and press F9.
Start, pause and look
Press F5 (or Run > Start Debugging). The PowerShell extension runs the script in its terminal and stops before line 9, with the line highlighted in yellow. The Run and Debug view opens on the left, and its Variables list shows the script's variables as they are right now. Hover over any variable in the editor and its value pops up too.
$to looks right: the path where this receipt would go. So why does the script think it's already there?
One line at a time
The toolbar at the top steps through the script:
| Key | Button | What it does |
|---|---|---|
| F10 | Step Over | Run this line, then pause on the next |
| F11 | Step Into | Go into a function this line calls, and pause there |
| Shift+F11 | Step Out | Finish the function you're in, and pause after it |
| F5 | Continue | Run on to the next breakpoint, or the end |
| Shift+F5 | Stop | Stop the script |
Press F10. The highlight jumps to line 10, the warning, so the if on line 9 answered yes. To see why, use Watch: in the Run and Debug view, click + beside Watch and type Test-Path $Destination, then add Test-Path $to. Watch shows each answer, and updates it after every step:
There it is: the folder is there (True), but this receipt isn't (False), and line 9 asks about the folder. Press Shift+F5 to stop, change line 9 back to if (Test-Path $to) {, save, and press F5 again: it pauses at the breakpoint for each receipt, and F5 carries on. Click the red dot to remove it when you're done.
That's the habit: make the bug happen, pause right before where it goes wrong, then step and watch until a value surprises you. One more help: in VS Code's terminal, an error's location shows as file, line and column, such as move-receipts.ps1:6:5, and Ctrl+click opens the file at that spot.
Without VS Code: `Set-PSBreakpoint`
On a computer without VS Code, PowerShell's own debugger works at the prompt:
Set-PSBreakpoint -Script .\move-receipts.ps1 -Line 9
.\move-receipts.ps1
ID Script Line Command Variable Action
-- ------ ---- ------- -------- ------
1 move-receipts.ps1 9
Moving receipts to C:\Users\Sam\terminal-practice\mover\backup-drive
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on 'C:\Users\Sam\terminal-practice\mover\move-receipts.ps1:9'
At C:\Users\Sam\terminal-practice\mover\move-receipts.ps1:9 char:13
+ if (Test-Path $Destination) {
+ ~~~~~~~~~~~~~~~~~~~~~~
The prompt now starts with [DBG]:. Type a variable's name or any command to look around:
$to
Test-Path $Destination
Test-Path $to
C:\Users\Sam\terminal-practice\mover\backup-drive\2026-10-03-groceries.pdf
True
False
At the [DBG]: prompt, v steps over a line, s steps into one, c carries on, and q stops the script. Get-PSBreakpoint | Remove-PSBreakpoint removes the breakpoint when you're done.
On a Mac or Linux, VS Code and Set-PSBreakpoint work the same. On many laptops, and on a Mac, hold Fn to press F5, F9, F10 or F11.
Check yourself
2 questions to try on the lesson page.
So what
When a script does the wrong thing quietly, don't guess: pause it where the decision is made, look at the values, and the bug usually points at itself.