Mihadeth.LegacyWrapper 1.0.0-preview.3

This is a prerelease version of Mihadeth.LegacyWrapper.
dotnet tool install --global Mihadeth.LegacyWrapper --version 1.0.0-preview.3
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Mihadeth.LegacyWrapper --version 1.0.0-preview.3
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Mihadeth.LegacyWrapper&version=1.0.0-preview.3&prerelease
                    
nuke :add-package Mihadeth.LegacyWrapper --version 1.0.0-preview.3
                    

Legacy Wrapper

A .NET CLI tool that prepares a legacy .NET Framework application for cloud deployment without modifying a single file of the original codebase. It reads your existing solution, produces a detailed compatibility report, packages your already-compiled binaries as NuGet packages declared as netstandard2.0, and scaffolds a modern .NET 10 host project that references them.

This is the Strangler Fig pattern applied to .NET migration. The legacy solution keeps running in production untouched; a new, independent wrapper solution is generated alongside it, and you migrate internals incrementally afterwards — one package at a time — instead of doing a big-bang rewrite.

The technical premise: .NET Framework 4.6.1 and later implement .NET Standard 2.0, and so does modern .NET. Framework-compiled assemblies declared as netstandard2.0 in a .nuspec load on .NET 10 through the compatibility shim.

Status: preview (1.0.0-preview.2). The command surface, defaults, and diagnostic IDs described here may change before 1.0.0. This page documents 1.0.0-preview.2 specifically.

MIT licensed. Questions, bugs, or help wrapping a specific solution: mihadeth@gmail.com (see Support at the end of this page).

⚠️ Important — what you get, and what you still do.

Automated: analysis, compatibility reporting, NuGet packaging of your compiled binaries, and a complete .NET 10 host scaffold — project file, DI wiring, configuration bridge, Dockerfile.

Manual — HTTP endpoints. Web API controllers, IIS handlers, and REST-style WCF are generated with correct route templates and HTTP-method attributes, but the parameter signatures are stubbed — route tokens are emitted as string regardless of the legacy parameter's real type, request bodies as a single [FromBody] object?, and query-string parameters not at all. The action bodies throw NotImplementedException until you retype the signature and wire each action to your business-logic classes. This is structural: legacy ApiController and IHttpHandler types derive from System.Web types that cannot load on .NET 10, so the generated proxy cannot instantiate them. Budget roughly one edit per endpoint.

Manual — connection strings and detected secrets. Replaced with placeholders rather than copied. The wrapper connects to nothing until you supply real values.

The exception: WCF services hosted through CoreWCF register and invoke your legacy implementations directly, with no manual step.

On this page: Who this is for · When not to use it · Requirements and prerequisites (start with the .NET Framework 4.6.1 floor) · Install · Quick start · Command reference · Understanding the compatibility report · What gets generated · Remediation and IL rewriting · Troubleshooting · Limitations · Support

New here? Read Requirements and prerequisites, then run legacy-wrapper analyze — it is read-only, needs no legacy build tools, and tells you within minutes whether the rest of this page applies to you.


Who this is for

Use Legacy Wrapper when you have:

  • A .NET Framework 4.6.1 or later solution (4.7.2+ recommended) whose value is in business-logic assemblies — services, domain models, data access, calculation engines.
  • WCF services, especially ones using BasicHttpBinding, NetTcpBinding, WSHttpBinding, or NetNamedPipeBinding. These get the most automation, because CoreWCF hosting invokes your real types.
  • Web API or MVC controllers whose logic you are willing to re-point at the underlying services during a controlled wire-up pass.
  • A mandate to containerize or move to the cloud without a code-freeze rewrite, and a need to prove feasibility with evidence before committing budget.

Legacy Wrapper is also useful purely as an assessment tool. The analyze command is read-only, requires no legacy build tools, needs no compiled binaries, and produces a self-contained HTML report you can circulate.

When not to use it

Situation Why
The application is ASP.NET Web Forms (.aspx, .ascx, .master) Web Forms has no .NET Core equivalent. The tool detects it and reports WEB-FORMS-001 as Blocking. It does not wrap it. Non-UI business logic in the same solution can still be wrapped.
The application exposes ASMX web services (.asmx) ASMX cannot be hosted on .NET Core. Reported as ASMX-001, Blocking. Each operation needs converting to an ASP.NET Core controller action or a CoreWCF service.
Any project targets below .NET Framework 4.6.1 The netstandard2.0 packaging premise is invalid below 4.6.1. Hard-blocked — see the next section.
Core messaging runs on MSMQ (netMsmqBinding) or duplex HTTP (wsDualHttpBinding) or WS-Federation No CoreWCF equivalent. Reported WCF-B01, WCF-B02, WCF-B03 — all Blocking.
You want an actual port to modern .NET This tool deliberately does not migrate your code. If your goal is genuine modernization of the source, use an upgrade-oriented tool instead.
The value is almost entirely in HTTP endpoints with thin or no service layer You will get analysis plus routing scaffolding, but the wire-up work per endpoint may approach the cost of rewriting the controller.
The solution contains VB.NET or F# projects whose logic matters Source analysis is C# only. .vbproj and .fsproj projects are still packaged and still binary-analyzed with Mono.Cecil, but the eight source analyzers, endpoint discovery, WCF discovery, and IoC discovery never run on them — and no diagnostic tells you so. A VB.NET project can report zero findings simply because nothing looked at it. Treat any VB/F# project's score as uninformative and review it by hand.

Requirements and prerequisites

The .NET Framework 4.6.1 floor — read this first

Every project in your solution must target .NET Framework 4.6.1 or later. 4.6.1 is the first version that implements .NET Standard 2.0; binaries built against anything older cannot be loaded through the compatibility shim, so packaging them is pointless. This is enforced, not merely advised.

Target framework Result
Anything below 4.6.1 — v2.0, v3.5, v4.0, v4.5, v4.5.2, v4.6, and the equivalent net20 … net46 monikers Blocked. TFM-001 Blocking diagnostic; package and wrap exit with code 4.
v4.6.1–v4.8.1, net461–net481 Supported. 4.6.1 through 4.7.1 work but implement parts of .NET Standard 2.0 through facade assemblies; 4.7.2 or 4.8 is recommended.
netstandard*, netcoreapp*, net5.0 and later Already modern. No diagnostic.
Unparseable or missing Treated as unknown. Not blocking, but support is not claimed.

The fix is a retarget, not a migration: change <TargetFrameworkVersion> to v4.7.2 (or v4.8), or <TargetFramework> to net472/net48 for SDK-style projects, rebuild the legacy solution, and rerun the tool. No source changes are implied by TFM-001 itself. This converts a hard "no" into roughly a day of work, which is why it is worth checking before anything else.

