Skip to main content

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, an Import of Microsoft.CSharp.targets via $(MSBuildToolsPath), .csproj files over 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.

Source facts

Repository
dotnet/skills
Last source activity
October 5, 2026 at 09:02
Detected SKILL.md language
English
Stars
5,647
Forks
443

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
msbuild-modernization
description
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, an Import of Microsoft.CSharp.targets via $(MSBuildToolsPath), .csproj files over 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.
license
MIT
# MSBuild Modernization: Legacy to SDK-style Migration ## Identifying Legacy vs SDK-style Projects **Legacy indicators:** - `<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />` - Explicit file lists (`<Compile Include="..." />` for every `.cs` file) - `ToolsVersion` attribute on `<Project>` element - `packages.config` file present - `Properties\AssemblyInfo.cs` with assembly-level attributes **SDK-style indicators:** - `<Project Sdk="Microsoft.NET.Sdk">` attribute on root element - Minimal content — a simple project may be 10–15 lines - No explicit file includes (implicit globbing) - `<PackageReference>` items instead of `packages.config` **Quick check:** if a `.csproj` is more than 50 lines for a simple class library or console app, it is likely legacy format. ```xml <!-- Legacy: ~80+ lines for a simple library --> <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <OutputType>Library</OutputType> <RootNamespace>MyLibrary</RootNamespace> <AssemblyName>MyLibrary</AssemblyName> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <Deterministic>true</Deterministic> </PropertyGroup> <!-- ... 60+ more lines ... --> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project> ``` ```xml <!-- SDK-style: ~8 lines for the same library --> <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net472</TargetFramework> </PropertyGroup> </Project> ``` ## Migration Checklist: Legacy → SDK-style ### Step 1: Replace Project Root Element **BEFORE:** ```xml <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <!-- ... project content ... --> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project> ``` **AFTER:** ```xml <Project Sdk="Microsoft.NET.Sdk"> <!-- ... project content ... --> </Project> ``` Remove the XML declaration, `ToolsVersion`, `xmlns`, and both `<Import>` lines. The `Sdk` attribute replaces all of them. ### Step 2: Set TargetFramework **BEFORE:** ```xml <PropertyGroup> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> </PropertyGroup> ``` **AFTER:** ```xml <PropertyGroup> <TargetFramework>net472</TargetFramework> </PropertyGroup> ``` **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` | ### Step 3: Remove Explicit File Includes **BEFORE:** ```xml <ItemGroup> <Compile Include="Controllers\HomeController.cs" /> <Compile Include="Models\User.cs" /> <Compile Include="Models\Order.cs" /> <Compile Include="Services\AuthService.cs" /> <Compile Include="Services\OrderService.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <!-- ... 50+ more lines ... --> </ItemGroup> <ItemGroup> <Content Include="Views\Home\Index.cshtml" /> <Content Include="Views\Shared\_Layout.cshtml" /> <!-- ... more content files ... --> </ItemGroup> ``` **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: ```xml <ItemGroup> <Content Include="..\shared\config.json" Link="config.json" CopyToOutputDirectory="PreserveNewest" /> </ItemGroup> ``` ### Step 4: Remove AssemblyInfo.cs **BEFORE** (`Properties\AssemblyInfo.cs`): ```csharp using System.Reflection; using System.Runtime.InteropServices; [assembly: AssemblyTitle("MyLibrary")] [assembly: AssemblyDescription("A useful library")] [assembly: AssemblyCompany("Contoso")] [assembly: AssemblyProduct("MyLibrary")] [assembly: AssemblyCopyright("Copyright © Contoso 2024")] [assembly: ComVisible(false)] [assembly: Guid("...")] [assembly: AssemblyVersion("1.2.0.0")] [assembly: AssemblyFileVersion("1.2.0.0")] ``` **AFTER** (in `.csproj`): ```xml <PropertyGroup> <AssemblyTitle>MyLibrary</AssemblyTitle> <Description>A useful library</Description> <Company>Contoso</Company> <Product>MyLibrary</Product> <Copyright>Copyright © Contoso 2024</Copyright> <Version>1.2.0</Version> </PropertyGroup> ``` Delete `Properties\AssemblyInfo.cs` — the SDK auto-generates assembly attributes from these properties. **Alternative:** if you prefer to keep `AssemblyInfo.cs`, disable auto-generation: ```xml <PropertyGroup> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> </PropertyGroup> ``` ### Step 5: Migrate packages.config → PackageReference **BEFORE** (`packages.config`): ```xml <?xml version="1.0" encoding="utf-8"?> <packages> <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net472" /> <package id="Serilog" version="3.1.1" targetFramework="net472" /> <package id="Microsoft.Extensions.DependencyInjection" version="8.0.0" targetFramework="net472" /> </packages> ``` **AFTER** (in `.csproj`): ```xml <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Serilog" Version="3.1.1" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> </ItemGroup> ``` Delete `packages.config` after migration. **Migration options:** - **Visual Studio:** right-click `packages.config` → *Migrate packages.config to PackageReference* - **CLI:** `dotnet migrate-packages-config` or manual conversion - **Binding redirects:** SDK-style projects auto-generate binding redirects — remove the `<runtime>` section from `app.config` if present ### Step 6: Remove Unnecessary Boilerplate Delete all of the following — the SDK provides sensible defaults: ```xml <!-- DELETE: SDK imports (replaced by Sdk attribute) --> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" ... /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- DELETE: default Configuration/Platform (SDK provides these) --> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{...}</ProjectGuid> <OutputType>Library</OutputType> <!-- keep only if not Library --> <AppDesignerFolder>Properties</AppDesignerFolder> <FileAlignment>512</FileAlignment> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <Deterministic>true</Deterministic> </PropertyGroup> <!-- DELETE: standard Debug/Release configurations (SDK defaults match) --> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> <!-- DELETE: framework assembly references (implicit in SDK) --> <ItemGroup> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> <Reference Include="System.Xml.Linq" /> <Reference Include="Microsoft.CSharp" /> </ItemGroup> <!-- DELETE: packages.config reference --> <None Include="packages.config" /> <!-- DELETE: designer service entries --> <Service Include="{508349B6-6B84-11D3-8410-00C04F8EF8E0}" /> ``` **Keep** only properties that differ from SDK defaults (e.g., `<OutputType>Exe</OutputType>`, `<RootNamespace>` if it differs from the assembly name, custom `<DefineConstants>`). ### Step 7: Enable Modern Features After migration, consider enabling modern C# features: ```xml <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> ``` - `<Nullable>enable</Nullable>` — enables nullable reference type analysis - `<ImplicitUsings>enable</ImplicitUsings>` — auto-imports common namespaces (.NET 6+) - **Avoid `<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. ## Complete Before/After Example **BEFORE** (legacy — 65 lines): ```xml <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub