npx skills add ...
npx skills add dotnet/skills --skill msbuild-modernization
Guide for modernizing and migrating MSBuild project files to SDK-style format. USE FOR: converting legacy .csproj/.vbproj with verbose XML to SDK-style, migrating packages.config to PackageReference, removing Properties/AssemblyInfo.cs in favor of auto-generation, eliminating explicit <Compile Include> lists via implicit globbing, consolidating shared settings into Directory.Build.props. Indicators of legacy projects: ToolsVersion attribute, <Import Project=\"$(MSBuildToolsPath)\">, .csproj files > 50 lines for simple projects. DO NOT USE FOR: projects already in SDK-style format, non-.NET build systems (npm, Maven, CMake), .NET Framework projects that cannot move to SDK-style.
npx skills add dotnet/skills --skill msbuild-modernization
Legacy indicators:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /><Compile Include="..." /> for every .cs file)ToolsVersion attribute on <Project> elementpackages.config file presentProperties\AssemblyInfo.cs with assembly-level attributesSDK-style indicators:
<Project Sdk="Microsoft.NET.Sdk"> attribute on root element<PackageReference> items instead of packages.configQuick check: if a .csproj is more than 50 lines for a simple class library or console app, it is likely legacy format.
BEFORE:
AFTER:
Remove the XML declaration, ToolsVersion, xmlns, and both <Import> lines. The Sdk attribute replaces all of them.
BEFORE:
AFTER:
TFM mapping table:
Legacy TargetFrameworkVersion | SDK-style TargetFramework |
|---|---|
v4.6.1 | net461 |
v4.7.2 | net472 |
v4.8 | net48 |
| (migrating to .NET 6) | net6.0 |
| (migrating to .NET 8) | net8.0 |
BEFORE:
AFTER:
Delete all of these <Compile> and <Content> item groups entirely. SDK-style projects include them automatically via implicit globbing.
Exception: keep explicit entries only for files that need special metadata or reside outside the project directory:
BEFORE (Properties\AssemblyInfo.cs):
AFTER (in .csproj):
Delete Properties\AssemblyInfo.cs — the SDK auto-generates assembly attributes from these properties.
Alternative: if you prefer to keep AssemblyInfo.cs, disable auto-generation:
BEFORE (packages.config):
AFTER (in .csproj):
Delete packages.config after migration.
Migration options:
packages.config → Migrate packages.config to PackageReferencedotnet migrate-packages-config or manual conversion<runtime> section from app.config if presentDelete all of the following — the SDK provides sensible defaults:
Keep only properties that differ from SDK defaults (e.g., <OutputType>Exe</OutputType>, <RootNamespace> if it differs from the assembly name, custom <DefineConstants>).
After migration, consider enabling modern C# features:
<Nullable>enable</Nullable> — enables nullable reference type analysis<ImplicitUsings>enable</ImplicitUsings> — auto-imports common namespaces (.NET 6+)<LangVersion>latest — the effective language version is determined by the SDK/compiler defaults, not just the TFM, so builds can silently vary across machines with different SDKs installed. Omit <LangVersion> unless you need to pin a specific version. For reproducible builds, pin the SDK version repo-wide with global.json (which indirectly fixes the default language version), or set an explicit numeric <LangVersion> (e.g. <LangVersion>12</LangVersion>) per project to directly control the language version.BEFORE (legacy — 65 lines):
AFTER (SDK-style — 11 lines):
Embedded resources: files not in a standard location may need explicit includes:
Content files with CopyToOutputDirectory: these still need explicit entries:
Multi-targeting: change the element name from singular to plural:
WPF/WinForms projects: use the appropriate SDK or properties:
Test projects: use the standard SDK with test framework packages:
Centralizes NuGet version management across a multi-project solution. See https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management for details.
Step 1: Create Directory.Packages.props at the repository root with <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> and <PackageVersion> items for all packages.
Step 2: Remove Version from each project's PackageReference:
Identify properties repeated across multiple .csproj files and move them to shared files.
Directory.Build.props (for properties — placed at repo or src root):
Directory.Build.targets (for targets/tasks — placed at repo or src root):
Keep in individual .csproj files only what is project-specific:
| Tool | Usage |
|---|---|
dotnet try-convert | Automated legacy-to-SDK conversion. Install: dotnet tool install -g try-convert |
| .NET Upgrade Assistant | Full migration including API changes. Install: dotnet tool install -g upgrade-assistant |
| Visual Studio | Right-click packages.config → Migrate packages.config to PackageReference |
| Manual migration | Often cleanest for simple projects — follow the checklist above |
Recommended approach:
try-convert for a first passDirectory.Build.props