Run legacy-wrapper analyze first — it reports TFM-001 per offending project and still writes a full report, so you learn about every below-floor project in one pass.

To run the tool

  • .NET 10 SDK — Legacy Wrapper is a .NET 10 global tool. The runtime alone is not enough: Roslyn source analysis loads your projects through MSBuild, which requires the SDK. With only the runtime installed, every project degrades to a SRC-* warning and you get an "analysis incomplete" report with no real findings. Download: https://dotnet.microsoft.com/download/dotnet/10.0
  • Your legacy solution should be NuGet-restored before you analyze it (nuget restore Legacy.sln). Without restored packages, Roslyn frequently loads projects with zero documents, which produces SRC-LOAD warnings and an unreliable score.
  • Windows, macOS, or Linux. Analysis works identically on all three.

To build your legacy solution

This is the part that is genuinely platform-dependent, and the honest answer is not "it works everywhere".

Command Needs a legacy build tool? Needs compiled legacy binaries? Needs network?
doctor No — that is what it checks No No
analyze No (the .NET 10 SDK is still required — see above) Optional. With binaries present, additional binary-level analysis runs (BIN-REF-*, PS-BIN, PS-PINVOKE) Optional. nuget.org is queried for third-party package compatibility; on failure those packages are scored Unknown rather than failing the run
package Yes, unless --skip-build Yes, or projects are skipped Only if nuget.exe is absent and the dotnet pack fallback must restore
wrap Yes, unless --skip-build Yes As above
generate No No — but packages must already exist at --packages No

Classic non-SDK .NET Framework .csproj files need a real MSBuild. The tool probes for MSBuild first (via vswhere and PATH on Windows, and msbuild on PATH elsewhere), then falls back to dotnet build, which works for SDK-style projects and is limited for legacy non-SDK ones. Mono's xbuild is deliberately not accepted — it was retired in 2019 and cannot build modern .NET Framework solutions.

Practical guidance:

  • On Windows, install Visual Studio 2022 Build Tools with the managed-desktop workload — the bare install ships no workloads and leaves MSBuild missing:

    winget install Microsoft.VisualStudio.2022.BuildTools --override "--quiet --add Microsoft.VisualStudio.Workload.ManagedDesktopBuildTools --add Microsoft.VisualStudio.Workload.NetCoreBuildTools --add Microsoft.Net.Component.4.8.SDK --add Microsoft.Net.Component.4.7.2.TargetingPack"
    

    Add the targeting pack matching your solution's framework version. Confirm with legacy-wrapper doctor, which must show an MSBuild row before you rely on it. This is the smoothest path.

  • On macOS or Linux, the recommended workflow is to build the legacy solution on Windows or in CI, copy the bin output, and run package or wrap with --skip-build. Alternatively install Mono's msbuild (brew install mono / sudo apt install mono-devel) and expect mixed results with older project types.

  • legacy-wrapper analyze needs no legacy build tool at all and is fully usable cross-platform.

nuget.exe is optional. When it is missing, the tool generates a minimal SDK-style project referencing the .nuspec and packs with dotnet pack instead. doctor shows this as a warning row, never a failure.

What leaves your machine

Analysis, packaging, and generation run entirely locally. Your source, binaries, and configuration are never uploaded. Two exceptions, both visible and both optional:

  • nuget.org queries. analyze, generate, and wrap send the package IDs and versions referenced by your solution to https://api.nuget.org/v3-flatcontainer/ to check modern compatibility. No source, no assembly, no configuration. With no network access these packages are scored Unknown and the run continues.
  • --ai-provider. Only when you explicitly pass it. Code context for the affected types is sent to Anthropic or OpenAI. Off by default — see the AI section below.

The tool writes nothing outside your --output/--report paths and a temporary directory it deletes afterwards. It never modifies your legacy solution.


Install

dotnet tool install -g Mihadeth.LegacyWrapper --prerelease

The --prerelease flag is required while the tool is in preview — without it, dotnet tool install finds no matching version.

The installed command is legacy-wrapper, not the package name:

legacy-wrapper --version
legacy-wrapper --help

To update or remove:

dotnet tool update -g Mihadeth.LegacyWrapper --prerelease
dotnet tool uninstall -g Mihadeth.LegacyWrapper

If legacy-wrapper is not found after installing, ~/.dotnet/tools (macOS/Linux) or %USERPROFILE%\.dotnet\tools (Windows) is not on your PATH.

Run doctor first

legacy-wrapper doctor

Example output:

Legacy Wrapper — Prerequisites Check
═══════════════════════════════════════
  ✅ .NET SDK         10.0.108        (dotnet)
  ❌ MSBuild          not found
  ✅ nuget.exe        found              (/opt/homebrew/bin/nuget)

All required tools are available.

MSBuild was not found — .NET Framework (non-SDK) solutions may not build:
  → Building .NET Framework projects requires Windows MSBuild or Mono's 'msbuild' command.
  → Install Mono msbuild: brew install mono
  → Or use --skip-build with binaries compiled elsewhere.

⚠️ Warning — a green doctor is not a green build. doctor exits 0 when the .NET SDK alone is present, even with MSBuild missing — the summary line reads "All required tools are available." For classic non-SDK .NET Framework solutions, the MSBuild caveat block underneath is the signal that matters. A green doctor on macOS or Linux does not guarantee your legacy solution will build.

doctor exits 1 when neither the .NET SDK nor MSBuild is found, and prints platform-specific install commands.


Quick start

Step 1 — Analyze (read-only, safe, no legacy build required)

legacy-wrapper analyze --solution ./Legacy.sln --format all --report ./reports/Legacy

Writes reports/Legacy.html, reports/Legacy.json, and reports/Legacy.md, and prints a summary: overall score, per-project breakdown, up to 15 top issues ordered by severity, and discovered entrypoints (Web API/MVC endpoints, HTTP handlers, WCF services, IoC containers).

Read the HTML report before spending time on a build. If Blocking: is non-zero, wrap will very likely stop at exit code 4.

ℹ️ Note. analyze exits 0 regardless of the score — even at 0/100 with blockers present. Do not use its exit code as a go/no-go gate; read the report or the JSON output instead.

Step 2 — See which issues have a bridge available

legacy-wrapper analyze --solution ./Legacy.sln --show-remediable

Adds a === Remediation Summary === block: how many diagnostics have bridges available, the name and target file of each bridge class, any license note attached to one, how many extra NuGet packages the bridges pull in, and how many DI registrations are flagged for review.

The HTML report labels this same count IL-Rewritable; the console labels it Bridges available. They are the same set.

