How to Debug PowerToys Modules Effectively with Visual Studio: A Complete Guide

PowerToys modules are loaded as dynamic-link libraries (DLLs) into the central Runner process, allowing you to debug any utility by attaching the Visual Studio debugger to PowerToys.exe and stepping through source code with full symbol support.

PowerToys is a collection of utilities for power users that runs as modular components within a unified host process. Debugging PowerToys modules effectively with the Visual Studio debugger requires understanding the dynamic loading architecture shared between the Runner and individual utilities. This guide walks through the exact workflow used by the microsoft/PowerToys repository maintainers to trace execution, inspect state, and diagnose crashes across both native C++ and managed C# codebases.

Understanding the PowerToys Debugging Architecture

The Runner and Module Loader

The PowerToys Runner (PowerToys.exe) serves as the host executable that loads each utility via the module loader subsystem. Located in src/runner/, the Runner calls LoadLibrary and GetProcAddress to dynamically load module DLLs that implement the ITool interface. Each utility—whether FancyZones, PowerRename, or PowerToys Run—resides in src/modules/<module_name>/ and exports standard lifecycle methods including Load, Enable, and Disable. Because modules execute within the same process space as the Runner, a single Visual Studio debugging session can step across module boundaries without switching attach contexts.

Debug Symbols and Configuration

When building the Debug configuration, the solution generates .pdb symbol files for both the Runner and every module, typically output to x64/Debug/ or x86/Debug/. These symbols enable source-level debugging inside src/runner/ModuleLoader.cpp and individual module implementations. The Settings UI runs as a separate WinUI process in src/settings-ui/ that communicates with the Runner over named pipes, though most module logic debugging occurs within the main Runner process.

Step-by-Step Guide to Debugging PowerToys Modules

1. Configure the Solution for Debug Mode

Open PowerToys.sln at the repository root and select the Debug configuration to ensure .pdb files are generated for all projects. According to the build guidelines in tools/build/BUILD-GUIDELINES.md, this configuration preserves optimization settings compatible with breakpoints while producing the symbol files required for stack unwinding and variable inspection.

2. Launch the Debugger and Enable Your Target Module

Press F5 or select Debug → Start Debugging to build the solution and launch PowerToys.exe under the debugger. Once running, enable your target module through the PowerToys Settings UI (or via hotkey if already configured). This action forces the Runner to load the module's DLL into the process space, making its code available to the debugger.

3. Setting Breakpoints in Module Source Files

Navigate to the module's source code—for example, src/modules/fancyzones/FancyZones.cpp—and set breakpoints on the relevant functions. Breakpoints appear hollow until the DLL loads; once you trigger the module's activation, Visual Studio resolves the symbols and the breakpoint icon fills, indicating active binding. For instance, placing a breakpoint inside FancyZones::OnHotKeyPressed() will pause execution when the hotkey combination is pressed.

void FancyZones::OnHotKeyPressed()
{
    // VS will stop here when the FancyZones module is active
    // and the hot‑key is triggered.
    LOG_DEBUG(L"FancyZones hot‑key pressed"); // Uses PowerToysLogger
    // …rest of implementation…
}

4. Inspecting State Across Module Boundaries

When execution pauses, use Debug → Windows → Locals, Watch, and Immediate to inspect variables. Step through code using F10 (Step Over) and F11 (Step Into) to follow logic across the boundary between src/runner/PowerToys.cpp and the module's implementation files. The call stack window shows the full execution path from the Runner's message loop through the module loader and into the specific utility code.

Advanced Visual Studio Debugging Techniques for PowerToys

Attaching to a Running Process

If you need to debug a module loaded after initial launch (such as after changing a Group Policy setting), use Debug → Attach to Process…, select PowerToys.exe from the list, and then enable the target module. This method is essential for diagnosing issues that only reproduce after specific runtime configuration changes without restarting the entire debugging session.

Mixed-Mode Debugging for C++ and C#

PowerToys combines native C++ modules with managed C# components in the Settings UI. To debug both simultaneously, configure the debugger type in project properties under Debugging → Debugger Type to enable both Native Code and Managed (CoreCLR) engines. This allows seamless stepping across the interop boundary when investigating issues in the settings view models located in src/settings-ui/PowerToys.Settings.UI/Settings/SettingsPageViewModel.cs.

Troubleshooting Symbol Loading Issues

If breakpoints remain hollow (unbound), open the Modules window (Debug → Windows → Modules) and verify the correct .pdb file is loaded for the DLL in question. You can manually load symbols via Load Symbols if the automatic search path fails, pointing directly to x64/Debug/ or your custom output directory. Refer to doc/devdocs/tools/debugging-tools.md for specific symbol path configurations used by the PowerToys team.

Using PowerToysLogger for Diagnostic Logging

Complement breakpoint debugging with the PowerToysLogger framework described in doc/devdocs/logging.md. Logs write to %LOCALAPPDATA%\Microsoft\PowerToys\PowerToys_<date>.log and provide historical context for race conditions or timing-sensitive bugs that are difficult to catch interactively.

// Example: Enabling a module programmatically for quick debugging
// (Only for local development – not part of the shipped product)
using Microsoft.PowerToys.Settings.UI.Library;
var settings = SettingsRepository.GetInstance();
settings.SetEnabled(ModuleEnum.FancyZones, true);

Summary

  • PowerToys modules are DLLs loaded by the Runner process via LoadLibrary, enabling single-process debugging of all utilities.

  • Use the Debug configuration to generate .pdb symbols, then launch with F5 to attach to PowerToys.exe.

  • Enable the target module through Settings UI to trigger DLL loading, then set breakpoints in files like src/modules/fancyzones/FancyZones.cpp.

  • Configure mixed-mode debugging to step through both native C++ Runner code and managed C# Settings UI components.

  • Consult doc/devdocs/tools/debugging-tools.md for official guidance on symbol paths and remote debugging scenarios.

Frequently Asked Questions

How do I debug a PowerToys module that crashes during startup?

Attach the Visual Studio debugger to PowerToys.exe before enabling the problematic module, then enable it through the Settings UI. Set breakpoints early in the module's Load or Enable methods—typically found in the module's main .cpp file under src/modules/<module_name>/—to catch initialization errors before they propagate.

Can I debug both C++ and C# code in the same PowerToys debugging session?

Yes. Configure the startup project properties to use the Mixed debugger type (supporting both Native and Managed CoreCLR) in Debugging → Debugger Type. This allows you to step from the native Runner code in src/runner/ModuleLoader.cpp directly into managed Settings UI code in src/settings-ui/ without restarting Visual Studio.

Where are the debug symbols located for PowerToys modules?

Debug symbols (.pdb files) are generated in x64/Debug/ or x86/Debug/ (depending on your platform target) when building the Debug configuration. If Visual Studio cannot locate symbols automatically, manually specify the path through Debug → Windows → Modules → Load Symbols, or copy the .pdb files adjacent to their corresponding DLLs.

Why won't my breakpoints hit in a PowerToys module?

Breakpoints remain inactive if the module DLL has not yet been loaded into the Runner process. Ensure you have triggered the module activation (via Settings UI or hotkey) so that src/runner/ModuleLoader.cpp executes LoadLibrary. Additionally, verify that the timestamp of the source file matches the compiled binary and that the correct .pdb symbol file is loaded in the Modules window.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →