Msbuild Modernization
.NET (Microsoft)OfficialPublished by .NET (Microsoft)
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to modernizing legacy .NET project files (.csproj/.vbproj). It outlines the transition from verbose, manually managed XML configurations to the concise, convention-based SDK-style format. The guide covers essential migration steps, including replacing project root elements, adopting implicit file globbing, migrating package management, and consolidating shared configurations using Directory.Build.props.
What it does
- Identifies legacy project indicators like ToolsVersion and explicit file includes.
- Provides a step-by-step migration path to SDK-style project structures.
- Explains how to replace packages.config with PackageReference.
- Demonstrates how to remove boilerplate and leverage auto-generated assembly attributes.
- Shows how to consolidate shared settings using Directory.Build.props.
- Lists recommended tools for automating the migration process.
When to use it
- When you need to modernize legacy .NET Framework projects.
- When project files are excessively verbose and difficult to maintain.
- When you want to adopt modern .NET features like implicit usings and nullable reference types.
- When you need to simplify dependency management across a solution.
Advantages
- Significantly reduces project file size and complexity.
- Enables modern .NET development features and improved build performance.
- Simplifies dependency management through PackageReference.
- Improves maintainability by using implicit file inclusion.
- Facilitates easier cross-project configuration via shared property files.
Usage examples
- “How do I convert my legacy .csproj file to the new SDK-style format?”
- The agent will provide the step-by-step migration checklist, including replacing the project root element, setting the TargetFramework, and removing unnecessary boilerplate.
- “What is the best way to manage NuGet packages in a large solution?”
- The agent will explain how to migrate from packages.config to PackageReference and how to implement Central Package Management using Directory.Packages.props.
- “How can I share common build properties across multiple projects?”
- The agent will guide you through creating a Directory.Build.props file at your repository root to centralize settings like TargetFramework and Company information.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/dotnet/msbuild-modernization/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip msbuild-modernization.zip -d .agents/skills
Documentation by .NET (Microsoft)
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.csfile)ToolsVersionattribute on<Project>elementpackages.configfile presentProperties\AssemblyInfo.cswith assembly-level attributes
SDK-style indicators:
<Project Sdk="Microsoft.NET.Sdk">attribute on root elementMinimal content — a simple project may be 10–15 lines
No explicit file includes (implicit globbing)
<PackageReference>items instead ofpackages.config
Quick check: if a .csproj is more than 50 lines for a simple class library or console app, it is likely legacy format.
<!-- 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><!-- 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 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:
<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:
<PropertyGroup>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>AFTER:
<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:
<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:
<ItemGroup>
<Content Include="..\shared\config.json" Link="config.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>Step 4: Remove AssemblyInfo.cs
BEFORE (Properties\AssemblyInfo.cs):
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):
<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:
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>Step 5: Migrate packages.config → PackageReference
BEFORE (packages.config):
<?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):
<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 PackageReferenceCLI:
dotnet migrate-packages-configor manual conversionBinding redirects: SDK-style projects auto-generate binding redirects — remove the
<runtime>section fromapp.configif present
Step 6: Remove Unnecessary Boilerplate
Delete all of the following — the SDK provides sensible defaults:
<!-- 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:
<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 withglobal.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 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')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{12345678-1234-1234-1234-123456789ABC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MyLibrary</RootNamespace>
<AssemblyName>MyLibrary</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<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>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Models\User.cs" />
<Compile Include="Models\Order.cs" />
<Compile Include="Services\UserService.cs" />
<Compile Include="Services\OrderService.cs" />
<Compile Include="Helpers\StringExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>AFTER (SDK-style — 11 lines):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup>
</Project>Common Migration Issues
Embedded resources: files not in a standard location may need explicit includes:
<ItemGroup>
<EmbeddedResource Include="..\shared\Schemas\*.xsd" LinkBase="Schemas" />
</ItemGroup>Content files with CopyToOutputDirectory: these still need explicit entries:
<ItemGroup>
<Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Include="scripts\*.sql" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>Multi-targeting: change the element name from singular to plural:
<!-- Single target -->
<TargetFramework>net8.0</TargetFramework>
<!-- Multiple targets -->
<TargetFrameworks>net472;net8.0</TargetFrameworks>WPF/WinForms projects: use the appropriate SDK or properties:
<!-- Option A: WindowsDesktop SDK -->
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<!-- Option B: properties in standard SDK (preferred for .NET 5+) -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UseWPF>true</UseWPF>
<!-- or -->
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
</Project>Test projects: use the standard SDK with test framework packages:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" />
</ItemGroup>
</Project>Central Package Management Migration
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:
<!-- BEFORE -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!-- AFTER -->
<PackageReference Include="Newtonsoft.Json" />Directory.Build Consolidation
Identify properties repeated across multiple .csproj files and move them to shared files.
Directory.Build.props (for properties — placed at repo or src root):
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Company>Contoso</Company>
<Copyright>Copyright © Contoso 2024</Copyright>
</PropertyGroup>
</Project>Directory.Build.targets (for targets/tasks — placed at repo or src root):
<Project>
<Target Name="PrintBuildInfo" AfterTargets="Build">
<Message Importance="High" Text="Built $(AssemblyName) → $(TargetPath)" />
</Target>
</Project>Keep in individual .csproj files only what is project-specific:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>MyApp</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" />
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
</ItemGroup>
</Project>Tools and Automation
| 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:
Run
try-convertfor a first passReview and clean up the output manually
Build and fix any issues
Enable modern features (nullable, implicit usings)
Consolidate shared settings into
Directory.Build.props
Files
- ATTRIBUTION.mdAdded by AutoGineer856 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md17.0 KiB
SKILL.md
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.csfile)ToolsVersionattribute on<Project>elementpackages.configfile presentProperties\AssemblyInfo.cswith assembly-level attributes
SDK-style indicators:
<Project Sdk="Microsoft.NET.Sdk">attribute on root elementMinimal content — a simple project may be 10–15 lines
No explicit file includes (implicit globbing)
<PackageReference>items instead ofpackages.config
Quick check: if a .csproj is more than 50 lines for a simple class library or console app, it is likely legacy format.
<!-- 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><!-- 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 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:
<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:
<PropertyGroup>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
</PropertyGroup>AFTER:
<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:
<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:
<ItemGroup>
<Content Include="..\shared\config.json" Link="config.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>Step 4: Remove AssemblyInfo.cs
BEFORE (Properties\AssemblyInfo.cs):
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):
<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:
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>Step 5: Migrate packages.config → PackageReference
BEFORE (packages.config):
<?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):
<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 PackageReferenceCLI:
dotnet migrate-packages-configor manual conversionBinding redirects: SDK-style projects auto-generate binding redirects — remove the
<runtime>section fromapp.configif present
Step 6: Remove Unnecessary Boilerplate
Delete all of the following — the SDK provides sensible defaults:
<!-- 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:
<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 withglobal.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 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')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{12345678-1234-1234-1234-123456789ABC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MyLibrary</RootNamespace>
<AssemblyName>MyLibrary</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<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>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Models\User.cs" />
<Compile Include="Models\Order.cs" />
<Compile Include="Services\UserService.cs" />
<Compile Include="Services\OrderService.cs" />
<Compile Include="Helpers\StringExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>AFTER (SDK-style — 11 lines):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup>
</Project>Common Migration Issues
Embedded resources: files not in a standard location may need explicit includes:
<ItemGroup>
<EmbeddedResource Include="..\shared\Schemas\*.xsd" LinkBase="Schemas" />
</ItemGroup>Content files with CopyToOutputDirectory: these still need explicit entries:
<ItemGroup>
<Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Include="scripts\*.sql" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>Multi-targeting: change the element name from singular to plural:
<!-- Single target -->
<TargetFramework>net8.0</TargetFramework>
<!-- Multiple targets -->
<TargetFrameworks>net472;net8.0</TargetFrameworks>WPF/WinForms projects: use the appropriate SDK or properties:
<!-- Option A: WindowsDesktop SDK -->
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<!-- Option B: properties in standard SDK (preferred for .NET 5+) -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UseWPF>true</UseWPF>
<!-- or -->
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
</Project>Test projects: use the standard SDK with test framework packages:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7" />
</ItemGroup>
</Project>Central Package Management Migration
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:
<!-- BEFORE -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!-- AFTER -->
<PackageReference Include="Newtonsoft.Json" />Directory.Build Consolidation
Identify properties repeated across multiple .csproj files and move them to shared files.
Directory.Build.props (for properties — placed at repo or src root):
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Company>Contoso</Company>
<Copyright>Copyright © Contoso 2024</Copyright>
</PropertyGroup>
</Project>Directory.Build.targets (for targets/tasks — placed at repo or src root):
<Project>
<Target Name="PrintBuildInfo" AfterTargets="Build">
<Message Importance="High" Text="Built $(AssemblyName) → $(TargetPath)" />
</Target>
</Project>Keep in individual .csproj files only what is project-specific:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<AssemblyName>MyApp</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" />
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
</ItemGroup>
</Project>Tools and Automation
| 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:
Run
try-convertfor a first passReview and clean up the output manually
Build and fix any issues
Enable modern features (nullable, implicit usings)
Consolidate shared settings into
Directory.Build.props
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- .NET (Microsoft)
- Publisher
- .NET (Microsoft) (Official)
- Source
- dotnet/skills · plugins/dotnet-msbuild/skills/msbuild-modernization · commit 96c3d68
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by .NET (Microsoft).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download msbuild-modernization.zip