Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
24720e1
Fix auto-injection packaging
miloszkukla Aug 13, 2026
3d784de
Document auto-injection limitations
miloszkukla Aug 13, 2026
77d304d
Add .NET Framework 4.7.2 support
miloszkukla Aug 13, 2026
978e30f
Remove .NET Framework target from injector
miloszkukla Aug 14, 2026
4aab3bb
Multitarget shared tests
miloszkukla Aug 14, 2026
c5d873b
Merge net472 support into packaging stack
miloszkukla Aug 14, 2026
9130b85
Move Inspector packaging dependency to Server
miloszkukla Aug 14, 2026
25f965e
Test WPF inspection across targets and modes
miloszkukla Aug 14, 2026
d7c708f
Remove stale InjectorHelper comment
miloszkukla Aug 14, 2026
980ab6d
Avoid reinjecting a loaded Inspector
miloszkukla Aug 14, 2026
0e43d11
Run the WPF inspection matrix in CI
miloszkukla Aug 14, 2026
8b73714
Refresh framework and test documentation
miloszkukla Aug 14, 2026
79787f5
Merge packaging stack into injection documentation
miloszkukla Aug 14, 2026
3e0e375
Merge injection documentation into test stack
miloszkukla Aug 14, 2026
fa91eec
Fix clean CI validation
miloszkukla Aug 14, 2026
09b43fd
Skip payload reference during packaging
miloszkukla Aug 14, 2026
35c1177
Address consolidated review feedback
miloszkukla Aug 14, 2026
285930a
Select Inspector payload by target architecture
miloszkukla Aug 14, 2026
61b73b1
Add repository WPF CLI skill
miloszkukla Aug 14, 2026
d886e13
Add full-content ScrollViewer screenshots
miloszkukla Aug 14, 2026
651ed2e
Preserve screenshot mode regression coverage
miloszkukla Aug 14, 2026
3d35343
Test existing screen screenshot routing
miloszkukla Aug 14, 2026
aa72933
Preserve existing element screenshot rendering
miloszkukla Aug 14, 2026
499e4ae
Address final review findings
miloszkukla Aug 14, 2026
b5b447f
Stabilize hosted WPF matrix startup
miloszkukla Aug 14, 2026
a70c66f
Harden full-content screenshot capture
miloszkukla Aug 14, 2026
ff2ad71
Fix screenshot stitching resource bounds
miloszkukla Aug 14, 2026
aaf6ad8
Scope Inspector dependency resolution
miloszkukla Aug 14, 2026
978185b
Align resolver guidance and sync instructions
miloszkukla Aug 14, 2026
19c200d
Bound full-content capture duration
miloszkukla Aug 14, 2026
867dea7
Bound screenshot rendering and encoding
miloszkukla Aug 14, 2026
cad0813
Bound physical screenshot offset generation
miloszkukla Aug 14, 2026
49093c9
Report Inspector pipe timeout provenance
miloszkukla Aug 14, 2026
61cf29f
Test clean injection payloads and preserve timeouts
miloszkukla Aug 15, 2026
08a0b91
Make overlap scanning cancellation-aware
miloszkukla Aug 15, 2026
a9bb029
Prevent resolver scope leakage and align docs
miloszkukla Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions .agents/skills/wpf-visual-tree-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
name: wpf-visual-tree-cli
description: Operate WpfVisualTreeMcp through the `wpfinspect` or `WpfVisualTreeMcp.Server.exe` command-line interface to discover, attach to, inspect, diagnose, screenshot, and intentionally drive running Windows WPF applications. Use for WPF visual-tree, dependency-property, binding, DataContext, resource, style, layout, screenshot, interaction, wait, snapshot, diff, and live-property experiments from PowerShell or another shell. Choose self-hosting when it is safer or more reliable than runtime auto-injection.
---

# WPF Visual Tree CLI

Use the CLI for quick diagnostics, repeatable shell automation, and environments without a configured MCP client. Keep inspection read-only unless the user explicitly requests interaction or live modification.

## Establish the executable

Prefer the installed .NET tool command:

```powershell
Get-Command wpfinspect -ErrorAction Stop
wpfinspect help
```

