Majorsilence.Forms.Migrator
26.0.52
dotnet tool install --global Majorsilence.Forms.Migrator --version 26.0.52
dotnet new tool-manifest
dotnet tool install --local Majorsilence.Forms.Migrator --version 26.0.52
#tool dotnet:?package=Majorsilence.Forms.Migrator&version=26.0.52
nuke :add-package Majorsilence.Forms.Migrator --version 26.0.52
majorsilence-migrate
A .NET global tool that converts WinForms source and projects to Majorsilence.Forms.
dotnet tool install -g Majorsilence.Forms.Migrator
majorsilence-migrate <input> [options]
From a clone of the repo, without installing:
dotnet run --project tools/Majorsilence.Forms.Migrator -- <input> [options]
<input> is a .sln, a .csproj/.vbproj, a directory, or a single .cs/.vb/.resx file.
What it does
Project files (.csproj)
- Removes
UseWindowsForms/UseWPF/EnableWindowsTargeting. - Drops the
-windowsTFM suffix, keeping the .NET version (net8.0-windows→net8.0,net10.0-windows10.0.19041.0→net10.0). A multi-target<TargetFrameworks>stays plural with each entry stripped. Pass--tfmto instead force one exact framework. The same-windowsstrip is applied to any custom.props/.targetsthe project pulls in via<Import Project="…" />. - Drops the
Microsoft.WindowsDesktop.Appframework reference. - Removes WinForms-only NuGet packages. Vendor UI suites —
Telerik.UI.for.WinForms*,DevExpress.Win*,Infragistics.Win*,C1.Win*(ComponentOne),Syncfusion.*.WinForms*— are dropped (their<PackageReference>s, and any now-empty<ItemGroup>). The Telerik/DevExpress namespaces map onto theMajorsilence.Forms.*compat layers via the source rewrites / a--mapfile; add more packages to drop with a map file'sremovePackagesglobs (*wildcard, case-insensitive). - Adds
Majorsilence.Forms+ a platform backend reference (package or project) — but only to projects that are actually WinForms: a project is treated as WinForms when it opts in (<UseWindowsForms>, aSystem.Windows.Formsassembly reference / VB project import) or any of its source usesSystem.Windows.Forms. Non-UI projects in a solution (data/service libraries) are left without a UI dependency. - Honours Central Package Management. When a
Directory.Packages.propsgoverns the project (found by walking up from the project) and central management is on, the addedPackageReferences omit theirVersionand the versions are pinned in thatDirectory.Packages.propsvia<PackageVersion>entries instead (existing entries are left untouched). With--outputthe props file is mirrored into the output tree; a repo-wide props file above the migrated tree is flagged for manual editing instead. - Skips legacy non-SDK projects and malformed XML with a warning (never throws).
Source files (.cs / .vb)
System.Windows.Forms→Majorsilence.Forms(using directives and fully-qualified refs).System.Drawingis split three ways:- Primitive value types (
Color,Point,Size,Rectangle,PointF,SizeF,RectangleF) are framework types Majorsilence.Forms keeps — left untouched. - GDI+ types (
Bitmap,Brush,Pen,Font, …) →Majorsilence.Forms.Drawing. - WinForms-compat types Majorsilence relocates (
Graphics,ContentAlignment,SystemColors,SystemBrushes,SystemPens,SystemFonts,ColorTranslator) →Majorsilence.Forms. - A bare
using System.Drawing;is reconciled: it's kept only when a primitive (Color/Point/…) is used unqualified, otherwise removed. When GDI+ types are used unqualified, ausing Majorsilence.Forms.Drawing;is added (replacing theSystem.Drawingimport if it's no longer needed).
- Primitive value types (
System.Drawing.Drawing2D/.Imaging/.Text→Majorsilence.Forms.Drawing.*;.Printing→Majorsilence.Forms.Printing.System.ComponentModel.ComponentResourceManager→Majorsilence.Forms.ComponentResourceManager(the cross-platform resource manager — see Resources below). Only that one type is redirected; the rest ofSystem.ComponentModelis left alone.ApplicationConfiguration.Initialize()is commented out (no equivalent; styles are implicit).- Anything it can't confidently map (e.g.
Graphics,System.Drawing.Design,VisualStyles) is flagged for manual review, not silently mangled.
Solutions (.sln)
When the input is a solution and --output is set, every project it lists is converted and the .sln
itself is copied into the output tree (project paths are preserved), so the result opens directly.
Visual Basic (.vbproj / .vb)
.vbprojprojects are discovered and converted alongside.csproj.- Forces
<MyType>Empty</MyType>— the VB "My" application framework (MyType=Windows/WindowsForms) pulls inMicrosoft.VisualBasic.Devices.Computer,WinForms.FormandApplicationSettingsBase, none of which exist cross-platform. - Excludes the generated
My Project\*.Designer.vbfiles (they don't compile withMyType=Empty). - Rewrites project-level VB global imports (
<Import Include="System.Windows.Forms" />). - Adds the missing form constructor automatically.
MyType=Emptyremoves VB's implicit WinForms constructor, so a form that usesInitializeComponentbut has noPublic Sub New()gets one injected (afterInherits), marked with a' [majorsilence-migrate]comment. The decision is made across the form's partial files (Form1.vb+Form1.Designer.vb): the constructor is injected only when no partial already declares one, and always into the code-behind, never a designer file. It only warns if it can't find where to insert. - Entry-point warning only for executables.
MyType=Emptyremoves the auto entry point, but onlyExe/WinExeprojects need one — class libraries (the SDK default) are not flagged. - Skips the
My Project\*.Designer.vbfiles (Resources/Settings/Application). The project converter excludes them from compilation, so they're neither converted nor flagged. - Remaining warnings a human still owns: real-exe entry points, and
My.*usage — flagged with a member-specific hint (My.Resources→ load from the.resx;My.Settings→ a config/JSON-backed settings class;My.Application/My.Computer→AppContext/System.IO/RuntimeInformation).
Typed DataSets (.xsd + .Designer.vb)
These are pure ADO.NET (System.Data) — no WinForms UI — so they work cross-platform as-is. The migrator
does not modify them. (Their System.ComponentModel.Design.HelpKeywordAttribute is a design-time
attribute that ships in the BCL, so it is not flagged.)
Resources (.resx)
Majorsilence.Forms ships a cross-platform ComponentResourceManager that reads the .resx XML directly
(no GDI+, no BinaryFormatter), so migrated designer code — resources.GetObject(...),
resources.GetString(...), resources.ApplyResources(ctrl, name) — works at runtime on every OS.
Because of that, the migrator only flags what genuinely can't be carried across:
- Plain string tables, primitive designer values (
Point/Size/Color/Boolean/Int32/…), and images stored asbytearray.base64(the modern form — raw image bytes decoded by SkiaSharp) are all consumable. They're not flagged; with--outputthe.resxis copied into the mirrored tree. BinaryFormatter/SOAP blobs are recovered from the NRBF wire format (viaSystem.Formats.Nrbf), without runningBinaryFormatter:System.Drawing.Bitmap/Icon→ the embedded image bytes;System.Windows.Forms.ImageListStreamer→ the comctl32 image-list strip (RLE + BMP + mask) decoded into per-frame images;System.Data.SqlTypesscalars andDBNull(aSqlCommand's design-time parameter defaults). These are consumable, not flagged.
- The genuinely unportable cases are flagged with case-specific guidance: ActiveX/COM control state
(
AxHost/OCX) has no managed equivalent — replace the control; any other serialized object must be re-created in code (BinaryFormatteris gone from modern .NET).
To let
new ComponentResourceManager(typeof(Form))find its.resxat runtime, embed the raw.resx(logical name<FullTypeName>.resx) or keep it beside the assembly. Constructing from an explicit.resxviaComponentResourceManager.FromFile/FromStream/FromXmlalways works.
Options
| Option | Description |
|---|---|
-o, --output <dir> |
Write to a mirrored tree. Omit to convert in place (leaves .bak files). |
-n, --dry-run |
Report changes without writing. |
--no-backup |
In-place mode: don't leave a .bak beside each changed file (e.g. when under git). |
--diff |
Print a unified diff for each changed file. |
--backend <name> |
avalonia (default) | uno | headless. |
--references <mode> |
package (default) | project. |
--tfm <tfm> |
Force a target framework. Default: keep the version, just drop the -windows suffix. |
--package-version <v> |
NuGet version for package references. Defaults to the migrator's own version — the tool and the packages ship from the same release. |
--repo-root <dir> |
Repo root for resolving --references project paths. |
--map <file> |
JSON file of extra namespace mappings (repeatable). |
--strict |
Exit non-zero if any manual-review warning is produced (CI gate). |
--report <file> |
Path for the Markdown report (default migration-report.md by the output). |
--no-report |
Don't write the report. |
Custom mappings
Bring third-party controls across with a JSON map (layered on the built-in rules):
{
"namespaces": {
"DevExpress.XtraEditors": "Majorsilence.Forms.DevExpress"
},
"removePackages": [ "Acme.WinForms.*" ]
}
removePackages is an optional list of extra WinForms-only package-id globs to drop (on top of the
built-in Telerik/DevExpress/Infragistics/ComponentOne/Syncfusion patterns).
Telerik is already built in — all of
Telerik.WinControls,.UI,.UI.Docking,.UI.Data,.Data, and.Enumerationsmap toMajorsilence.Forms.Telerikout of the box (see Source files above), including the guards that leave unsupported namespaces (.Themes,.UI.Export,.Design,.Primitives,.Layouts) and unmapped heavyweight types (RadPdfViewer,RadRichTextEditor, …) unrewritten with a warning instead of pointing them at a namespace that doesn't exist. Remove anyTelerik.*entry from your--mapfile — custom-map rewrites (Pass 1a) have no such guards, so a leftover Telerik entry will blindly clobber the deliberately-unrewritten heavyweight references the built-in rules are protecting.
majorsilence-migrate ./LegacyApp --map telerik.json --strict
Caveats
This is a textual transform, not a Roslyn rewrite — it's fast and tolerant of code that doesn't
currently compile, but type- and member-level API differences between WinForms and Majorsilence.Forms
still need a build pass and manual fixes afterward. Always build the converted project and work through
the migration-report.md before committing.
| Product | Versions 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. |
This package has no dependencies.