Its closing line says "Run with --remediate". That means re-run generate or wrap with --remediate — analyze itself does not accept that flag.

Step 3 — Run the full pipeline

legacy-wrapper wrap --solution ./Legacy.sln --output ./GeneratedWrapper

Three labelled steps (Analyze, Package, Generate) followed by a summary. Produces ./GeneratedWrapper/ containing WrapperHost.sln, src/WrapperHost/, local-packages/, nuget.config, Dockerfile, docker-compose.yml, and COMPATIBILITY-REPORT.html.

If it exits 4, unremediable blocking issues were found. The list of [ID] Title items goes to standard error, only COMPATIBILITY-REPORT.html was written, and no wrapper was generated. That report is your work list.

Step 4 — Build and run the wrapper

cd GeneratedWrapper
dotnet build WrapperHost.sln
docker compose up --build

The generated Dockerfile is a multi-stage build on the .NET 10 SDK and ASP.NET runtime Linux images and exposes port 8080; docker-compose.yml maps 8080:8080.

The generated solution is designed to build without edits, and the generator is tested against compiled output. Real legacy solutions vary: if dotnet restore fails with NU1101 or NU1202, a legacy project was skipped during packaging or one of its dependencies has no .NET Standard-compatible version — check the Packages: N created and Skipped: N project(s) lines from the packaging step first. See the What gets generated section below for which endpoints actually execute before you wire them up.

Common variations

# Binaries already built elsewhere (for example a Windows CI drop).
# Also skips the build-tool pre-flight check entirely.
legacy-wrapper wrap -s ./Legacy.sln -o ./GeneratedWrapper --skip-build

# Use Debug binaries instead of Release
legacy-wrapper wrap -s ./Legacy.sln -o ./GeneratedWrapper -c Debug

# Large solution: bundle everything into one aggregate package above 5 projects
legacy-wrapper wrap -s ./Legacy.sln -o ./GeneratedWrapper -m 5

# Always one package per project, no matter how many
legacy-wrapper wrap -s ./Legacy.sln -o ./GeneratedWrapper -m 0

# Rewrite IL call sites to route through the generated bridges
legacy-wrapper wrap -s ./Legacy.sln -o ./GeneratedWrapper --rewrite-il

# Generate DI adapter classes (TODO stubs unless an AI provider is configured)
legacy-wrapper wrap -s ./Legacy.sln -o ./GeneratedWrapper --di-overrides

Split pipeline

Use the individual commands when you need the stages to run in different places — for example, packaging on a Windows build agent and generating on a developer machine.

legacy-wrapper package  --solution ./Legacy.sln --output ./local-packages
legacy-wrapper generate --solution ./Legacy.sln --packages ./local-packages --output ./Wrapper --remediate

⚠️ Warning — two rules apply to the split pipeline.

  1. Use the same --max-projects value on both commands. They independently decide per-project versus aggregate packaging. If they disagree, the generated .csproj references packages that do not exist in the feed.
  2. If you used package --rewrite-il, generate must include --remediate. IL rewriting retargets call sites into bridge classes in the WrapperHost.Remediation namespace, and those classes only exist when remediation code is generated. Without --remediate the wrapper compiles but throws TypeLoadException or MissingMethodException the first time a rewritten call executes. wrap --rewrite-il is not affected — wrap always generates bridges.

Command reference

Five commands: analyze, package, generate, wrap, doctor. No command has an alias. --solution uses the short alias -s everywhere; all other short aliases are per-command.

Global options

Available on every command.

Option Alias Default Description
--verbose -v off Print full exception details (type, stack trace, inner exceptions) on failure
--log-file <path> — none Write a copy of all output, including full exception details, to the given file

--verbose affects failure output only — it does not make progress output chattier. There is no --quiet; progress output is unconditional.

--log-file mirrors both standard output and standard error to the file, overwriting it each run. Importantly, the full exception detail is written to the log file even without --verbose, so --log-file alone gives you a terse console and a complete diagnostic record. This is the recommended combination when reporting a problem.

legacy-wrapper analyze

Analyze a legacy .NET Framework solution for compatibility. Read-only. Runs no build, needs no legacy build tools, writes nothing except report files.

Option Alias Type Default Required
--solution -s file — Yes
--report -r file path COMPATIBILITY-REPORT in the current directory No
--format -f string html No
--show-remediable — flag False No

--format accepts html, json, md, or all. all expands to exactly html, json, md.

ℹ️ Note — the extension you write in --report is discarded and replaced by the format. The directory and the base filename are taken from --report; the extension always comes from --format. So --report out/foo.html --format json writes out/foo.json, and --report out/x.html --format all writes out/x.html, out/x.json, and out/x.md. With no --report at all and the default --format html, the output is ./COMPATIBILITY-REPORT.html.

⚠️ Warning — an invalid --format value is not an error. It runs the entire analysis, writes nothing, prints Unknown format: {value} to standard error, and still exits 0. Check for the HTML report: … / JSON report: … / Markdown report: … confirmation lines.

Exit codes: 0 on success (including a 0/100 score, and including an unknown format); 1 if the solution file does not exist or analysis throws. analyze never returns 2, 3, or 4.

legacy-wrapper package

Build the legacy solution and package the compiled binaries as NuGet packages.

Option Alias Type Default Required
--solution -s file — Yes
--output -o directory ./local-packages No
--skip-build — flag False No
--configuration -c string Release No
--rewrite-il — flag False No
--max-projects -m integer 10 No

Behavior notes:

  • A build-tool pre-flight check runs first, before any other work, unless --skip-build is passed. If neither the .NET SDK nor MSBuild is found, the full doctor report is written to standard error and the command exits 2 immediately.
  • A .NET Framework 4.6.1 floor gate runs before the build. Any project below the floor aborts the command with exit 4.
  • Per-project package IDs are Legacy.{AssemblyName}, except that an assembly name already beginning with Legacy. is used unchanged — Legacy.Core packs as Legacy.Core, not Legacy.Legacy.Core. The version is always 1.0.0-wrapped.
  • --max-projects switches to a single aggregate package named Legacy.{SolutionName} (same prefix rule) when the project count exceeds the limit. --max-projects 0 disables the limit (always per-project). Negative values are rejected.
  • .nuspec files and IL-rewritten assembly copies are written to a temporary directory that is deleted afterwards. Your original legacy binaries are never modified — IL rewriting always operates on a copy.

⚠️ Warning — package --rewrite-il is not self-contained. IL rewriting retargets call sites into bridge classes that only generate or wrap can produce. If you use it, the matching generate run must pass --remediate, or the wrapper compiles and then throws TypeLoadException / MissingMethodException at the first rewritten call. package does not warn you about this. Prefer wrap --rewrite-il, which always generates the bridges.

⚠️ Warning — missing binaries are skipped, not failed. Projects with no compiled assembly on disk are skipped. The console prints Skipping {project} — no compiled assembly found. and a Skipped: N project(s) with no compiled assembly summary line, and the command still exits 0 — even if every project was skipped and zero packages were produced. Always check the Packages: N created line.

Exit codes: 0 success; 1 solution not found, or any other failure (including a negative --max-projects); 2 no build tool; 3 the legacy build failed; 4 a project targets below .NET Framework 4.6.1.

legacy-wrapper generate

Scaffold the .NET 10 wrapper solution against packages that already exist. Runs analysis internally but performs no build and no pre-flight check.

Option Alias Type Default Required
--solution -s file — Yes
--packages -p directory — Yes
--output -o directory ./GeneratedWrapper No
--remediate — flag False No
--di-overrides — flag False No
--ai-provider — string none No
--ai-api-key — string environment fallback No
--max-projects -m integer 10 No
  • --di-overrides implies --remediate.
  • --max-projects here only decides whether the generated .csproj references one aggregate package or one package per project. It must match the value used at package time. Unlike package and wrap, a negative value is silently treated as per-project mode rather than rejected.

⚠️ Important — generate has no blocker gate. Unlike wrap, it will generate a wrapper for a solution with unremediable blocking issues without complaint. Run analyze first and read the report.

Exit codes: 0 success; 1 solution not found, packages directory not found, or any failure. generate never returns 2, 3, or 4.

legacy-wrapper wrap

The full pipeline: analyze, remediate, blocker gate, package, generate, write the HTML report.

Option Alias Type Default Required
--solution -s file — Yes
--output -o directory ./GeneratedWrapper No
--skip-build — flag False No
--configuration -c string Release No
--remediate — flag False No
--rewrite-il — flag False No
--di-overrides — flag False No
--ai-provider — string none No
--ai-api-key — string environment fallback No
--max-projects -m integer 10 No

wrap packages into {--output}/local-packages itself, so it has no --packages option. It writes only an HTML report — use analyze --format all if you need JSON or Markdown.

ℹ️ Note — --remediate is a no-op on wrap. wrap always runs the remediation analysis (it needs it to classify blockers for the gate) and always passes the resulting plan into generation. wrap and wrap --remediate produce identical output. --rewrite-il and --di-overrides do change behavior.

Pipeline order is Analyze → Remediate → blocker gate → Package → Generate. The gate fires when at least one Blocking diagnostic matched no remediation strategy. In that case the offending items are listed on standard error, COMPATIBILITY-REPORT.html is written to the output directory, no wrapper is generated, and the command exits 4.

Exit codes: 0 success; 1 solution not found or any other failure; 2 no build tool; 3 the legacy build failed; 4 unremediable blocking issues or a project below .NET Framework 4.6.1.

legacy-wrapper doctor

Check build-tool prerequisites and show platform-specific install guidance. No command-specific options.

Exit codes: 0 when the .NET SDK or MSBuild is available; 1 when neither is found, or when probing throws.

Exit code contract

Code Meaning Emitted by
0 Success, or --help, or doctor with a build tool present all
1 Input file or directory not found, or an unhandled failure. Also the parse-error code for a missing required option, and the code doctor uses when no build tool is found all
2 No build tool found — pre-flight or mid-run package, wrap
3 The legacy solution build failed package, wrap
4 The wrapper cannot be produced: unremediable blocking issues (wrap only), or a project below .NET Framework 4.6.1 (package and wrap) package, wrap

Code 4 has two distinct causes. Both mean the same thing operationally: nothing was generated, and the legacy side needs attention first.


Understanding the compatibility report

Score

score = 100 − (blocking × 25) − (critical × 10) − (warning × 2)

Clamped to the range 0–100. Info diagnostics contribute nothing.

Score Category
90–100 Highly Compatible
70–89 Mostly Compatible
40–69 Significant Work Required
0–39 Major Rewrite Required

The score is calculated twice: once per project over that project's diagnostics, and once over the flat union of every project's diagnostics for the overall score. The overall score is therefore not an average of the project scores — a large solution accumulates penalties and floors at 0 quickly. Four blocking issues anywhere in the solution are enough to reach 0/100.

The analysis-incomplete signal

If Roslyn cannot load or compile a project, the tool records a SRC-* diagnostic and marks the whole report incomplete. The console shows:

Analysis incomplete for 2 of 7 project(s) — score is unreliable
See SRC-* diagnostics below for details.

The Markdown report carries the same warning, and the JSON report exposes analysisIncomplete and projectsWithFailedAnalysisCount as top-level fields — usable as a CI gate.

⚠️ Warning — an incomplete analysis barely dents the score. SRC-* diagnostics are Warning severity, so a failed project costs 2 points (or 4, when a load failure and a missing compilation are both recorded for the same project). A solution where source analysis failed everywhere can still score in the 90s. If the incomplete banner is present, do not quote the score. Read the SRC-* entries first and fix the load problem. Binary analysis via Mono.Cecil runs independently and is the fallback signal, but only when compiled binaries exist on disk.

Severity levels

The console report tags each issue with the same markers used below.

Tag Severity Meaning
[BLK] Blocking Cannot work at runtime. Must be addressed before the wrapper will function. Trips the wrap blocker gate when no bridge exists.
[CRT] Critical Will throw at runtime when the code path executes.
[WRN] Warning May work, but needs testing or has behavioral differences.
[INF] Info Informational. Does not affect the score.

Diagnostic reference — Blocking

