Skip to content

Add .NET 4.7.2 support and reliable WPF injection packaging - #22

Open
miloszkukla wants to merge 36 commits into
faze79:mainfrom
miloszkukla:agent/net472-packaging-integration
Open

Add .NET 4.7.2 support and reliable WPF injection packaging#22
miloszkukla wants to merge 36 commits into
faze79:mainfrom
miloszkukla:agent/net472-packaging-integration

Conversation

@miloszkukla

@miloszkukla miloszkukla commented Aug 14, 2026

Copy link
Copy Markdown

Summary

  • add .NET Framework 4.7.2 support to the Inspector and Shared contracts while keeping Injector and InjectorHelper on net8.0
  • build and package the x64/Win32 native bootstrappers plus the complete .NET Framework Inspector dependency closure
  • make Server own the packaged Inspector payload and validate it during publish and NuGet pack
  • document self-hosted and auto-injection modes, requirements, limitations, and diagnostics
  • add multi-targeted Shared tests and a live 12-case WPF matrix covering net472, net48, and net8.0-windows, x86/x64, and both inspection modes
  • detect an already-running Inspector through a fast named-pipe probe so repeat attachment reuses it without reinjection
  • add bounded, cancellable full-content screenshots for scrollable and non-virtualized WPF elements
  • update CI to build native payloads, run the live matrix, and verify published/package contents; update release validation for the native and managed payload
  • add the repo-scoped wpf-visual-tree-cli agent skill and make AGENTS.md delegate repository guidance to CLAUDE.md

Why

The v0.12.0 release artifacts omit WpfInspectorBootstrapper.dll and the managed Inspector dependency closure, so auto-injection cannot work from the official ZIP or NuGet package. The Inspector also did not target .NET Framework 4.7.2 even though its WPF implementation is compatible with that runtime.

On .NET Framework, managed-module enumeration does not reliably reveal the loaded Inspector. A second attach --auto-inject could therefore attempt injection again. The Inspector's named pipe is the authoritative readiness signal and now provides a short pre-injection check, while the longer retry remains reserved for post-injection startup.

Review feedback incorporated

  • enable private dependency resolution for both .NET Framework Inspector targets
  • scope that resolver to the Inspector payload chain and directory so unrelated application binds are unchanged
  • allow unknown-requester dependency resolution only while Inspector initialization or request processing is active
  • suppress execution-context flow when starting the persistent IPC server task so it cannot inherit the initialization resolver scope
  • make the self-hosting example compile without relying on an omitted using
  • report Loaded (existing) when the Inspector was already present, including the pipe-timeout path
  • replace the 700 ms retrying pre-check with a 100 ms single-shot pipe probe
  • select the Inspector payload from the target process architecture rather than defaulting to x64
  • collect both architecture payloads at target execution time so clean publish and pack cannot miss newly built outputs
  • treat named-pipe availability as operational readiness and loaded-module presence as diagnostic only
  • run AutoInjection matrix cases against sample outputs without an Inspector project reference or dependency closure
  • preserve the full-content execution-deadline error separately from Dispatcher scheduling timeouts
  • bound full-content screenshot raw/output/PNG memory, render and compose in cancellable tiles, cap physical offset preflight work, and derive logical-page stitching from realized geometry instead of pixel equality

Injector remains on net8.0: it no longer references the Windows-targeted Inspector assembly, and changing it to net8.0-windows would also force a Server/tool TFM change. Missing Inspector payloads already produce FileNotFoundException in InjectIntoProcess.