If it is unavailable, check for an extracted `WpfVisualTreeMcp.Server.exe`. Install or update the global tool only when authorized:

```powershell
dotnet tool install --global WpfVisualTreeMcp
dotnet tool update --global WpfVisualTreeMcp
```

Run `wpfinspect help` and `wpfinspect <command> --help` before relying on bundled syntax when the installed version differs from the current repository. Read [references/cli-reference.md](references/cli-reference.md) for the current command map and examples.

Distinguish command compatibility from artifact contents. The published v0.12.0 artifacts have a known Auto-injection packaging defect: they omit the native bootstrapper and the complete .NET Framework Inspector dependency closure. The current repository source builds both bootstrapper architectures, packages the dependency closure under both architecture directories, selects the Inspector payload for the target process architecture, resolves co-located .NET Framework dependencies only for the Inspector payload chain, and validates publish/pack payloads. Do not assume an installed package contains that repair until its release notes or package contents confirm it.

Do not treat `dotnet build` alone as a complete Auto-injection source build because it skips the native `.vcxproj`. Read [references/cli-reference.md](references/cli-reference.md) for the native x64/Win32 MSBuild commands before publishing a source build for injection.

## Decide whether to suggest an update

Suggest `dotnet tool update --global WpfVisualTreeMcp` only when the installation is the global .NET tool, a newer stable package exists, and the observed limitation is plausibly version-specific. Check rather than assume:

```powershell
Get-Command wpfinspect -ErrorAction SilentlyContinue
dotnet tool list --global | Select-String '^wpfvisualtreemcp\s'
dotnet tool search WpfVisualTreeMcp --detail
```

Consult the newer release notes or changelog before claiming that an update fixes a problem. Recommend updating when a newer version adds the missing command or option, fixes the encountered error, restores packaging artifacts such as an architecture helper, or brings an older command schema in line with the current reference.

For failed Auto-injection from v0.12.0, specifically check whether a newer stable release includes the native-bootstrapper and dependency-closure repair. If no such release exists, explain that current source contains the fix and offer a source build or self-hosted mode; do not imply that reinstalling the same package repairs it.

Do not recommend an update for limitations that remain architectural: injection blocked by policy or privilege, the need to capture startup diagnostics, or incompatible target frameworks for self-hosting. Route those cases to self-hosted mode as described below.

Do not execute the update merely to check availability. Explain the evidence and ask before changing a working global tool, especially when scripts may depend on a pinned version. If the package is absent, suggest `dotnet tool install --global WpfVisualTreeMcp`; if the user runs an extracted release EXE, direct them to update that release instead because the global-tool command will not replace it.

After an approved update, verify the installed version and live syntax, then retry the smallest safe failing operation:

```powershell
dotnet tool update --global WpfVisualTreeMcp
dotnet tool list --global | Select-String '^wpfvisualtreemcp\s'
wpfinspect help
```

## Follow the operating workflow

1. List candidate processes and parse stdout as JSON:

```powershell
$wpfProcessList = wpfinspect list --compact | ConvertFrom-Json
$wpfProcessList.processes | Format-Table processId, processName, mainWindowTitle, runtimeType, dotNetVersion
```

Do not assign to `$pid`; PowerShell treats `$PID` case-insensitively as a read-only automatic variable. Use `$targetProcessId`.

2. Select an exact process ID. Prefer PID over process name when multiple instances exist. Re-run `list` after an application restart because its PID changes.

3. Probe attachment without injection first:

```powershell
$attachResult = wpfinspect attach --pid $targetProcessId --compact | ConvertFrom-Json
$attachResult.inspectorStatus
```

Continue if the Inspector is already loaded. If it is not loaded, choose auto-injection or self-hosting using the rules below. Auto-inject once; the Inspector remains in that target process for later one-shot commands:

```powershell
wpfinspect attach --pid $targetProcessId --auto-inject
```

4. Inspect from broad to narrow: `tree` or `find`, then `props`, `bindings`, `data-context`, `styles`, `layout`, `evaluate-binding`, or `explain-triggers`. Use returned `elem_...` handles only while the target process and element remain alive.