ID What triggered it What to do
TFM-001 A project targets .NET Framework below 4.6.1 Retarget to 4.7.2 or 4.8, rebuild, rerun. Project-file change only.
WEB-FORMS-001 .aspx/.ascx/.master files, or classes deriving from Page, UserControl, or MasterPage No .NET Core equivalent. Rewrite as Razor Pages or Blazor. Non-UI logic in the same solution can still be wrapped.
ASMX-001 .asmx files, or [WebService] / [WebMethod] / WebService base class Convert each operation to an ASP.NET Core controller action (REST consumers) or a CoreWCF service with BasicHttpBinding (SOAP consumers).
SW-001 … SW-008 System.Web API surface: HttpContext.Current, HttpApplication, HttpRequest, HttpResponse, HttpServerUtility, HttpSessionState, HttpCachePolicy, FormsAuthentication Migrate to the ASP.NET Core equivalent: IHttpContextAccessor, middleware or IHostedService, ASP.NET Core HttpRequest/HttpResponse, IWebHostEnvironment for path mapping plus WebUtility/HtmlEncoder for encoding, ISession, response caching or IDistributedCache, and cookie authentication middleware respectively.
SW-GEN A System.Web member with no entry in the knowledge base Unmapped System.Web dependency. Review and migrate manually.
CFG-002 Member access on a type in System.Web.Configuration Not available in ASP.NET Core. Use IConfiguration.
RT-003 System.Runtime.Remoting usage Removed from .NET Core. Use gRPC, an ASP.NET Core Web API, or named pipes.
PS-004 System.EnterpriseServices.ServicedComponent (COM+) Replace with standard dependency injection and System.Transactions transaction management.
WCF-B01 netMsmqBinding in system.serviceModel config No CoreWCF equivalent. Replace with a message broker (Azure Service Bus, RabbitMQ) or a background job queue.
WCF-B02 wsDualHttpBinding in config Duplex over HTTP is unsupported. Use SignalR or gRPC bidirectional streaming.
WCF-B03 Binding security mode containing "Federation" or "Federated" WS-Federation is not in CoreWCF. Migrate to OpenID Connect or OAuth 2.0.
PKG-INCOMPAT A referenced NuGet package whose latest version on nuget.org only declares .NET Framework dependency groups Replace with the recommended package when the report names one; otherwise find an alternative or reimplement. Requires network access — without it the package is scored Unknown rather than Incompatible.
BIN-REF-001 … BIN-REF-008 An assembly-level reference found in the compiled binary: System.Web, System.Web.Services, System.Web.Mvc, Microsoft.Web.Infrastructure, System.Messaging, System.Runtime.Remoting, System.EnterpriseServices, System.Workflow.* These are the safety net for code whose source was never analyzed — vendored DLLs, IL-weaved assemblies, VB.NET and F# projects, projects whose Roslyn load failed. Each carries its own recommendation. Note that a BIN-REF entry is suppressed when source analysis already produced a diagnostic in the same category for that project, so the absence of BIN-REF-001 does not mean System.Web is absent — check the SW-* findings.

Diagnostic reference — Critical

ID What triggered it What to do
SER-001 BinaryFormatter construction, Serialize, or Deserialize Bridge available. --remediate generates SerializationBridge over System.Text.Json with no extra package; --rewrite-il redirects the call sites. Review carefully — JSON differs from BinaryFormatter for private fields, cyclic references, and polymorphic types.
SER-002 SoapFormatter Same bridge as SER-001.
SER-003 NetDataContractSerializer Same bridge, or move to DataContractSerializer.
SER-GEN A serializer type with no knowledge-base entry Same treatment; also covered by the bridge.
RT-001 AppDomain.CreateDomain --remediate generates AssemblyIsolationBridge over AssemblyLoadContext, but it is classified bridge-only — deliberately not IL-rewritten, so you wire it up by hand. Separate config files and security boundaries are not replicated.
RT-002 Thread.Abort No bridge exists, deliberately. There is no safe automatic substitute. Rewrite to cooperative CancellationToken cancellation before wrapping.
HTTP-MOD-001 One per module: classes implementing IHttpModule, and <httpModules> / <modules> entries in web.config ASP.NET Core has no IHttpModule pipeline, so the module's per-request logic (authentication, logging, URL rewriting) is silently dropped. Port each to middleware and register it in the wrapper's pipeline.
GLOBAL-ASAX-001 An HttpApplication subclass with request-lifecycle handlers such as Application_BeginRequest, Application_Error, or Session_Start These are never invoked by ASP.NET Core, so the loss is silent. Port each to middleware.

Diagnostic reference — Warning

ID What triggered it What to do
SRC-ERR Roslyn source analysis threw for a project Treat that project's results as unreliable. See the analysis-incomplete section.
SRC-NOCOMPILE Roslyn returned no compilation for a project Same. The source analyzers never ran for that project.
SRC-LOAD The workspace reported load failures, or loaded a project with zero documents Most common on old non-SDK .csproj files and on macOS/Linux. Restore the solution first, and prefer running analysis on Windows with matching build tooling.
CFG-001 ConfigurationManager or WebConfigurationManager member access Already handled — the generated LegacyConfigBridge bridges ConfigurationManager.AppSettings from appsettings.json at startup. Migrating to IConfiguration is the real fix.
PS-001 Microsoft.Win32.Registry Bridge available: RegistryBridge over IConfiguration, and IL-rewritable. Without it, registry calls throw PlatformNotSupportedException on Linux and macOS containers.
PS-002 System.Management (WMI) Windows-only. Use a cross-platform alternative or platform-conditional code.
PS-003 [DllImport] in source, or P/Invoke methods found in the compiled IL Ensure the native library ships in the container image, or replace with a cross-platform API.
PS-005 System.Drawing types Bridge available: ImageProcessingBridge over SixLabors.ImageSharp, IL-rewritable. Carries a license obligation — see the remediation section. Covers load, save, and resize; advanced GDI+ work (gradients, complex paths) needs manual review.
PS-GEN A platform-specific namespace member with no knowledge-base entry Review platform compatibility for your target environment.
PS-BIN The compiled binary references System.Management or System.ServiceProcess Windows-only dependency. Provide a cross-platform alternative, or switch the generated Dockerfile to Windows container images (see What gets generated).
PS-PINVOKE Aggregate count of P/Invoke methods in an assembly, one entry per assembly Companion to PS-003. Use it to size the native-dependency work.
RT-004 MarshalByRefObject The type exists on .NET 10, but remoting and AppDomain scenarios do not work. Review usage.
SW-009, SW-010 System.Web.Mvc.Controller, System.Web.Http.ApiController The wrapper generates proxy controllers. Review action return types, content negotiation, and model binding.
BIN-REF-009 The compiled binary references System.Transactions System.Transactions.Local exists, but escalation to distributed or MSDTC transactions throws PlatformNotSupportedException off Windows. Verify no path escalates; restructure to single-connection transactions or an outbox pattern.
WCF-W01 webHttpBinding in config Not a blocker. REST-style WCF should become ASP.NET Core controllers, and the tool generates them from [WebGet] and [WebInvoke] attributes.
GLOBAL-ASAX-002 An HttpApplication subclass whose only convention methods are startup or shutdown (Application_Start, Application_End, Application_Init, Application_Disposed) Move that initialization into the wrapper's Program.cs before the host starts, or into an IHostedService.

Diagnostic reference — Info

ID What triggered it Note
ROUTE-001 Programmatic RouteTable.Routes registrations in a Global.asax class Detected but not translated into wrapper endpoints. Recreate them with MapControllerRoute in Program.cs. Info severity means it does not reduce the score — do not skim past it.
WCF-I01, WCF-I02, … One per distinct binding type that migrates directly to CoreWCF: BasicHttpBinding, NetTcpBinding, WSHttpBinding, NetNamedPipeBinding Confirmation, not a problem. The number after WCF-I is positional and depends on how many supported bindings your solution has — it is not a stable identifier.
BIN-ERR A compiled assembly could not be read by the binary analyzer Binary analysis was skipped for that file. Worth investigating despite the Info severity — a native or mixed-mode DLL will not load on .NET 10 either.

