Debug PowerShell Azure Functions locally

Azure Functions lets you develop your functions as PowerShell scripts.

You can debug your PowerShell functions locally as you would any PowerShell scripts using the following standard development tools:

  • Visual Studio Code: Microsoft's free, lightweight, and open-source text editor with the PowerShell extension that offers a full PowerShell development experience.
  • A PowerShell console: Debug using the same commands you would use to debug any other PowerShell process.

Azure Functions Core Tools supports local debugging of Azure Functions, including PowerShell functions.

Example function app

The function app used in this article has a single HTTP triggered function and has the following files:

PSFunctionApp
 | - HttpTriggerFunction
 | | - run.ps1
 | | - function.json
 | - local.settings.json
 | - host.json
 | - profile.ps1

This function app is similar to the one you get when you complete the PowerShell quickstart.

The function code in run.ps1 looks like the following script:

param($Request)

$name = $Request.Query.Name

if($name) {
    $status = 200
    $body = "Hello $name"
}
else {
    $status = 400
    $body = "Please pass a name on the query string or in the request body."
}

Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body = $body
})

Set the attach point

To debug any PowerShell function, the function needs to stop for the debugger to be attached. The Wait-Debugger cmdlet stops execution and waits for the debugger.

Note

When using PowerShell 7, you don't need to add the Wait-Debugger call in your code.

All you need to do is add a call to the Wait-Debugger cmdlet just above the if statement, as follows:

param($Request)

$name = $Request.Query.Name

# This is where we will wait for the debugger to attach
Wait-Debugger

if($name) {
    $status = 200
    $body = "Hello $name"
}
# ...

Debugging starts at the if statement.

With Wait-Debugger in place, you can now debug the functions using either Visual Studio Code or a PowerShell console.

Debug in Visual Studio Code

To debug your PowerShell functions in Visual Studio Code, you must have the following installed:

After installing these dependencies, load an existing PowerShell Functions project, or create your first PowerShell Functions project.

Note

Should your project not have the needed configuration files, you are prompted to add them.

Set the PowerShell version

PowerShell Core installs side by side with Windows PowerShell. Set PowerShell Core as the PowerShell version to use with the PowerShell extension for Visual Studio Code.

  1. Press F1 to display the command pallet, then search for Session.

  2. Choose PowerShell: Show Session Menu.

  3. If your Current session isn't PowerShell Core 6, choose Switch to: PowerShell Core 6.

When you have a PowerShell file open, you see the version displayed in green at the bottom right of the window. Selecting this text also displays the session menu. To learn more, see the Choosing a version of PowerShell to use with the extension.

Start the function app

Verify that Wait-Debugger is set in the function where you want to attach the debugger. With Wait-Debugger added, you can debug your function app using Visual Studio Code.

Choose the Debug pane and then Attach to PowerShell function.

debugger

You can also press the F5 key to start debugging.

The start debugging operation does the following tasks:

  • Runs func extensions install in the terminal to install any Azure Functions extensions required by your function app.
  • Runs func host start in the terminal to start the function app in the Functions host.
  • Attach the PowerShell debugger to the PowerShell runspace within the Functions runtime.

Note

You need to ensure PSWorkerInProcConcurrencyUpperBound is set to 1 to ensure correct debugging experience in Visual Studio Code. This is the default.

With your function app running, you need a separate PowerShell console to call the HTTP triggered function.

In this case, the PowerShell console is the client. The Invoke-RestMethod is used to trigger the function.

In a PowerShell console, run the following command:

Invoke-RestMethod "http://localhost:7071/api/HttpTrigger?Name=Functions"

You'll notice that a response isn't immediately returned. That's because Wait-Debugger has attached the debugger and PowerShell execution went into break mode as soon as it could. This is because of the BreakAll concept, which is explained later. After you press the continue button, the debugger now breaks on the line right after Wait-Debugger.

At this point, the debugger is attached and you can do all the normal debugger operations. For more information on using the debugger in Visual Studio Code, see the official documentation.