Validation

  • exact-head fork workflow run 32844813301: all jobs passed for commit a9bb029, including clean publish, native and managed builds, the full test suite, the 12-case WPF matrix, payload verification, NuGet pack, code quality, and artifact upload
  • dotnet build WpfVisualTreeMcp.sln -c Release --no-incremental --no-restore: passed with no errors (existing nullable warnings only)
  • full VSTest suite: 138 passed with 1 expected integration-theory skip in the ordinary solution run
  • clean dotnet publish after removing all managed outputs: 35 native payload files, including both .NET Framework closures, InjectorHelper payloads, and both CoreCLR Inspector payloads
  • clean dotnet pack: 94 archive entries with all required injection payload entries present
  • local live WPF matrix: 12/12 passed across three frameworks, two architectures, and both self-hosted/auto-injection modes, using dependency-clean AutoInjection targets
  • repeat attach --auto-inject uses Loaded (existing) and the target remains inspectable
  • focused regressions cover screenshot bounds/deadlines, scoped .NET Framework dependency resolution, and persistent IPC task context isolation
  • PowerShell syntax checks passed for both scripts; YAML parsing passed for all three workflows; all relative Markdown links resolve
  • repository CLI skill passes quick_validate.py and agrees with the documented 28 tools/commands and target frameworks

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands framework compatibility and hardens distribution for auto-injection by multi-targeting the Inspector/Shared contracts down to .NET Framework 4.7.2, packaging the full injection payload (native bootstrappers + managed dependency closure), adding validation during publish/pack, and introducing integration coverage across frameworks/architectures/modes.

Changes:

  • Multi-target WpfVisualTreeMcp.Inspector and WpfVisualTreeMcp.Shared for net472/net48/net8.0(-windows) and add .NET Framework assembly-resolution support for co-located private dependencies.
  • Make the Server own and validate the complete auto-injection payload during Publish/Pack, and adjust payload layout (including CoreCLR runtimeconfig naming).
  • Add Shared and end-to-end integration test projects + a PowerShell runner, and update docs/workflows to build/verify payloads and run the inspection matrix.

Reviewed changes

Copilot reviewed 29 out of 31 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
WpfVisualTreeMcp.sln Adds new Shared and Integration test projects to the solution.
tests/WpfVisualTreeMcp.Tests/ProcessInjectorTests.cs Updates injector unit test setup to avoid relying on Inspector path resolution for invalid PID case.
tests/WpfVisualTreeMcp.Shared.Tests/WpfVisualTreeMcp.Shared.Tests.csproj New multi-targeted Shared contract test project.
tests/WpfVisualTreeMcp.Shared.Tests/SharedModelsTests.cs Adds basic model construction/shape tests for Shared DTOs.
tests/WpfVisualTreeMcp.Shared.Tests/IpcSerializerTests.cs Adds regression coverage ensuring derived IPC request properties serialize/round-trip correctly.
tests/WpfVisualTreeMcp.IntegrationTests/WpfVisualTreeMcp.IntegrationTests.csproj New net8.0-windows integration test project for the live WPF matrix.
tests/WpfVisualTreeMcp.IntegrationTests/IntegrationTheoryAttribute.cs Adds an opt-in theory attribute that skips unless integration env vars are set.
tests/WpfVisualTreeMcp.IntegrationTests/InspectionModeMatrixTests.cs Adds 12-case framework/arch/mode CLI-driven inspection matrix tests.
tests/WpfVisualTreeMcp.IntegrationTests/AssemblyInfo.cs Disables test parallelization to reduce flakiness for process-level integration tests.
tests/run-integration-tests.ps1 Adds a local runner to build native payload + publish samples and execute the live matrix.
src/WpfVisualTreeMcp.Shared/WpfVisualTreeMcp.Shared.csproj Adds net472 target and System.Text.Json package reference for .NET Framework TFMs.
src/WpfVisualTreeMcp.Server/WpfVisualTreeMcp.Server.csproj Packages/copies the full injection payload and validates required artifacts during publish/pack.
src/WpfVisualTreeMcp.Server/Services/ProcessManager.cs Adds fast named-pipe probe to detect an already-running Inspector and standardizes status messaging.
src/WpfVisualTreeMcp.Inspector/WpfVisualTreeMcp.Inspector.csproj Adds net472 target and updates explicit WPF refs for .NET Framework builds.
src/WpfVisualTreeMcp.Inspector/InspectorService.cs Adds .NET Framework AssemblyResolve hook for co-located private dependency resolution.
src/WpfVisualTreeMcp.InjectorHelper/WpfVisualTreeMcp.InjectorHelper.csproj Removes an obsolete warning-suppression comment and keeps helper configuration.
src/WpfVisualTreeMcp.Injector/WpfVisualTreeMcp.Injector.csproj Simplifies Injector to net8.0 only and removes Inspector project reference.
src/WpfVisualTreeMcp.Injector/README.md Rewrites injector docs to reflect real injection flow, requirements, and diagnostics.
src/WpfVisualTreeMcp.Injector/ProcessInjector.cs Updates Inspector DLL path probing to support packaged layouts.
samples/SampleWpfApp/SelfHostedInspector.cs Adds a helper wrapper for starting/stopping the Inspector in self-hosted mode.
samples/SampleWpfApp/SampleWpfApp.csproj Multi-targets the sample app for net472/net48/net8.0-windows and adds explicit platforms.
samples/SampleWpfApp/App.xaml.cs Makes self-hosting conditional via env var and routes start/stop through helper.
README.md Documents auto-injection mode, requirements, diagnostics, multi-target support, and updated build/test commands.
docs/TOOLS_REFERENCE.md Clarifies scope of the reference and points to README/CLI for the full tool list.
docs/GETTING_STARTED.md Updates installation and framework support guidance, including sample run command.
docs/ARCHITECTURE.md Updates architecture notes for multi-runtime Inspector and injection behavior/security model.
CLAUDE.md Updates local build/test guidance and adds the integration matrix runner command.
CHANGELOG.md Adds unreleased notes covering payload packaging fixes and runtimeconfig naming.
.github/workflows/release.yml Builds native bootstrappers and verifies publish + NuGet payload contents during release.
.github/workflows/publish-mcp-registry.yml Updates checkout action major version.
.github/workflows/build.yml Adds native bootstrapper build, payload verification, x86 runtime install, and integration matrix execution.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/WpfVisualTreeMcp.Injector/ProcessInjector.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 49 changed files in this pull request and generated no new comments.