What gets generated

Wired up versus scaffolding

Read this before you evaluate the output.

Generated artifact State
WrapperHost.csproj Working. Targets net10.0 and references your legacy packages at version 1.0.0-wrapped, plus compatible modern third-party packages and CoreWCF where needed.
nuget.config pointing at the local feed plus nuget.org Working.
LegacyConfigBridge.cs and appsettings.json Working, with placeholders. Non-secret <appSettings> values are migrated and bridged back to ConfigurationManager.AppSettings at startup. Connection strings and likely-secret keys are placeholdered — see below.
CoreWCF hosting (CoreWcfExtensions.cs) Working. Registers and exposes your actual legacy service types and contracts. This is the one path where your legacy code genuinely executes without a manual step.
DI wiring (ServiceCollectionExtensions.cs) Working, derived from your detected IoC container registrations. Review it.
Remediation bridges (Remediation/*.cs) Working, but review them — a bridge is a behavioral substitution, not an identity.
Program.cs, Dockerfile, docker-compose.yml Working. Multi-stage build on mcr.microsoft.com/dotnet/sdk:10.0 and mcr.microsoft.com/dotnet/aspnet:10.0 — Linux images, exposing port 8080. If your report contains PS-002 (WMI), PS-003 (P/Invoke to Windows DLLs), PS-BIN, or unremediated PS-001/PS-005, switch both FROM lines to the -windowsservercore-ltsc2022 tags and run on a Windows container host.
Web API / MVC proxy controllers (Controllers/*.cs) Scaffolding. Route templates and HTTP-method attributes are correct. Parameter signatures are stubbed — route tokens are typed string, request bodies are a single [FromBody] object?, and query-string parameters are omitted entirely. Each action body throws NotImplementedException.
HTTP handler proxies (Handlers/*.cs) Scaffolding. Same pattern, plus generated warning comments where the handler used session or authentication.
REST-style WCF controllers (from webHttpBinding) Scaffolding. Same pattern, and no body parameter is generated at all — add one for any operation that took a request payload.
DI override adapter classes (--di-overrides) Stubs unless an AI provider is configured — see the remediation section.

⚠️ Important — endpoint scaffolding, not endpoint migration. Expect roughly one wire-up edit per HTTP endpoint, including retyping the generated parameters to match the legacy signature. This is manual for a structural reason: legacy ApiController and IHttpHandler types derive from System.Web types that cannot be loaded on .NET 10, so the generated proxy cannot instantiate them. You point each action at the underlying business-logic classes instead, resolving them through the generated DI wiring.

One subtlety worth knowing: in the Web API and MVC proxy controllers, actions whose legacy counterpart returned void are generated as return Ok(); rather than a throw. Those endpoints silently do nothing instead of failing loudly. When planning the work, search the generated Controllers/ and Handlers/ folders for NotImplementedException and for return Ok(); — the second group is easy to miss because it never errors.

Configuration and secrets

Every connection string value is replaced with REPLACE_WITH_ACTUAL_CONNECTION_STRING_FOR_{NAME}. appSettings keys matching any of thirteen secret patterns — including password, pwd, secret, apikey, token, credential, clientsecret, accesskey, and privatekey — are replaced with REPLACE_WITH_ACTUAL_VALUE_FOR_{KEY}.

This is deliberate: the tool refuses to copy credentials into a generated file. But it means the wrapper will not connect to anything until you supply real values through environment variables, user secrets, or your deployment platform's secret store.

⚠️ Warning — the scrubbed keys are not listed anywhere. The tool computes them internally but does not print them. After generating, open <your --output directory>/src/WrapperHost/appsettings.json and search for REPLACE_WITH_ACTUAL by hand. Nothing else will tell you.

A second gotcha: ConfigurationManager.ConnectionStrings is read-only at runtime, so it cannot be bridged the way AppSettings is. Legacy code that reads connection strings needs updating to use IConfiguration.GetConnectionString(). The generated bridge documents this in a comment at the relevant spot.

Layout

GeneratedWrapper/
├── WrapperHost.sln
├── nuget.config                          # LocalLegacyPackages → ./local-packages, plus nuget.org
├── Dockerfile
├── docker-compose.yml
├── COMPATIBILITY-REPORT.html
├── local-packages/
│   └── Legacy.*.1.0.0-wrapped.nupkg      # copied from the packaging feed
└── src/
    └── WrapperHost/
        ├── WrapperHost.csproj            # net10.0
        ├── Program.cs
        ├── appsettings.json
        ├── Configuration/
        │   └── LegacyConfigBridge.cs
        ├── Controllers/                  # one file per Web API endpoint and per REST-style WCF service
        ├── Handlers/                     # one file per discovered IHttpHandler
        ├── Infrastructure/
        │   ├── LegacyHttpContextAdapter.cs      # only when handlers were discovered
        │   ├── CoreWcfExtensions.cs             # only when WCF services were discovered
        │   └── ServiceCollectionExtensions.cs   # only when IoC containers were detected
        └── Remediation/                  # only when bridge classes or DI adapters were generated

The wrapper project name is fixed at WrapperHost. The four subdirectories Controllers/, Handlers/, Infrastructure/, and Configuration/ are always created, even when empty. Remediation/ is not — it appears only when there was actually something to bridge: on any wrap run (which always remediates), or on a generate run with --remediate or --di-overrides.


Remediation and IL rewriting

--remediate

Available on generate and wrap. Generates bridge classes under <your --output directory>/src/WrapperHost/Remediation/ that provide modern implementations of APIs your legacy code depends on:

Diagnostic Bridge class Replacement Extra package
SER-001, SER-002, SER-003 SerializationBridge System.Text.Json None (in-box)
PS-005 ImageProcessingBridge SixLabors.ImageSharp 3.1.12 Yes — see the license note below
PS-001 RegistryBridge IConfiguration, reading the LegacyRegistry section of appsettings.json None
RT-001 AssemblyIsolationBridge AssemblyLoadContext None
CFG-001 LegacyConfigBridge Already generated as part of the standard config bridge; no additional class None

Generating a bridge does not change your legacy code. It makes a modern implementation available; either you call it, or --rewrite-il points the existing call sites at it.

Remember that on wrap the remediation analysis always runs, so --remediate there is a no-op. On generate the flag is real.

⚠️ Warning — SixLabors.ImageSharp license obligation. When PS-005 is remediated, the generated wrapper takes a dependency on SixLabors.ImageSharp 3.1.12, which is distributed under the Six Labors Split License — free for open-source projects and small organizations, but commercial use above the revenue threshold requires a paid license. See https://sixlabors.com/pricing/ for current terms. The tool surfaces this note on the console and in the report; treat it as a purchasing consideration, not a footnote.

If you cannot accept those terms, omit --remediate (the PS-005 bridge is only generated when you ask for remediation), or delete src/WrapperHost/Remediation/ImageProcessingBridge.cs and the SixLabors.ImageSharp <PackageReference> from the generated WrapperHost.csproj and handle System.Drawing usage yourself. Do not combine that removal with --rewrite-il, which retargets call sites into the bridge you just deleted.

--rewrite-il

Available on package and wrap. Uses Mono.Cecil to rewrite call sites in your compiled assemblies so that blocked API calls target the generated bridge methods instead.

Nine rules exist, covering exactly five diagnostics: PS-001 (registry reads), SER-001, SER-002, SER-003 (serializer Serialize and Deserialize), and PS-005 (Image.FromFile and Image.FromStream). Nothing else is IL-rewritable. RT-001 is deliberately excluded — replacing AppDomain.CreateDomain automatically is too dangerous.

Safety properties worth knowing:

  • Your original binaries are never modified. The rewriter always works on a copy in a temporary directory, and the rewritten copy is what gets packaged.
  • Rewriting fails soft. If an assembly cannot be rewritten, the tool prints a warning and continues with the original assembly. A successful overall run therefore does not guarantee that every assembly was rewritten — read the Rewrote N IL call(s) in {project} lines.
  • On wrap, when the remediation analysis found DI-level substitutions, the diagnostics those cover are excluded from IL rewriting so the two mechanisms do not both fire. The tool prints Skipping IL rewrite for N diagnostic(s) covered by DI substitution.
  • wrap --rewrite-il is safe, because wrap always generates the bridge classes the rewritten calls target. package --rewrite-il is not self-contained — the matching generate run must pass --remediate, as described in the split-pipeline warning above.

--di-overrides

Available on generate and wrap, implies --remediate. When your legacy code registers an affected type in an IoC container, this generates partial adapter classes that substitute the bridge implementation at the DI level rather than at the call site. Without it, affected registrations get advisory comments only.

The generated adapters are TODO stubs that you fill in — unless you also configure an AI provider.

--ai-provider and --ai-api-key

Optional. Sends code context to an external LLM (Anthropic or OpenAI) to fill in the DI adapter bodies instead of leaving stubs.

  • --ai-provider accepts anthropic or claude for Anthropic, and openai or gpt for OpenAI. Matching is case-insensitive; any other value is treated as unconfigured.
  • --ai-api-key overrides the ANTHROPIC_API_KEY / OPENAI_API_KEY environment variables. If no explicit key is given, ANTHROPIC_API_KEY is tried first (defaulting the provider to anthropic), then OPENAI_API_KEY.

⚠️ Warning — three things to know before enabling this.

  1. It sends your code context to a third-party API. Check that against your organization's policy first.
  2. The output is not compile-validated. Review every generated adapter.
  3. Both flags are silently ignored without --di-overrides, and an unrecognized provider name or an unresolvable API key silently downgrades to TODO stubs with no error message. The only positive confirmation is the console line AI adapter generation enabled ({provider}). — if you do not see it, AI generation did not run.

Troubleshooting

Exit 2 — "No build tool found"

Cause. Neither the .NET SDK nor MSBuild was found. The pre-flight check runs before any work in package and wrap, so nothing was wasted.

Fix. Run legacy-wrapper doctor and follow the printed guidance. On Windows install Visual Studio 2022 Build Tools with the managed-desktop workload (see Requirements); on macOS or Linux install the .NET SDK, and for classic Framework projects install Mono's msbuild — or build the solution elsewhere and pass --skip-build, which bypasses the check entirely.

Exit 3 — "Build failed"

Cause. A build tool was found, but building your legacy solution failed. The full standard output and standard error from the build are included in the message.

Fix. Build the solution in your normal IDE or CI first, confirm it succeeds there, then rerun with --skip-build.

If you are on macOS or Linux with only the .NET SDK, this is the expected outcome for classic non-SDK .csproj files. dotnet build handles SDK-style projects and is limited for legacy ones.

Note that --configuration is passed to the build only — it does not steer where the tool looks for binaries afterwards. See "Packages: 0 created" but exit code 0 below.

Exit 4 — "Unsupported target framework"

Cause. At least one project targets .NET Framework below 4.6.1. Message: Cannot package: N project(s) target a .NET Framework version below the 4.6.1 minimum required for netstandard2.0 packaging: …

Fix. Retarget the named projects to 4.7.2 or 4.8, rebuild, rerun. This is a project-file change, not a code migration.

Exit 4 — unremediable blocking issues (wrap)

Cause. At least one Blocking diagnostic matched no remediation strategy. The tool lists each as [ID] Title followed by its recommendation, on standard error.

Fix. COMPATIBILITY-REPORT.html was written to your output directory even though no wrapper was generated — that report is the work list. Address the listed items in the legacy application (or accept them as scoped manual work) and rerun. Common culprits are WEB-FORMS-001, ASMX-001, TFM-001, SW-001 through SW-008, and WCF-B01 through WCF-B03.

Exit 1 — "Solution file not found" / "Packages directory not found"

Cause. The path passed to --solution or --packages does not exist.

Fix. Check the path. --packages must point at a directory that already contains .nupkg files, which normally means running package first.

"Packages: 0 created" but exit code 0

Cause. Every project was skipped because no compiled assembly was found. Relative to each project's own directory, the tool probes the explicit OutputPath from the .csproj if one is set, then bin/Release, bin/Debug, bin/Release/{tfm}, and bin/Debug/{tfm}. It looks for {AssemblyName}.dll for library projects and {AssemblyName}.exe for everything else.

Fix. Build the legacy solution first, or drop the binaries into one of those locations, then check the Skipped: N project(s) line. A silently empty feed is the failure mode here — the exit code will not tell you.

⚠️ Warning — the probe order is fixed and ignores --configuration. That flag reaches the build invocation only. Debug-only binaries are therefore found under the default --configuration Release, which is convenient — but the reverse is the hazard: a stale bin/Release DLL always wins over a freshly built bin/Debug one, silently packaging old code. If you build Debug, either clean bin/Release or point the project's OutputPath at the output you actually want.

"Omitted dependency … from the nuspec"

Cause. A declared package dependency had no matching assembly in the legacy build output, so it was left out of the generated .nuspec.

Fix. Usually means the legacy solution was not fully restored or built. The message names the dependency and states that the wrapped package may fail at runtime if that dependency is required. Restore and rebuild, then repackage.

"Analysis incomplete for N of M project(s) — score is unreliable"

Cause. Roslyn could not load or compile some projects — SRC-LOAD, SRC-ERR, or SRC-NOCOMPILE. Most common with old non-SDK project files and when running on macOS or Linux. Also check that you have the .NET 10 SDK installed and not just the runtime — MSBuild is unavailable without it, and every project then fails to load.

Fix. Restore the solution (nuget restore or dotnet restore) so package references resolve, then rerun. If it persists, run analyze on Windows with matching build tooling. Binary analysis still runs independently when compiled binaries are present, so packaging decisions are not blind — but do not quote the compatibility score from an incomplete report.

A VB.NET or F# project reports no findings at all

Cause. Expected, and it is not a diagnostic. Source analysis is C# only. .vbproj and .fsproj projects load, get packaged, and get binary-scanned, but no source analyzer, endpoint discovery, WCF discovery, or IoC discovery runs on them, and nothing warns you.

Fix. Rely on the BIN-REF-*, PS-BIN, and PS-PINVOKE findings for those projects, and review their source by hand. Do not read a clean score on a VB/F# project as a compatibility signal.

"Unknown format" and no report file

Cause. --format received a value other than html, json, md, or all. The analysis ran to completion, nothing was written, and the exit code was 0.

Fix. Use one of the four valid values. Confirm success by looking for the HTML report: … line.

The wrapper builds but every endpoint throws NotImplementedException

Cause. Expected. Proxy controllers and handler proxies are scaffolding — see the What gets generated section.

Fix. Wire each action to the corresponding business-logic class, resolved through the generated DI wiring in Infrastructure/ServiceCollectionExtensions.cs, and retype the generated parameters to match the legacy signature. Do not try to instantiate the legacy ApiController or IHttpHandler type itself — it cannot load on .NET 10.

The wrapper starts but cannot reach the database

Cause. Connection strings were placeholdered, not copied.

Fix. Open ./GeneratedWrapper/src/WrapperHost/appsettings.json (or the equivalent path under your own --output directory), replace every REPLACE_WITH_ACTUAL_CONNECTION_STRING_FOR_* and REPLACE_WITH_ACTUAL_VALUE_FOR_* value with a real value (preferably from environment variables or a secret store), and update legacy code that reads ConfigurationManager.ConnectionStrings to use IConfiguration.GetConnectionString() instead.

TypeLoadException or MissingMethodException at runtime after IL rewriting

Cause. You ran package --rewrite-il and then generate without --remediate. The rewritten call sites target bridge classes that were never generated.

Fix. Regenerate with generate --remediate, or use wrap --rewrite-il, which always generates bridges.

Diagnosing anything else

legacy-wrapper wrap -s ./Legacy.sln -o ./GeneratedWrapper --log-file lw.log

The console stays terse, but lw.log receives everything plus the full exception with type, stack trace, and inner exceptions — even without --verbose. Add -v if you also want the stack trace on screen. The log file is overwritten on each run.

Please attach lw.log when reporting a problem.


Limitations

An honest summary of what this tool does not do. None of these are bugs; they are boundaries.

1. HTTP endpoint proxies are scaffolding. Route templates and HTTP-method attributes are generated correctly. Parameter signatures are not: route tokens come out as string, request bodies as [FromBody] object?, and query-string parameters are not generated at all. The call into your business logic is manual, one edit per endpoint, for the structural reason described above. Budget accordingly.

2. CoreWCF hosting is the only path that executes legacy code with no manual step. If your application's value sits in WCF services classified as directly migratable, you get the most out of this tool. If it sits in Web API and MVC controllers, you get analysis plus scaffolding.

3. Connection strings and detected secrets are never copied. The wrapper will not connect to anything until you supply real values, and the list of scrubbed keys is not printed — inspect appsettings.json manually.

4. .NET Framework 4.6.1 is a hard, enforced floor. Below it, packaging is refused with exit code 4.

5. Web Forms and ASMX are detected and blocked, not wrapped. For a 2010-era ASP.NET application this is the most likely reason wrap stops.

6. HTTP modules and Global.asax lifecycle handlers do not run in the wrapper. They are reported as Critical (HTTP-MOD-001, GLOBAL-ASAX-001) precisely because the loss would otherwise be silent. Port each to ASP.NET Core middleware.

7. Programmatic routes are detected but not translated. ROUTE-001 is Info severity; recreate those routes with MapControllerRoute in Program.cs.

8. Thread.Abort has no bridge, by design. There is no safe automatic substitute. Rewrite to cooperative cancellation first.

9. AI adapter generation is opt-in, sends code to a third party, and is not compile-validated. Failures degrade silently to TODO stubs.

10. Analysis is best-effort and says so. If Roslyn cannot load a project you get SRC-* warnings and an incomplete banner. Do not read a high score off an incomplete report.

11. Binary-level dependency checks require compiled binaries and, for third-party package compatibility, network access to nuget.org. Without network access, package compatibility degrades to Unknown rather than failing.

12. The generated Dockerfile targets Linux containers. If your report keeps Windows-only diagnostics (PS-002, PS-003, PS-BIN, unremediated PS-001/PS-005), you must switch it to Windows container images yourself.

13. Source analysis is C# only. VB.NET and F# projects are packaged and binary-scanned, but no source-level analyzer runs on them and no warning is emitted. Their reported score reflects binary analysis alone.

14. This is a preview release — see Status at the top of this page.


Support

Support is direct from the author, on a best-effort basis — there is no service-level commitment attached to this preview release.

For bug reports, feature requests, or help wrapping a specific solution, contact Michael Matskevich at mihadeth@gmail.com.

To make the first reply useful, rerun the failing command with --log-file lw.log and include: the tool version (legacy-wrapper --version), your OS, the exact command you ran, your legacy solution's target framework, and lw.log itself. That log holds the full exception detail — inner exceptions and stack traces — which the console deliberately omits.

The incompatibility knowledge base — the API mappings, third-party package equivalents, and WCF feature matrix that drive the analysis — ships inside the tool itself. If you have a mapping to contribute or a missing incompatibility to report, send it to the address above and it will be considered for the next release.

License

MIT. Copyright (c) 2026 Michael Matskevich.

Note that remediation bridges may introduce third-party dependencies with their own license terms. In particular, --remediate on a PS-005 diagnostic adds SixLabors.ImageSharp under the Six Labors Split License — see the remediation section above.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.0.0-preview.3 83 8/2/2026
1.0.0-preview.2 75 8/2/2026