Skip to content

Debugging Plugins

Peter McDonald edited this page Aug 12, 2026 · 10 revisions

Debugging Plugins

Dataverse plug-ins run on the server, so you can't attach a debugger directly. The Plugin Profiler captures a plug-in's exact execution context; Dataverse PowerTools turns that capture into a unit test you debug in VS Code — set breakpoints, F5, step through — with the real server-side context, no live org needed.

On Windows, the whole loop is one-click inside VS Code. On macOS/Linux, capturing is done once in the Plugin Registration Tool (the capture tool is .NET Framework); everything after — download, replay, debug — works everywhere.

The plugin card's Debugging section (and the Command Palette) drives all of it.

Plugin debugging in Dataverse PowerTools — the generated replay test, the replay running green in-process (no live org), and a rendered trace log

Step-by-step with screenshots: Profiling a Plug-in Run walks the whole capture → download → replay loop with the button, dialog and picker for each step highlighted.

Prerequisites

Before any of the debugging functions work you need:

  • A deployed plug-in with a registered stepBuild Package & Deploy from the plugin card. There is nothing to profile until the plug-in is on the server with a step that fires.
  • The Plugin Profiler solution installed in the environment — a one-time, per-environment install. On Windows Profile next run installs it for you; on macOS/Linux install it once via the Plugin Registration Tool.
  • dotnet and pac on your PATH (see Requirements).
  • A plugin test project for the replay step — run Setup Plugin Unit Testing from the plugin card if you don't have one.
  • Windows for the capture, and for running the replayProfile next run uses a .NET Framework capture tool (the same constraint the Plugin Registration Tool has), and the replay is a test in your plug-in test project, which targets .NET Framework. Download a run, generating the replay test and the trace logs are all cross-platform.
  • The C# extension (ms-dotnettools.csharp) — it provides the .NET debugger Replay & debug attaches. Without it the replay still runs; nothing stops.

The overall process

flowchart TD
    A[Build Package &amp; Deploy<br/>plug-in + registered step] --> B{Operating system?}
    B -->|Windows| C[Debugging → Profile next run<br/>installs profiler, starts capture]
    B -->|macOS / Linux| D[Plugin Registration Tool<br/>Install + Start Profiling]
    C --> E[Trigger the plug-in in your app<br/>create/update the record, run the action]
    D --> E
    E --> F[Pick the captured run<br/>saved into profiles/]
    F --> G[Set breakpoints in<br/>Execute / ExecuteDataversePlugin]
    G --> H[Debugging → Replay &amp; debug<br/>generates Replay_Class_timestamp.cs]
    H --> I[Debug the generated test<br/>from Test Explorer / dotnet test]
    I --> J([Breakpoints hit with the exact<br/>captured server-side context — no live org])
Loading

Three stages — profile (capture a real execution), download (bring the capture local), replay & debug (turn it into a test you step through). On Windows all three are one-click in VS Code; elsewhere only the capture step uses the Plugin Registration Tool. Each is detailed below.

1. Profile the next run (Windows — in VS Code)

