Building a PowerShell Debug Visualizer: Step‑by‑Step GuideDebugging PowerShell scripts can quickly become tedious when you’re trying to inspect complex objects, nested collections, or large amounts of textual data. A debug visualizer — a small UI tool that formats and displays objects while you debug — can save you time and reduce cognitive load. This guide walks through building a PowerShell Debug Visualizer from concept to a working extension that integrates with Visual Studio Code (VS Code) and the PowerShell extension. You’ll learn architecture decisions, implementation details, examples, and tips for making your visualizer robust and usable.
Why build a PowerShell Debug Visualizer?
- Faster inspection of complex objects: Instead of printing raw JSON or browsing nested properties in the debugger’s variables tree, a visualizer can present a tailored view (tables, charts, collapsible trees).
- Custom workflows: Visualizers can highlight only the relevant fields, format dates and numbers, or render graphical elements (e.g., dependency graphs).
- Reusable tooling: Once built, a visualizer can be shared across teams to standardize debugging and reduce ad-hoc print-debugging.
Overview & architecture
A PowerShell Debug Visualizer that integrates with VS Code typically consists of three main components:
- PowerShell debug adapter (already provided by the PowerShell extension) — coordinates breakpoints, variable scopes, and evaluation requests.
- A small extension or client (VS Code extension or webview) — receives object data, renders the visualizer UI, and communicates with the debug adapter.
- A serialization/conversion layer — converts PowerShell objects into a JSON shape suitable for the visualizer. This may run in the debug session (via Evaluate requests) or in the extension host.
High-level flow:
- Developer hits a breakpoint in VS Code.
- Extension asks the debug adapter to evaluate an expression (e.g., a variable name or a helper function that serializes the object).
- The result (JSON string or structured data) is returned to the extension.
- Extension renders the data in a webview, tree, or custom panel.
Choosing where serialization runs
Options:
- Run a helper function in the debug session to serialize objects to JSON using ConvertTo-Json or a custom serializer. Pros: full access to the live object, respects types; Cons: can be slow or fail on non-serializable objects.
- Use the debug protocol’s variable inspection APIs to walk object properties from the extension. Pros: avoids heavy serialization; Cons: more complex and requires repeated round-trips.
Recommendation: start with an in-session serializer for MVP, then optimize with incremental property loading if needed.
Step‑by‑step implementation
Prerequisites
- VS Code (latest)
- PowerShell extension for VS Code
- Node.js and npm (for building the extension)
- Basic knowledge of TypeScript/JavaScript and PowerShell scripting
1) Create a simple VS Code extension
Use Yeoman or VS Code’s Extension Generator:
npm install -g yo generator-code yo code
Choose “New Extension (TypeScript)” and a name like powerShell-debug-visualizer.
The extension will add a command (run from Command Palette) and scaffolding for a webview panel.
2) Add a command to open the visualizer panel
In src/extension.ts, register a command that opens a WebviewPanel. Keep the panel ready to receive messages from the extension host.
Example (TypeScript skeleton):
import * as vscode from 'vscode'; export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.commands.registerCommand('psVisualizer.open', () => { const panel = vscode.window.createWebviewPanel( 'psVisualizer', 'PowerShell Debug Visualizer', vscode.ViewColumn.Beside, { enableScripts: true } ); panel.webview.html = getWebviewHtml(); // Handle messages from the webview panel.webview.onDidReceiveMessage(async (msg) => { if (msg.command === 'evaluate') { const result = await evaluateInDebugSession(msg.expression); panel.webview.postMessage({ command: 'result', result }); } }); }) ); } function getWebviewHtml(): string { return `<!doctype html> <html> <body> <div id="root"></div> <script> const vscode = acquireVsCodeApi(); // UI logic here </script> </body> </html>`; }
3) Evaluate PowerShell expressions in the active debug session
Use VS Code’s debug API to send an EvaluateRequest to the active debug session. This requires the debug session to be running and paused.
Add a helper:
async function evaluateInDebugSession(expression: string) { const session = vscode.debug.activeDebugSession; if (!session) { return { error: 'No active debug session' }; } try { const response = await session.customRequest('evaluate', { expression, frameId: 0, context: 'watch' }); return response; } catch (e) { return { error: e.message || String(e) }; } }
Note: The PowerShell debug adapter supports evaluate requests; you might need to pass a valid frameId from stackTrace response. For MVP, try context ‘repl’ or ‘hover’ if ‘watch’ fails.
4) Serialize complex objects in-session
Create a PowerShell helper function that returns JSON for a target variable. Put this in your script or evaluate it on demand:
PowerShell helper (string sent as expression):
function Convert-ForVisualizer($obj) { # Attempt robust JSON serialization, preserving type hints and custom properties $psObj = [pscustomobject]@{ __type = $obj.GetType().FullName __toString = $obj.ToString() Properties = @() } foreach ($p in $obj | Get-Member -MemberType Properties) { try { $value = $obj.$($p.Name) $psObj.Properties += [pscustomobject]@{ Name = $p.Name; Value = $value } } catch { $psObj.Properties += [pscustomobject]@{ Name = $p.Name; Value = "<unavailable>" } } } $psObj | ConvertTo-Json -Depth 5 -Compress } Convert-ForVisualizer -obj $variableName
From TypeScript, evaluate “Convert-ForVisualizer -obj $myVar” (or inject variable name into the expression).
5) Render the JSON in the webview
Inside the webview HTML, use a small JS app (plain JS or a framework) to render tables, trees, and search. Basic example:
<div id="root"></div> <script> const vscode = acquireVsCodeApi(); window.addEventListener('message', event => { const msg = event.data; if (msg.command === 'result') { const data = JSON.parse(msg.result.body); // depends on adapter response shape renderData(data); } }); function requestEval(expr) { vscode.postMessage({ command: 'evaluate', expression: expr }); } function renderData(obj) { document.getElementById('root').innerText = JSON.stringify(obj, null, 2); } </script>
Adjust depending on how the debug adapter encodes returned values (sometimes the body contains a result string).
6) Add convenience features
- Watch expressions: allow typing variable names or expressions to evaluate.
- Property inspector: lazy-load properties by issuing evaluate requests for sub-objects when expanded.
- Formatters: date/time, byte sizes, GUIDs, IP addresses, etc.
- Export: copy JSON or export to a file.
- Custom renderers: show HTML, markdown, images, or graphs for particular types (e.g., System.Drawing.Bitmap => render as data URI).
Example: Visualizing a list of custom objects
Imagine $users is an array of objects with properties Name, Email, LastLogin (DateTime), and Roles (array).
- The serializer returns an object with __type and Properties array where Roles is left as a nested array.
- The webview displays a table: columns for Name, Email, LastLogin (formatted), Roles (count + tooltip showing values).
- Clicking a row opens a detailed pane showing each property and value; arrays are expandable.
This UX reduces time spent expanding nested nodes in the standard Variables pane.
Performance considerations
- Avoid serializing very large collections fully. Provide sampling (first N items) and an option to fetch more.
- Keep JSON depth reasonable; use ConvertTo-Json -Depth with a limit.
- For very large or binary data, return placeholders and provide an on-demand fetch mechanism.
- Cache repeated evaluations for the same object reference (use object ID where available) to reduce round-trips.
Error handling & robustness
- The debug session may not be paused — detect and show a clear message.
- Evaluations can throw exceptions — catch and show stack/exception text.
- Some objects cannot be accessed due to side effects; mark values as “
” rather than crashing. - Respect user security: do not auto-evaluate expressions from untrusted sources.
Packaging & distribution
- Update package.json with activation events (onCommand:psVisualizer.open) and required permissions.
- Test across platforms (Windows, macOS, Linux) since PowerShell types and adapters differ.
- Publish to the VS Code Marketplace if desired; include clear README and usage examples.
Next steps & enhancements
- Integrate with Pester tests to visualize test objects.
- Add layout presets and shareable views via JSON configuration.
- Support alternate editors or debuggers that speak the Debug Adapter Protocol (DAP).
- Build a gallery of custom renderers for common PowerShell types.
Building a PowerShell Debug Visualizer combines PowerShell scripting, the VS Code extension API, and careful UX for inspecting data. Start small with an in-session serializer and a simple webview, then iterate on performance, lazy-loading, and custom renderers as you gather feedback.