5. Prefer `wait-for` over sleep-and-retry loops. Use `select-item` instead of clicking virtualized ComboBox/ListBox/ListView/TabControl entries.

6. Keep stdout machine-readable. Add `--compact` for parsing and `--verbose` only when diagnostics on stderr are needed.

## Protect target state

Treat these commands as state-changing and run them only when the user requests the corresponding action: `click`, `select-item`, `set-text`, `send-keys`, `set-property`, `revert-property`, and `clear-binding-errors`. `highlight` visibly alters the target temporarily.

Prefer UI Automation behavior. Use `--physical` only when necessary because it moves the real cursor, focuses or raises the window, and can affect whichever desktop is active. Double-click and right-click are physical operations.

Before `set-property`, take a labeled snapshot when useful. Revert experimental edits after measurement unless the user asks to leave them applied.

## Choose auto-injection or self-hosting

Use auto-injection when the application cannot be modified, its security policy permits runtime DLL injection, and the installed artifact contains the complete injection payload. Current source builds package the x64 and x86 bootstrappers, the `net48` Inspector dependency closure with resolution scoped to the Inspector payload chain, the x86 helper, and the CoreCLR runtime configuration; inherent injection constraints still apply.

Recommend self-hosted mode instead when it solves a concrete auto-injection limitation:

- Security policy, endpoint protection, process hardening, or organizational rules block `CreateRemoteThread`/`LoadLibrary` injection.
- Privilege or user-session boundaries prevent the server from opening and modifying the target process.
- Cross-bitness injection fails because the architecture-matching helper or required x86 .NET 8 runtime is missing.
- Diagnostics must start with application startup so early binding errors or UI initialization behavior are not missed.
- Repeated application launches need a deterministic Inspector endpoint without re-injecting each new PID.
- Injection destabilizes this particular application or its custom CLR hosting environment.

Self-hosting requires source changes: reference the Inspector target matching the application and call `InspectorService.Initialize(...)` during WPF startup. The current repository Inspector targets `net472`, `net48`, and `net8.0-windows`, so .NET Framework 4.7.2 applications can self-host directly without retargeting to 4.8. Match a modern WPF application to `net8.0-windows`, not plain `net8.0`.

Do not confuse self-hosting support with the injected payload. Auto-injection into .NET Framework processes deliberately uses the `net48` Inspector and requires the .NET Framework 4.8 runtime. Adding `net472` enables source-integrated self-hosting; it does not add a `net472` Auto-injection payload.

Do not suggest self-hosting when target source cannot be changed. Do not suggest MCP as a remedy for blocked injection; MCP uses the same Inspector deployment choices.

## Recover from common failures

- No processes: verify the WPF app has a visible main window, match privilege level, and run `list` again.
- Inspector not loaded: inspect `inspectorStatus`; then explicitly auto-inject or explain the self-hosted alternative.
- Target restarted or handle failed: re-list, use the new PID, attach, and reacquire element handles.
- x64 server to x86 target fails: verify the bundled x86 helper, x86 bootstrapper, and x86 .NET 8 runtime. If they are absent from v0.12.0, recommend a newer release containing the packaging repair or a current source build; prefer self-hosting if installing them is unsuitable.
- Native bootstrapper or managed dependency is missing: treat this as the known v0.12.0 packaging defect when applicable. Do not keep retrying injection; update to a release containing the repair, use a verified current source build, or self-host.
- ARM64 target: explain that native ARM64 Auto-injection is unsupported; use a supported x64/x86 target or self-host when the application architecture and Inspector reference permit it.
- Popup/menu missing from screenshot: use `screenshot --mode screen` while the window is visible and unobstructed. Use default `render` mode for covered windows and ordinary controls.
- Unscrolled ScrollViewer content is missing: when installed help lists it, pass the precise ScrollViewer or owning control handle to `screenshot --full-content`; use a larger `--max-height` for long output. This render-mode option restores the original scroll position after paging virtualized content. It cannot be combined with `--mode screen`, logically scrolling virtualized controls with horizontal overflow are unsupported, output is area-downscaled to the built-in safe limit, and captures that exceed the retained-bitmap or encoded-PNG budgets or 25-second cooperative deadline fail safely.
- Command fails opaquely: rerun that command with `--verbose`, keeping stderr separate from stdout JSON.
- Missing command, option, helper, or known fixed behavior: check installed versus current package versions and release notes; suggest a global-tool update only when the evidence connects the limitation to version drift.
4 changes: 4 additions & 0 deletions .agents/skills/wpf-visual-tree-cli/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "WPF Visual Tree CLI"
short_description: "Inspect and drive WPF apps from the CLI"
default_prompt: "Use $wpf-visual-tree-cli to inspect a running WPF app safely from the command line."
167 changes: 167 additions & 0 deletions .agents/skills/wpf-visual-tree-cli/references/cli-reference.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# WpfVisualTreeMcp CLI reference

