Stepping through PowerShell Scripts with the PowerShell Terminal

Image Description

Daily PowerShell #25

Scripting Daily PowerShell Debugging

November 10, 2021

Learn how to use the built in debugging commands to step through scripts in the PowerShell terminal.

Although VS Code, PSScriptPad and Visual Studio provide ways of debugging PowerShell scripts, you can also debug scripts without leaving your terminal. Use the built in debugging commands to pause on breakpoints, step and evaluate state in scripts.

For the purposes of this blog post, we’ll use the following script that we’ll save as process.ps1.

function New-Process {
	param($NewProcess)
	
  Write-Debug "Starting process $NewProcess"
	Start-Process $NewProcess
}
$NewProcess = "Notepad"
New-Process $NewProcess 

$Variable = (Get-Process).Length
Write-Host $Variable 

Setting Breakpoints

In order to pause the debugger, you will need to either use the $DebugPreference variable, the Set-PSBreakpoint cmdlet or the Wait-Debugger cmdlet.

$DebugPreference Flag

The $DebugPreference variable defines what happens when Write-Debug is called. One of the options is to set the $DebugPreference to Break. This will cause the debugger to break whenever it hits a Write-Debug call.

For example, when update the variable and then call our script the debugger will break at the Write-Debug call.

$DebugPreference = 'Break'
.\process.ps1

PS C:\Users\adamr> C:\Users\adamr\Desktop\process.ps1
Entering debug mode. Use h or ? for help.

At C:\Users\adamr\Desktop\process.ps1:4 char:2
+     Write-Debug "Starting process $NewProcess"
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Set-PSBreakpoint

Set-PSBreakpoint can be used to set a breakpoint within a PowerShell script. To use this cmdlet for a file, you’ll need the path and line number. Line numbers start at 1.

Set-PSBreakpoint -Script .\process.ps1 -Line 8

  ID Script                         Line Command                        Variable                       Action
  -- ------                         ---- -------                        --------                       ------
   1 process.ps1                          8

.\process.ps1

Hit Line breakpoint on 'C:\Users\adamr\Desktop\process.ps1:8'

At C:\Users\adamr\Desktop\process.ps1:8 char:1
+ $NewProcess = "Notepad"
+ ~~~~~~~~~~~~~~~~~~~~~~~

Wait-Debugger

The Wait-Debugger cmdlet can be used to cause the debugger to break where the cmdlet is called. We’ll update our script as follows.

Wait-Debugger
function New-Process {
	param($NewProcess)
	
	Write-Debug "Starting process $NewProcess"
	Start-Process $NewProcess
	
}
$NewProcess = "Notepad"
New-Process $NewProcess

$Variable = (Get-Process).Length
Write-Host $Variable 

Running the script will cause the debugger to break on line 1.

.\powershell.ps1

At C:\Users\adamr\Desktop\powershell.ps1:1 char:1
+ Wait-Debugger
+ ~~~~~~~~~~~~~

Stepping Through Scripts

Once you have stopped in a script, you can use the debugging commands to move through the execution of scripts. Let’s assume we have the Wait-Debugger on the first line of the script.

When the debugger breaks, you can enter the following commands:


 s, stepInto         Single step (step into functions, scripts, etc.)
 v, stepOver         Step to next statement (step over functions, scripts, etc.)
 o, stepOut          Step out of the current function, script, etc.

 c, continue         Continue operation
 q, quit             Stop operation and exit the debugger
 d, detach           Continue operation and detach the debugger.

 k, Get-PSCallStack Display call stack

 l, list             List source code for the current script.
                     Use "list" to start from the current line, "list <m>"
                     to start from line <m>, and "list <m> <n>" to list <n>
                     lines starting from line <m>

 <enter>             Repeat last command if it was stepInto, stepOver or list

 ?, h                displays this help message.


For instructions about how to customize your debugger prompt, type "help about_prompt".

Step Over

Command v

Step over will move to the next line. If the current line contains a function call, it won’t step into the execution of that function. It’ll just execute it. For example, if the current execution is on the New-Process command, it will execute the function but not step into it.

[DBG]: PS C:\Users\adamr>> v
At C:\Users\adamr\Desktop\process.ps1:10 char:1
+ New-Process $NewProcess
+ ~~~~~~~~~~~~~~~~~~~~~~~

[DBG]: PS C:\Users\adamr>> v
At C:\Users\adamr\Desktop\process.ps1:12 char:1
+ $Variable = (Get-Process).Length
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Step Into

Command: s

Step into will move to the next line. If the current line contains a function call, the next line will be within that function. For example, if we are on the New-Process call, we will move into the function rather than over it.

[DBG]: PS C:\Users\adamr>> v
At C:\Users\adamr\Desktop\process.ps1:10 char:1
+ New-Process $NewProcess
+ ~~~~~~~~~~~~~~~~~~~~~~~
[DBG]: PS C:\Users\adamr>> s
At C:\Users\adamr\Desktop\process.ps1:2 char:22
+ function New-Process {
+                      ~

Step Out

Command: o

Once within a function, you can step out of a function by use the step out command. This will execute the rest of the function and move to the next line in the stack frame above the current.

[DBG]: PS C:\Users\adamr>> s
At C:\Users\adamr\Desktop\process.ps1:2 char:22
+ function New-Process {
+                      ~
[DBG]: PS C:\Users\adamr>> o
At C:\Users\adamr\Desktop\process.ps1:12 char:1
+ $Variable = (Get-Process).Length
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Continue

Command: c

When you issue the continue command, it will cause the script to run into the end or until it hits another breakpoint.

[DBG]: PS C:\Users\adamr>> o
At C:\Users\adamr\Desktop\process.ps1:12 char:1
+ $Variable = (Get-Process).Length
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[DBG]: PS C:\Users\adamr>> c
328

Call Stacks

Command: k

Issuing the Get-PSCallstack command allows you see where in the source stack you are running. For each function call, you will move down in the call stack. Using Get-PSCallstack allows you to find your way back to where the current lined was called from.

In this example, we are within the New-Process function.

[DBG]: PS C:\Users\adamr>> s
At C:\Users\adamr\Desktop\process.ps1:2 char:22
+ function New-Process {
+                      ~
[DBG]: PS C:\Users\adamr>> k

Command       Arguments            Location
-------       ---------            --------
New-Process   {NewProcess=Notepad} process.ps1: line 2
process.ps1      {}                process.ps1: line 10
<ScriptBlock> {}                   <No file>

State

Additionally, while you are broken in a script, you can execute any PowerShell command as well as evaluate variables.

[DBG]: PS C:\Users\adamr>> s
At C:\Users\adamr\Desktop\process.ps1:2 char:22
+ function New-Process {
+                      ~

[DBG]: PS C:\Users\adamr>> $NewProcess