Deploy your plug-in and register a step first (Build Package & Deploy). Then, in the plugin card's Debugging section, click Profile next run:

  1. If the Plugin Profiler solution isn't installed yet, Dataverse PowerTools installs it for you (one-time, ~30s).
  2. Pick the step to profile (skipped if there's only one).
  3. When prompted, trigger the plug-in — do the action that fires it (create/update the record, run the action) in your app — then click Continue.
  4. The captured executions are listed; pick the run you want (a plug-in that fired many times shows them all, newest first). It's downloaded into profiles/ and profiling is stopped automatically.

Under the hood this uses the profiler's own Install Profiler / Start Profiling over the extension's connection — no Plugin Registration Tool window, no separate sign-in, under both service-principal and interactive auth.

macOS / Linux

Capturing needs the .NET Framework profiler, so do it once in the Plugin Registration Tool (Install ProfilerStart Profiling → trigger the plug-in). Then use Download a run (below) here — that part is cross-platform.

2. Download a captured run (any OS)

Debugging → Download a run lists the captured Plug-in Profile rows in your environment (type · message · table · sync/async · time) and saves the ones you pick into profiles/. Use this to grab a run captured earlier, or on a non-Windows machine.

You can also just drop a profile file exported from PRT (View Plug-in Profile → download) into profiles/.

3. Replay and debug (any OS)

  1. Set breakpoints in your plug-in's Execute / ExecuteDataversePlugin.
  2. Debugging → Replay & debug. It runs the captured execution back through your plug-in with the debugger attached, so those breakpoints are hit — with the exact server-side context that was captured, and no live org. If there is no replay test yet it writes one first; with several profiles in profiles/ it asks which one.
  3. Inspect, step, then Continue to let the run finish (or Stop to end the session).

Generate Replay Test — the button next to it — writes the same Replay_<Class>_<timestamp>.cs without running it. That is the one you commit: it decodes the profile, rebuilds the IPluginExecutionContext and invokes your plug-in in-process, so it runs under dotnet test in CI like any other test.

The generated replay test

Requirements: a plugin test project (run Setup Plugin Unit Testing if you don't have one), the C# extension (ms-dotnettools.csharp) for the debugger, and Windows to run it — the plug-in test project targets .NET Framework, the same constraint PRT has.

There is a full walkthrough with screenshots at Profiling a Plug-in Run.

The Profile & Debug… CodeLens above a [CrmPluginRegistration] class is the cross-platform / PRT-guided entry point:

The Profile & Debug CodeLens on a decorated plug-in class, with the Debugging block in the panel

Trace logs (a separate diagnostic — any OS)

Profiling replays one captured execution offline. Trace logs are the complementary live diagnostic: whatever your plug-in writes with ITracingService.Trace(...), captured server-side as it runs. Use trace logs to see what actually happened in production; use profiling to step through it in a debugger. Both are cross-platform (they're plain Web API calls — no profiler assemblies).

The org-wide trace level lives on the organization row and is surfaced as a coloured pill in the panel's org header:

Pill Level Meaning
🟢 Trace: Off 0 No plug-in trace logging
🟠 Trace: Errors 1 Log traces only when a plug-in throws
🔴 Trace: All 2 Log every plug-in execution — has a performance and storage cost
  • Change the level — click the pill, or run Set Plugin Trace Log Level from the palette, and pick Off / Exception only / All. Switching to All asks for confirmation (it's a firehose — remember to turn it back off when you're done). The pill re-colours immediately. Works under both service-principal and interactive auth.
  • Read the logsView Plugin Trace Logs lists the recent plugintracelog rows (most recent first); pick one and it opens as a formatted Markdown document (type name, message, mode, duration, and the trace text / exception):

A rendered plug-in trace log — type, message, timings and the Trace() output

Recommended route today

  • On Windows: Profile next run → trigger the plug-in → pick the run → set breakpoints → Replay & debug. Everything is one-click in VS Code.
  • On macOS / Linux: capture once in the Plugin Registration Tool (Install ProfilerStart Profiling → trigger) → Download a run here → Generate Replay Test, which you can commit. Running and debugging it needs a Windows host, because the plug-in test project targets .NET Framework.
  • Just want to see what happened in production, without a debugger? Set trace logging to Errors (or All briefly), reproduce, then View Plugin Trace Logs — no profiler, no test project, any OS.

How it works

Capturing requires the profiler to be pipeline-executable, which only happens through the profiler's EnablePlugin API (what PRT's Start Profiling calls) — raw Web-API step edits don't make it fire. Dataverse PowerTools ships a tiny Windows-only helper that calls that API with your existing access token, so Profile next run needs no PRT GUI. The captured profile is stored as a mbs_pluginprofile row. The replay does not use the profiler's own assemblies: the generated harness decodes the profile (base64 → deflate → report XML) into an IPluginExecutionContext using Microsoft.Xrm.Sdk alone, then calls your plug-in in-process. That is what lets it run as an ordinary unit test — in CI, with no live org and no child AppDomain.

Troubleshooting

Symptom Cause / fix
"No registered plugin steps" when profiling The plug-in is deployed but has no registered step, so there's nothing to profile. Register a step (Build Package & Deploy) and retry.
No captured profiles after triggering The trigger didn't fire the profiled step (wrong message/table/stage), or profiling wasn't started. Confirm the step you picked matches the action you performed, then trigger again.
Profiler not installed On Windows Profile next run installs it automatically; if that fails, install the Plugin Profiler solution once via the Plugin Registration Tool. It's per-environment.
Replay does nothing / can't find a test project Run Setup Plugin Unit Testing to create the test project first; Replay & debug writes the generated Replay_*.cs into it.
Capture or replay unavailable on macOS / Linux Those two steps use the .NET Framework profiler assemblies (Windows-only). Capture in PRT and run the replay test host on Windows; Download a run and trace logs work everywhere.
Trace pill greyed out / trace commands say "Connect to Dataverse first" There's no live connection. Connect the project to Dataverse, then the trace pill and Set/View Plugin Trace Log commands light up.

The screenshots above are captured from the Dataverse PowerTools UI (against a demo connection). Live captures against a running model-driven app — e.g. a form re-rendering after a hot-reload, or a live-triggered trace — are best recorded in normal use.

Clone this wiki locally