The command map in this reference matches the current WpfVisualTreeMcp repository. Treat the installed command's `help` output as authoritative; v0.12.0 does not contain every current option. Packaging and target-framework notes describe the current repository state; verify which later release first contains them.

## Installation and mode selection

```powershell
dotnet tool install --global WpfVisualTreeMcp
wpfinspect help
```

For an existing global-tool installation, inspect version drift without changing it:

```powershell
dotnet tool list --global | Select-String '^wpfvisualtreemcp\s'
dotnet tool search WpfVisualTreeMcp --detail
```

When a newer stable release specifically addresses the observed limitation and the user approves the change:

```powershell
dotnet tool update --global WpfVisualTreeMcp
wpfinspect help
```

This update command does not replace a manually extracted `WpfVisualTreeMcp.Server.exe`. Update that installation from GitHub Releases instead.

The published v0.12.0 package and ZIP omit required Auto-injection files. Reinstalling v0.12.0 does not fix that defect. Prefer a later release whose notes include the native-bootstrapper and .NET Framework dependency-closure repair, or use a verified current source build or self-hosted mode.

For an Auto-injection source build, build both native bootstrapper platforms with Visual Studio MSBuild before publishing the server; `dotnet build` alone skips the `.vcxproj`:

```powershell
msbuild src/WpfVisualTreeMcp.Bootstrapper/WpfVisualTreeMcp.Bootstrapper.vcxproj /m /p:Configuration=Release /p:Platform=x64
msbuild src/WpfVisualTreeMcp.Bootstrapper/WpfVisualTreeMcp.Bootstrapper.vcxproj /m /p:Configuration=Release /p:Platform=Win32
dotnet publish src/WpfVisualTreeMcp.Server/WpfVisualTreeMcp.Server.csproj --configuration Release --output ./publish
```

The release ZIP exposes the same CLI as `WpfVisualTreeMcp.Server.exe`. Starting the executable with no arguments runs the MCP stdio server; a recognized subcommand runs one CLI operation.

Every command except `list` accepts `--pid <id>` or `--process <name>`. Prefer `--pid`. Global options are `--compact`, `--verbose`, and `--help`/`-h`.

## Command map

### Discover and attach

```text
list
attach --pid|--process [--auto-inject]
```

`attach` without `--auto-inject` connects only when the Inspector is already hosted or was injected earlier. `--auto-inject` loads it once into the current target process.

### Find and inspect

```text
tree --pid [--root H] [--depth N]
props --pid --handle H
find --pid [--type T] [--name N] [--text S] [--visible-only] [--root H] [--max N] [--filter JSON]
find-deep --pid (--type T | --name N | --text S) [--visible-only] [--root H] [--filter JSON]
bindings --pid --handle H
binding-errors --pid
data-context --pid --handle H
resources --pid [--scope application|element] [--handle H]
styles --pid --handle H
layout --pid --handle H
evaluate-binding --pid --handle H --property P
explain-triggers --pid --handle H [--property P]
```

`find` limits results to 50 by default. Filters combine with AND. `find-deep` requires at least one of type, name, or text so the search is bounded semantically.

### Observe and compare

```text
watch-property --pid --handle H --property P
wait-for --pid (--type T | --name N | --text S) [--condition visible|exists|enabled|hidden] [--timeout MS] [--poll MS]
snapshot --pid [--handle H] [--label L] [--depth N]
diff --pid --before L1 --after L2
```

`watch-property` registers the watch, but the one-shot CLI cannot stream change events; re-read properties. `wait-for` defaults to a 10-second timeout and 250 ms poll interval, with a 25-second maximum timeout.

### Capture and export

```text
highlight --pid --handle H [--duration MS]
export --pid [--handle H] [--format json|xaml] [--out FILE]
screenshot --pid [--handle H] [--out FILE] [--max-width N] [--max-height N] [--mode render|screen] [--full-content]
```

`render` is the screenshot default and works if the window is covered, but it omits popup windows. `screen` captures visible popups, dropdowns, context menus, and tooltips but requires an unobstructed visible window.

Add `--full-content` in render mode to capture all content in a `ScrollViewer`. Pass the most precise element handle because a control template or subtree can contain more than one ScrollViewer. Ordinary content is rendered directly; virtualized content is paged and stitched, with the original scroll offsets restored. Increase `--max-height` when the default limit would make a long image unreadably small. Full-content capture cannot be combined with `--mode screen`, and logically scrolling virtualized controls with horizontal overflow are unsupported. Output is area-downscaled to at most 8,388,608 pixels; capture fails safely if retained frames plus output exceed 67,108,864 pixels, the encoded PNG exceeds 33,554,432 bytes, or chunked work reaches the 25-second cooperative deadline.

### Change application state

```text
clear-binding-errors --pid
click --pid --handle H [--physical] [--click-type single|double|right]
select-item --pid --handle H (--item-text S | --index N)
set-text --pid --handle H --text VALUE [--physical]
send-keys --pid --keys COMBO [--handle H]
set-property --pid --handle H --property P --value V
revert-property --pid (--all | [--handle H] [--property P])
```

`set-property` replaces a binding with a local value when applied to a bound dependency property. `revert-property` restores the prior binding, local value, or default.

## Read-only diagnostic example

```powershell
$wpfProcessList = wpfinspect list --compact | ConvertFrom-Json
$targetProcessId = ($wpfProcessList.processes | Where-Object processName -eq 'MyApp' | Select-Object -First 1).processId

$attachResult = wpfinspect attach --pid $targetProcessId --compact | ConvertFrom-Json
if ($attachResult.inspectorStatus -like 'Not loaded*') {
wpfinspect attach --pid $targetProcessId --auto-inject
}

$buttons = wpfinspect find --pid $targetProcessId --type Button --text Save --visible-only --compact | ConvertFrom-Json
wpfinspect binding-errors --pid $targetProcessId
```

Check for multiple matching processes before using `Select-Object -First 1`; do not silently choose one in an ambiguous live environment.

## Reversible experiment example

```powershell
wpfinspect snapshot --pid $targetProcessId --label before
wpfinspect set-property --pid $targetProcessId --handle elem_00000052 --property Width --value 300
wpfinspect snapshot --pid $targetProcessId --label after
wpfinspect diff --pid $targetProcessId --before before --after after
wpfinspect revert-property --pid $targetProcessId --handle elem_00000052 --property Width
```

## Self-hosted startup shape

Use only with an Inspector build compatible with the target application's framework. Current source provides `net472`, `net48`, and `net8.0-windows` Inspector targets:

```csharp
using System.Diagnostics;
using System.Windows;
using WpfVisualTreeMcp.Inspector;

public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
InspectorService.Initialize(Process.GetCurrentProcess().Id);
}

protected override void OnExit(ExitEventArgs e)
{
InspectorService.Instance?.Dispose();
base.OnExit(e);
}
}
```

After self-hosting, run `attach` without `--auto-inject`.

## Sources

- Repository: <https://github.com/faze79/WPFVisualTreeMcp>
- NuGet tool: <https://www.nuget.org/packages/WpfVisualTreeMcp>
- CLI implementation for v0.12.0: <https://github.com/faze79/WPFVisualTreeMcp/blob/v0.12.0/src/WpfVisualTreeMcp.Server/Cli/CliRunner.cs>
Loading