---
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:**
- ``
- Explicit file lists (`` for every `.cs` file)
- `ToolsVersion` attribute on `` element
- `packages.config` file present
- `Properties\AssemblyInfo.cs` with assembly-level attributes
**SDK-style indicators:**
- `` attribute on root element
- Minimal content — a simple project may be 10–15 lines
- No explicit file includes (implicit globbing)
- `` 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
Debug
AnyCPU
Library
MyLibrary
MyLibrary
v4.7.2
512
true
```
```xml
net472
```
## Migration Checklist: Legacy → SDK-style
### Step 1: Replace Project Root Element
**BEFORE:**
```xml
```
**AFTER:**
```xml
```
Remove the XML declaration, `ToolsVersion`, `xmlns`, and both `` lines. The `Sdk` attribute replaces all of them.
### Step 2: Set TargetFramework
**BEFORE:**
```xml
v4.7.2
```
**AFTER:**
```xml
net472
```
**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
```
**AFTER:**
Delete all of these `` and `` 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
```
### 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
MyLibrary
A useful library
Contoso
MyLibrary
Copyright © Contoso 2024
1.2.0
```
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
false
```
### Step 5: Migrate packages.config → PackageReference
**BEFORE** (`packages.config`):
```xml
```
**AFTER** (in `.csproj`):
```xml
```
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 `` section from `app.config` if present
### Step 6: Remove Unnecessary Boilerplate
Delete all of the following — the SDK provides sensible defaults:
```xml
Debug
AnyCPU
{...}
Library
Properties
512
true
true
true
full
false
bin\Debug\
DEBUG;TRACE
prompt
4
pdbonly
true
bin\Release\
TRACE
prompt
4
```
**Keep** only properties that differ from SDK defaults (e.g., `Exe`, `` if it differs from the assembly name, custom ``).
### Step 7: Enable Modern Features
After migration, consider enabling modern C# features:
```xml
net8.0
enable
enable
```
- `enable` — enables nullable reference type analysis
- `enable` — auto-imports common namespaces (.NET 6+)
- **Avoid `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 `` 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 `` (e.g. `12`) per project to directly control the language version.
## Complete Before/After Example
**BEFORE** (legacy — 65 lines):
```xml
Debug
AnyCPU
{12345678-1234-1234-1234-123456789ABC}
Library
Properties
MyLibrary
MyLibrary
v4.7.2
512
true
true
full
false
bin\Debug\
DEBUG;TRACE
prompt
4
pdbonly
true
bin\Release\
TRACE
prompt
4
```
**AFTER** (SDK-style — 11 lines):
```xml
net472
```
## Common Migration Issues
**Embedded resources:** files not in a standard location may need explicit includes:
```xml
```
**Content files with CopyToOutputDirectory:** these still need explicit entries:
```xml
```
**Multi-targeting:** change the element name from singular to plural:
```xml
net8.0
net472;net8.0
```
**WPF/WinForms projects:** use the appropriate SDK or properties:
```xml
true
true
```
**Test projects:** use the standard SDK with test framework packages:
```xml
net8.0
false
```
## 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](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 `true` and `` items for all packages.
**Step 2:** Remove `Version` from each project's `PackageReference`:
```xml
```
## 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):
```xml
net8.0
enable
enable
true
Contoso
Copyright © Contoso 2024
```
**`Directory.Build.targets`** (for targets/tasks — placed at repo or src root):
```xml
```
**Keep in individual `.csproj` files** only what is project-specific:
```xml
Exe
MyApp
```
## 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:**
1. Run `try-convert` for a first pass
2. Review and clean up the output manually
3. Build and fix any issues
4. Enable modern features (nullable, implicit usings)
5. Consolidate shared settings into `Directory.Build.props`