After you continue and fully invoke your script, you'll notice that:

  • The PowerShell console that did the Invoke-RestMethod has returned a result
  • The PowerShell Integrated Console in Visual Studio Code is waiting for a script to be executed

Later when you invoke the same function, the debugger in PowerShell extension breaks right after the Wait-Debugger.

Debugging in a PowerShell Console

Note

This section assumes you have read the Azure Functions Core Tools docs and know how to use the func host start command to start your function app.

Open up a console, cd into the directory of your function app, and run the following command:

func host start

With the function app running and the Wait-Debugger in place, you can attach to the process. You do need two more PowerShell consoles.

One of the consoles acts as the client. From this, you call Invoke-RestMethod to trigger the function. For example, you can run the following command:

Invoke-RestMethod "http://localhost:7071/api/HttpTrigger?Name=Functions"

You'll notice that it doesn't return a response, which is a result of the Wait-Debugger. The PowerShell runspace is now waiting for a debugger to be attached. Let's get that attached.

In the other PowerShell console, run the following command:

Get-PSHostProcessInfo

This cmdlet returns a table that looks like the following output:

ProcessName ProcessId AppDomainName
----------- --------- -------------
dotnet          49988 None
pwsh            43796 None
pwsh            49970 None
pwsh             3533 None
pwsh            79544 None
pwsh            34881 None
pwsh            32071 None
pwsh            88785 None

Make note of the ProcessId for the item in the table with the ProcessName as dotnet. This process is your function app.

Next, run the following snippet:

# This enters into the Azure Functions PowerShell process.
# Put your value of `ProcessId` here.
Enter-PSHostProcess -Id $ProcessId

# This triggers the debugger.
Debug-Runspace 1

Once started, the debugger breaks and shows something like the following output:

Debugging Runspace: Runspace1

To end the debugging session type the 'Detach' command at the debugger prompt, or type 'Ctrl+C' otherwise.

At /Path/To/PSFunctionApp/HttpTriggerFunction/run.ps1:13 char:1
+ if($name) { ...
+ ~~~~~~~~~~~
[DBG]: [Process:49988]: [Runspace1]: PS /Path/To/PSFunctionApp>>

At this point, you're stopped at a breakpoint in the PowerShell debugger. From here, you can do all of the usual debug operations, step over, step into, continue, quit, and others. To see the complete set of debug commands available in the console, run the h or ? commands.

You can also set breakpoints at this level with the Set-PSBreakpoint cmdlet.

Once you continue and fully invoke your script, you'll notice that:

  • The PowerShell console where you executed Invoke-RestMethod has now returned a result.
  • The PowerShell console where you executed Debug-Runspace is waiting for a script to be executed.

You can invoke the same function again (using Invoke-RestMethod for example) and the debugger breaks in right after the Wait-Debugger command.

Considerations for debugging

Keep in mind the following issues when debugging your Functions code.

BreakAll might cause your debugger to break in an unexpected place

The PowerShell extension uses Debug-Runspace, which in turn relies on PowerShell's BreakAll feature. This feature tells PowerShell to stop at the first command that is executed. This behavior gives you the opportunity to set breakpoints within the debugged runspace.

The Azure Functions runtime runs a few commands before actually invoking your run.ps1 script, so it's possible that the debugger ends up breaking within the Microsoft.Azure.Functions.PowerShellWorker.psm1 or Microsoft.Azure.Functions.PowerShellWorker.psd1.

Should this break happen, run the continue or c command to skip over this breakpoint. You then stop at the expected breakpoint.

Troubleshooting

If you have difficulties during debugging, you should check for the following:

Check Action
Run func --version from the terminal. If you get an error that func can't be found, Core Tools (func.exe) may be missing from the local path variable. Reinstall Core Tools.
In Visual Studio Code, the default terminal needs to have access to func.exe. Make sure you aren't using a default terminal that doesn't have Core Tools installed, such as Windows Subsystem for Linux (WSL). Set the default shell in Visual Studio Code to either PowerShell 7 (recommended) or Windows PowerShell 5.1.

Next steps

To learn more about developing Functions using PowerShell, see Azure Functions PowerShell developer guide.