Suppressed comments (1)

.github/workflows/release.yml:44

  • The release job still publishes without running the advertised 12-case live matrix. dotnet test skips IntegrationTheory unless the two integration environment variables are prepared, and tag pushes do not match the branch-only triggers in build.yml, so a tagged commit can release untested injection payloads. Install the x86 runtimes and invoke the integration runner in this workflow before creating the release publish.
    - name: Publish MCP Server from clean checkout
      run: dotnet publish src/WpfVisualTreeMcp.Server/WpfVisualTreeMcp.Server.csproj --no-restore --configuration Release --output ./publish/server

@miloszkukla
miloszkukla force-pushed the agent/net472-packaging-integration branch from 7d78e21 to 49093c9 Compare August 15, 2026 00:21
@miloszkukla

Copy link
Copy Markdown
Author

Follow-up on the remaining status-provenance issue from the independent review: fixed in 49093c9.

When the Inspector module was already loaded but its named pipe never becomes ready, attachment now reports Loaded (existing) - pipe timeout. A newly injected Inspector that times out still reports Injected - pipe timeout. A theory test covers both paths.

I also assessed Copilot's latest suppressed suggestion to rerun the full 12-case live matrix in release.yml and intentionally left that workflow unchanged. build.yml runs the matrix for pull requests and pushes to main/develop; the release job separately rebuilds, runs the managed suite, and validates the publish/package payload. Repeating the full GUI matrix in the tag workflow would duplicate a long-running test and its x86-runtime setup. If fully independent validation of arbitrary tagged commits is desired, that is a separate release-policy change rather than a correctness fix for this PR.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 49 changed files in this pull request and generated no new comments.

Suppressed comments (2)

