← blog

July 21, 2026 · 6 min read

Migrating a WinForms app with majorsilence-migrate

majorsilence-migrate is the CLI tool that automates moving a WinForms solution onto Majorsilence.Forms. Its central design decision is easy to miss and worth calling out explicitly: it does not parse a syntax tree or resolve symbols. It’s a multi-pass textual/regex rewriter over raw source text — and that’s on purpose.

Why textual, not Roslyn

From the tool’s own source comment: “This is a deliberately textual transform — it does not parse the syntax tree — which keeps it fast and tolerant of files that don’t currently compile.”

That trade-off buys two things a symbol-aware tool can’t offer:

The cost: no true cross-project symbol resolution. The rewriter can’t always tell whether a bare Panel reference means System.Windows.Forms.Panel or your own class of the same name — it relies on namespace-prefix and using/Imports context instead. In practice this is rarely ambiguous (WinForms and Telerik type names are distinctive), and anything it doesn’t recognize gets flagged for manual review rather than silently guessed at.

The optional Roslyn engine

For the genuinely ambiguous case — a custom type sharing a bare name with a WinForms/GDI+ type in the same file — there’s an opt-in second engine: --engine roslyn. It uses MSBuildWorkspace and real symbol resolution instead of regexes, layered on top of the textual engine rather than replacing it; several passes stay textual even in Roslyn mode, because they were never symbol-resolution problems to begin with.

The trade-offs run the other way from the default engine: it needs a solution or project that actually loads via MSBuild (a bare directory or single file falls back to the text engine with a warning), it’s orders of magnitude slower because MSBuild evaluation dominates the run, and if one project fails to load, only that project’s files fall back to the text engine — the run doesn’t abort. If MSBuild itself can’t be located at all, the whole run hard-fails rather than silently downgrading.

When to reach for it: after a first pass with the default --engine text, on a project that already loads cleanly, if you’re seeing a specific, confirmed name-collision case in the diff. For the initial pass over a large, possibly half-broken legacy codebase, the default textual engine is still the right tool.

What’s next

See MIGRATION.md in the repository for the full pass-by-pass breakdown, and COMPATIBILITY_MATRIX.md for what’s actually implemented once your migrated code compiles.