Getty Images/iStockphoto

How to successfully debug PowerShell scripts

Got a PowerShell script that isn't working as intended? Debug it using breakpoints and a few simple steps in VS Code, and you'll be on your way to scripting success.

Most PowerShell scripts are complex, so it's nearly impossible to write a perfect one the first time. That's nothing to be ashamed of. You can always debug it.

Effectively debugging a PowerShell script is a critical skill for any script writer, from a new desktop support technician to a seasoned developer. You must understand how to use breakpoints and know how to navigate the debugger in the PowerShell extension for Visual Studio (VS) Code.

To follow along with the steps in this article, you must have PowerShell, VS Code and the PowerShell extension in VS Code. But there are no OS requirements. You can debug PowerShell 7 scripts in VS Code with the PowerShell extension on Windows, MacOS and most Linux distributions.

If you open a PS1 file in VS Code and you don't already have the PowerShell extension, you will be prompted to install it.

Click install to install the recommended PowerShell extension.
Figure 1.

If you need to install it manually, go to the extension icon and search for "PowerShell."

To install manually search for PowerShell in the extension icon.
Figure 2.

Understand breakpoints

Breakpoints are user-specified points in the code where the debugger stops the code from executing to give you the opportunity to evaluate the state of variables before proceeding. This is valuable because it enables you to easily identify problems.

To set breakpoints in VS Code, put the cursor on the line that you wish to add a breakpoint to and press the F9 key. You can also hover your mouse over the left margin and click on the red dot, as shown in Figure 3.

Click red dot to add a breakpoint.
Figure 3.

Once you set the breakpoint, it will be indicated by a bright red dot, as shown in Figure 4.

Bright red dot to display breakpoint.
Figure 4.

Breakpoints also show up in the breakpoint list at the bottom of the debugger pane as seen in Figure 5. You can enable or disable breakpoints from the debugger pane.

Example of a breakpoint in the bottom of the debugger plane.
Figure 5.

How to debug

If you want to pull pricing data from Azure using the prices API, your script might look like the following.

$azPricesApiBase = "https://prices.azure.com/api/retail/prices"
$filters = @(
    "serviceName eq 'Virtual Machines'",
    "priceType eq 'Reservation'",
    "armRegionName eq 'westus3'"
)
$url = "$azPricesApiBase?$filter=$($filters -join ' and ')"

$items = & {
    $out = Invoke-RestMethod $url
    $out
    while ($null -ne $out.NextPageLink) {
        $out = Invoke-RestMethod $out.NextPageLink
        $out
    }
}

$items | Export-Excel -Path "C:\temp\azPrices.xlsx" -AutoSize -TableName "AzurePrices"

The goal in this example is to pull all virtual machine reservation pricing from the West US 3 region. Because the API paginates, you must execute multiple queries to get all the data.

If you execute this script in PowerShell, it throws an error.

Error example if the script is executed in PowerShell.
Figure 6.

The error suggests that something is wrong with the URL. Open the script in VS Code and start debugging.

First, put a breakpoint on Line 10 because that is where the error occurs.

Next, start the script in debugging mode. To do this, press F5 on your keyboard, or go to the Run menu and select Start Debugging. When the script gets down to Line 10, it pauses on the breakpoint and highlights the line, as shown in Figure 7.

Line 10 is highlighted at the breakpoint.
Figure 7.

Then examine the URL variable while the debugger has the script paused. You can see the contents of $url either by typing "$url" into the terminal or by expanding the appropriate scope of variables in the debugger under the Script section and scrolling down to $url, as seen in Figure 8.

Examine aspects of the code in the debugger.
Figure 8.

The entire first part of the URL that is present in the $azPricesApiBase variable isn't visible. If you scroll up to that variable or type it into the terminal, you'll find that it does have value, as shown in Figure 9.

The variable does have value if you look it up.
Figure 9.

Something unexpected occurs on Line 7 where you are building the $url variable.

In this case, there are two problems:

  • PowerShell interprets the question mark as part of the variable name. If this happens, you need to escape the question mark.
  • The API requires the literal string "$filter" as part of the query parameters. If this happens, you need to escape the dollar sign because the string is within double quotes.

After fixing both issues, Line 7 now looks like this:

$url = "$azPricesApiBase`?`$filter=$($filters -join ' and ')"

Next, click the Stop button to stop the debugger.

Hit the stop button to stop the debugger.
Figure 10.

Then execute the script again to find that the $url looks correct.

Execute the script again.
Figure 11.

The script runs successfully to completion, except the data that was expected in the spreadsheet is completely different.

The data in the spreadsheet is not accurate.
Figure 12.

Add more break points on Lines 11, 14 and 18 so you can review the data being retrieved.

Added breakpoints on Lines 11, 14, and 18.
Figure 13.

Afterwards, when you execute the script and it stops on Line 11, look at the $out variable. The information you want is in a sub-property named Items.

Check the $out variable on Line 11.
Figure 14.

Track the value of $out. Right click on it and select Watch. It will stay in the Watch section of the debugger pane.

If you want the script to continue, use the debugger controls. The options are Continue, Step Over, Step Into, Step Out and Stop, as seen in Figure 15.

Debugger controls include buttons to continue, step over, step into, step out, and stop.
Figure 15.
  • Continue. Continues to the next break point or the end of the script.
  • Step Over. Continues to the next statement in the current execution context.
  • Step Into. Continues to the next statement, including nested execution contexts such as function calls.
  • Step Out. In a nested execution context, the debugger will proceed until the current execution context is exited.
  • Stop. Halts the debugging and exits the debugger.

In this case, click Continue to get to the next breakpoint. Press F5 on the keyboard or the Continue button on the debugger controls, as seen in Figure 16.

Press continue in the debugger control section.
Figure 16.

On Line 14, the value of $out is similar. It shows the desired output in the Items property. Stop the debugging, and fix Lines 11 and 14 to reference the Items property.

$out.Items

Finally, remove all the breakpoints and execute the script. The data you expect to see should show up in the spreadsheet.

The shown data should be correct.
Figure 17.

The importance of variables

It is important to use variables wherever possible to easily debug a code. Variables make it easy to understand what the script executes and how it interacts with cmdlets and functions.

For example, if the script had built the URL in line with the Invoke-RestMethod cmdlet on Line 10, it would have made it more difficult to understand why the URL was incorrect because it would have been present in the debugger. That is what the $url variable defined on Line 7.

Anthony Howell is an IT expert who is well-versed in multiple infrastructure and automation technologies, including PowerShell, DevOps, cloud computing, and the Windows and Linux operating systems.

Dig Deeper on Systems automation and orchestration