tests/WpfVisualTreeMcp.IntegrationTests/InspectionModeMatrixTests.cs:83

  • The AutoInjection cases launch the same SampleWpfApp artifact used for self-hosting, but that project always references Inspector, so its output already contains Inspector, Shared, and the .NET Framework dependency closure. The target can therefore satisfy injected dependencies from its own application directory, allowing this matrix to pass even if the packaged payload or private resolver is broken. Run auto-injection against a sample built without that reference/dependency closure (or a separate clean target artifact) so the packaging guarantee is exercised end to end.
    src/WpfVisualTreeMcp.Inspector/InspectorService.cs:269
  • When the new cooperative deadline expires, CaptureFullContent throws TimeoutException, but the existing HandleRequestAsync catch converts every TimeoutException into Request timeout: UI thread is busy. A capture that actively runs until its 25-second budget is therefore reported as a dispatcher problem. Preserve a distinct full-content deadline error (for example, by detecting this token's cancellation separately) and reserve the busy message for the Dispatcher scheduling timeout.
            using var fullContentCaptureCts = requestType == "CaptureScreenshot"
                ? new CancellationTokenSource(FullContentCaptureTimeout)
                : null;
            var fullContentCaptureToken = fullContentCaptureCts?.Token ?? CancellationToken.None;

@miloszkukla

Copy link
Copy Markdown
Author

Addressed both suppressed findings from the latest Copilot review in 61cf29f.

  • Auto-injection matrix targets are now published from the existing sample project without the Inspector project reference or dependency closure. Each AutoInjection case asserts that WpfVisualTreeMcp.Inspector.dll is absent before attach.
  • That clean target exposed a real masked defect in all four .NET Framework AutoInjection cases: System.Runtime.CompilerServices.Unsafe is requested with no RequestingAssembly, so the safety-scoped resolver rejected it. Unknown-requester resolution is now allowed only inside an AsyncLocal Inspector processing scope; unrelated application binds remain untouched outside that scope.
  • Full-content cooperative deadline failures now retain Full-content capture exceeded its execution deadline. at the request boundary, while genuine Dispatcher timeouts still report Request timeout: UI thread is busy.

Validation at exact head 61cf29f:

  • Full solution: 136 passed, 1 expected live-matrix skip, 0 failed across net8.0, net472, and net48 test targets.
  • Live framework × architecture × mode matrix: 12/12 passed using dependency-clean AutoInjection targets.
  • Native bootstrapper: x64 and Win32 both built successfully via this machine's command-line v145 override; no .vcxproj toolset change is tracked.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 49 out of 50 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 49 out of 50 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/WpfVisualTreeMcp.Inspector/ScreenshotCapture.cs:521

  • The overlap scan is not cancellation-aware even though it can compare the full viewport up to five times. If the 25-second token expires during these tile loops, the UI thread keeps processing CopyPixels/byte comparisons until the entire scan returns, so a full-content request can substantially exceed its advertised execution deadline. Pass the capture token through FindVerticalOverlap/VerticalRegionsEqual and check it inside the tile loops.
                    y += previous.PixelHeight - FindVerticalOverlap(
                        previous, bitmap, expectedOverlap, 2);

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 49 out of 50 changed files in this pull request and generated no new comments.

Suppressed comments (1)

.github/workflows/build.yml:60

  • The PR describes run 31855379392 as an exact-head validation, but that run executed commit 61cf29f; the current PR head is 08a0b91, which subsequently changed screenshot overlap cancellation and its tests, and the current head has no check runs. Re-run this matrix for the current head (and update the Validation section) before treating the listed CI results as coverage of these changes.
    - name: Test WPF inspection matrix
      shell: pwsh
      run: ./tests/run-integration-tests.ps1 -SkipNativeBuild

@miloszkukla

Copy link
Copy Markdown
Author

Addressed the latest suppressed validation finding. The full fork workflow was rerun at the current PR head 08a0b916ca66843619dcc7c37dd8edd4d66d0e37 and passed: https://github.com/miloszkukla/WPFVisualTreeMcp/actions/runs/31885173690

The PR Validation section now references that exact-head run. It includes the 12-case WPF integration matrix and the complete managed suite (137 passed, 1 expected skip).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The dependency-resolution scope leaks into the persistent IPC task through AsyncLocal execution-context flow.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 49/50 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/WpfVisualTreeMcp.Inspector/IpcServer.cs Outdated
@miloszkukla

miloszkukla commented Aug 25, 2026

Copy link
Copy Markdown
Author

Follow-up validation at exact head a9bb029: fork workflow 32844813301 passed all jobs, including clean publish, both native bootstrapper architectures, 138 managed tests with 1 expected ordinary-run matrix skip, the live 12/12 WPF matrix, both .NET Framework target builds, payload verification, NuGet pack, and artifact upload. The valid dependency-resolver context-flow finding was fixed and replied to inline; the thread is resolved.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Despite comprehensive passing validation, it changes native injection, in-process dependency resolution, packaging, and UI-thread capture behavior that warrant final human review.

Review details
  • Files reviewed: 50/51 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants