{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/msbuild-modernization",
  "version": "1.0.0",
  "name": "msbuild-modernization",
  "description": "Guide for modernizing and migrating MSBuild project files to SDK-style format. Only activate in MSBuild/.NET build context. 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. INVOKES: dotnet try-convert, upgrade-assistant tools.",
  "system_prompt_fragment": "# MSBuild Modernization: Legacy to SDK-style Migration\n\n## Identifying Legacy vs SDK-style Projects\n\n**Legacy indicators:**\n\n- `<Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />`\n- Explicit file lists (`<Compile Include=\"...\" />` for every `.cs` file)\n- `ToolsVersion` attribute on `<Project>` element\n- `packages.config` file present\n- `Properties\\AssemblyInfo.cs` with assembly-level attributes\n\n**SDK-style indicators:**\n\n- `<Project Sdk=\"Microsoft.NET.Sdk\">` attribute on root element\n- Minimal content — a simple project may be 10–15 lines\n- No explicit file includes (implicit globbing)\n- `<PackageReference>` items instead of `packages.config`\n\n**Quick check:** if a `.csproj` is more than 50 lines for a simple class library or console app, it is likely legacy format.\n\n```xml\n<!-- Legacy: ~80+ lines for a simple library -->\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <OutputType>Library</OutputType>\n    <RootNamespace>MyLibrary</RootNamespace>\n    <AssemblyName>MyLibrary</AssemblyName>\n    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <Deterministic>true</Deterministic>\n  </PropertyGroup>\n  <!-- ... 60+ more lines ... -->\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n</Project>\n```\n\n```xml\n<!-- SDK-style: ~8 lines for the same library -->\n<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net472</TargetFramework>\n  </PropertyGroup>\n</Project>\n```\n\n## Migration Checklist: Legacy → SDK-style\n\n### Step 1: Replace Project Root Element\n\n**BEFORE:**\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\"\n          Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <!-- ... project content ... -->\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n</Project>\n```\n\n**AFTER:**\n\n```xml\n<Project Sdk=\"Microsoft.NET.Sdk\">\n  <!-- ... project content ... -->\n</Project>\n```\n\nRemove the XML declaration, `ToolsVersion`, `xmlns`, and both `<Import>` lines. The `Sdk` attribute replaces all of them.\n\n### Step 2: Set TargetFramework\n\n**BEFORE:**\n\n```xml\n<PropertyGroup>\n  <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>\n</PropertyGroup>\n```\n\n**AFTER:**\n\n```xml\n<PropertyGroup>\n  <TargetFramework>net472</TargetFramework>\n</PropertyGroup>\n```\n\n**TFM mapping table:**\n\n| Legacy `TargetFrameworkVersion` | SDK-style `TargetFramework` |\n|---------------------------------|-----------------------------|\n| `v4.6.1`                        | `net461`                    |\n| `v4.7.2`                        | `net472`                    |\n| `v4.8`                          | `net48`                     |\n| (migrating to .NET 6)           | `net6.0`                    |\n| (migrating to .NET 8)           | `net8.0`                    |\n\n### Step 3: Remove Explicit File Includes\n\n**BEFORE:**\n\n```xml\n<ItemGroup>\n  <Compile Include=\"Controllers\\HomeController.cs\" />\n  <Compile Include=\"Models\\User.cs\" />\n  <Compile Include=\"Models\\Order.cs\" />\n  <Compile Include=\"Services\\AuthService.cs\" />\n  <Compile Include=\"Services\\OrderService.cs\" />\n  <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n  <!-- ... 50+ more lines ... -->\n</ItemGroup>\n<ItemGroup>\n  <Content Include=\"Views\\Home\\Index.cshtml\" />\n  <Content Include=\"Views\\Shared\\_Layout.cshtml\" />\n  <!-- ... more content files ... -->\n</ItemGroup>\n```\n\n**AFTER:**\n\nDelete all of these `<Compile>` and `<Content>` item groups entirely. SDK-style projects include them automatically via implicit globbing.\n\n**Exception:** keep explicit entries only for files that need special metadata or reside outside the project directory:\n\n```xml\n<ItemGroup>\n  <Content Include=\"..\\shared\\config.json\" Link=\"config.json\" CopyToOutputDirectory=\"PreserveNewest\" />\n</ItemGroup>\n```\n\n### Step 4: Remove AssemblyInfo.cs\n\n**BEFORE** (`Properties\\AssemblyInfo.cs`):\n\n```csharp\nusing System.Reflection;\nusing System.Runtime.InteropServices;\n\n[assembly: AssemblyTitle(\"MyLibrary\")]\n[assembly: AssemblyDescription(\"A useful library\")]\n[assembly: AssemblyCompany(\"Contoso\")]\n[assembly: AssemblyProduct(\"MyLibrary\")]\n[assembly: AssemblyCopyright(\"Copyright © Contoso 2024\")]\n[assembly: ComVisible(false)]\n[assembly: Guid(\"...\")]\n[assembly: AssemblyVersion(\"1.2.0.0\")]\n[assembly: AssemblyFileVersion(\"1.2.0.0\")]\n```\n\n**AFTER** (in `.csproj`):\n\n```xml\n<PropertyGroup>\n  <AssemblyTitle>MyLibrary</AssemblyTitle>\n  <Description>A useful library</Description>\n  <Company>Contoso</Company>\n  <Product>MyLibrary</Product>\n  <Copyright>Copyright © Contoso 2024</Copyright>\n  <Version>1.2.0</Version>\n</PropertyGroup>\n```\n\nDelete `Properties\\AssemblyInfo.cs` — the SDK auto-generates assembly attributes from these properties.\n\n**Alternative:** if you prefer to keep `AssemblyInfo.cs`, disable auto-generation:\n\n```xml\n<PropertyGroup>\n  <GenerateAssemblyInfo>false</GenerateAssemblyInfo>\n</PropertyGroup>\n```\n\n### Step 5: Migrate packages.config → PackageReference\n\n**BEFORE** (`packages.config`):\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n  <package id=\"Newtonsoft.Json\" version=\"13.0.3\" targetFramework=\"net472\" />\n  <package id=\"Serilog\" version=\"3.1.1\" targetFramework=\"net472\" />\n  <package id=\"Microsoft.Extensions.DependencyInjection\" version=\"8.0.0\" targetFramework=\"net472\" />\n</packages>\n```\n\n**AFTER** (in `.csproj`):\n\n```xml\n<ItemGroup>\n  <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\n  <PackageReference Include=\"Serilog\" Version=\"3.1.1\" />\n  <PackageReference Include=\"Microsoft.Extensions.DependencyInjection\" Version=\"8.0.0\" />\n</ItemGroup>\n```\n\nDelete `packages.config` after migration.\n\n**Migration options:**\n\n- **Visual Studio:** right-click `packages.config` → *Migrate packages.config to PackageReference*\n- **CLI:** `dotnet migrate-packages-config` or manual conversion\n- **Binding redirects:** SDK-style projects auto-generate binding redirects — remove the `<runtime>` section from `app.config` if present\n\n### Step 6: Remove Unnecessary Boilerplate\n\nDelete all of the following — the SDK provides sensible defaults:\n\n```xml\n<!-- DELETE: SDK imports (replaced by Sdk attribute) -->\n<Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" ... />\n<Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n\n<!-- DELETE: default Configuration/Platform (SDK provides these) -->\n<PropertyGroup>\n  <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n  <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n  <ProjectGuid>{...}</ProjectGuid>\n  <OutputType>Library</OutputType>  <!-- keep only if not Library -->\n  <AppDesignerFolder>Properties</AppDesignerFolder>\n  <FileAlignment>512</FileAlignment>\n  <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>\n  <Deterministic>true</Deterministic>\n</PropertyGroup>\n\n<!-- DELETE: standard Debug/Release configurations (SDK defaults match) -->\n<PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n  <DebugSymbols>true</DebugSymbols>\n  <DebugType>full</DebugType>\n  <Optimize>false</Optimize>\n  <OutputPath>bin\\Debug\\</OutputPath>\n  <DefineConstants>DEBUG;TRACE</DefineConstants>\n  <ErrorReport>prompt</ErrorReport>\n  <WarningLevel>4</WarningLevel>\n</PropertyGroup>\n<PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n  <DebugType>pdbonly</DebugType>\n  <Optimize>true</Optimize>\n  <OutputPath>bin\\Release\\</OutputPath>\n  <DefineConstants>TRACE</DefineConstants>\n  <ErrorReport>prompt</ErrorReport>\n  <WarningLevel>4</WarningLevel>\n</PropertyGroup>\n\n<!-- DELETE: framework assembly references (implicit in SDK) -->\n<ItemGroup>\n  <Reference Include=\"System\" />\n  <Reference Include=\"System.Core\" />\n  <Reference Include=\"System.Data\" />\n  <Reference Include=\"System.Xml\" />\n  <Reference Include=\"System.Xml.Linq\" />\n  <Reference Include=\"Microsoft.CSharp\" />\n</ItemGroup>\n\n<!-- DELETE: packages.config reference -->\n<None Include=\"packages.config\" />\n\n<!-- DELETE: designer service entries -->\n<Service Include=\"{508349B6-6B84-11D3-8410-00C04F8EF8E0}\" />\n```\n\n**Keep** only properties that differ from SDK defaults (e.g., `<OutputType>Exe</OutputType>`, `<RootNamespace>` if it differs from the assembly name, custom `<DefineConstants>`).\n\n### Step 7: Enable Modern Features\n\nAfter migration, consider enabling modern C# features:\n\n```xml\n<PropertyGroup>\n  <TargetFramework>net8.0</TargetFramework>\n  <Nullable>enable</Nullable>\n  <ImplicitUsings>enable</ImplicitUsings>\n</PropertyGroup>\n```\n\n- `<Nullable>enable</Nullable>` — enables nullable reference type analysis\n- `<ImplicitUsings>enable</ImplicitUsings>` — auto-imports common namespaces (.NET 6+)\n- **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.\n\n## Complete Before/After Example\n\n**BEFORE** (legacy — 65 lines):\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\"\n          Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\n  <PropertyGroup>\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n    <ProjectGuid>{12345678-1234-1234-1234-123456789ABC}</ProjectGuid>\n    <OutputType>Library</OutputType>\n    <AppDesignerFolder>Properties</AppDesignerFolder>\n    <RootNamespace>MyLibrary</RootNamespace>\n    <AssemblyName>MyLibrary</AssemblyName>\n    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>\n    <FileAlignment>512</FileAlignment>\n    <Deterministic>true</Deterministic>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \">\n    <DebugSymbols>true</DebugSymbols>\n    <DebugType>full</DebugType>\n    <Optimize>false</Optimize>\n    <OutputPath>bin\\Debug\\</OutputPath>\n    <DefineConstants>DEBUG;TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' \">\n    <DebugType>pdbonly</DebugType>\n    <Optimize>true</Optimize>\n    <OutputPath>bin\\Release\\</OutputPath>\n    <DefineConstants>TRACE</DefineConstants>\n    <ErrorReport>prompt</ErrorReport>\n    <WarningLevel>4</WarningLevel>\n  </PropertyGroup>\n  <ItemGroup>\n    <Reference Include=\"System\" />\n    <Reference Include=\"System.Core\" />\n    <Reference Include=\"System.Xml.Linq\" />\n    <Reference Include=\"Microsoft.CSharp\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Models\\User.cs\" />\n    <Compile Include=\"Models\\Order.cs\" />\n    <Compile Include=\"Services\\UserService.cs\" />\n    <Compile Include=\"Services\\OrderService.cs\" />\n    <Compile Include=\"Helpers\\StringExtensions.cs\" />\n    <Compile Include=\"Properties\\AssemblyInfo.cs\" />\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"packages.config\" />\n  </ItemGroup>\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\n</Project>\n```\n\n**AFTER** (SDK-style — 11 lines):\n\n```xml\n<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net472</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\n    <PackageReference Include=\"Serilog\" Version=\"3.1.1\" />\n  </ItemGroup>\n</Project>\n```\n\n## Common Migration Issues\n\n**Embedded resources:** files not in a standard location may need explicit includes:\n\n```xml\n<ItemGroup>\n  <EmbeddedResource Include=\"..\\shared\\Schemas\\*.xsd\" LinkBase=\"Schemas\" />\n</ItemGroup>\n```\n\n**Content files with CopyToOutputDirectory:** these still need explicit entries:\n\n```xml\n<ItemGroup>\n  <Content Include=\"appsettings.json\" CopyToOutputDirectory=\"PreserveNewest\" />\n  <None Include=\"scripts\\*.sql\" CopyToOutputDirectory=\"PreserveNewest\" />\n</ItemGroup>\n```\n\n**Multi-targeting:** change the element name from singular to plural:\n\n```xml\n<!-- Single target -->\n<TargetFramework>net8.0</TargetFramework>\n\n<!-- Multiple targets -->\n<TargetFrameworks>net472;net8.0</TargetFrameworks>\n```\n\n**WPF/WinForms projects:** use the appropriate SDK or properties:\n\n```xml\n<!-- Option A: WindowsDesktop SDK -->\n<Project Sdk=\"Microsoft.NET.Sdk.WindowsDesktop\">\n\n<!-- Option B: properties in standard SDK (preferred for .NET 5+) -->\n<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <UseWPF>true</UseWPF>\n    <!-- or -->\n    <UseWindowsForms>true</UseWindowsForms>\n  </PropertyGroup>\n</Project>\n```\n\n**Test projects:** use the standard SDK with test framework packages:\n\n```xml\n<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net8.0</TargetFramework>\n    <IsPackable>false</IsPackable>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.9.0\" />\n    <PackageReference Include=\"xunit\" Version=\"2.7.0\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.5.7\" />\n  </ItemGroup>\n</Project>\n```\n\n## Central Package Management Migration\n\nCentralizes 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.\n\n**Step 1:** Create `Directory.Packages.props` at the repository root with `<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>` and `<PackageVersion>` items for all packages.\n\n**Step 2:** Remove `Version` from each project's `PackageReference`:\n\n```xml\n<!-- BEFORE -->\n<PackageReference Include=\"Newtonsoft.Json\" Version=\"13.0.3\" />\n\n<!-- AFTER -->\n<PackageReference Include=\"Newtonsoft.Json\" />\n```\n\n## Directory.Build Consolidation\n\nIdentify properties repeated across multiple `.csproj` files and move them to shared files.\n\n**`Directory.Build.props`** (for properties — placed at repo or src root):\n\n```xml\n<Project>\n  <PropertyGroup>\n    <TargetFramework>net8.0</TargetFramework>\n    <Nullable>enable</Nullable>\n    <ImplicitUsings>enable</ImplicitUsings>\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n    <Company>Contoso</Company>\n    <Copyright>Copyright © Contoso 2024</Copyright>\n  </PropertyGroup>\n</Project>\n```\n\n**`Directory.Build.targets`** (for targets/tasks — placed at repo or src root):\n\n```xml\n<Project>\n  <Target Name=\"PrintBuildInfo\" AfterTargets=\"Build\">\n    <Message Importance=\"High\" Text=\"Built $(AssemblyName) → $(TargetPath)\" />\n  </Target>\n</Project>\n```\n\n**Keep in individual `.csproj` files** only what is project-specific:\n\n```xml\n<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <AssemblyName>MyApp</AssemblyName>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Serilog\" />\n    <ProjectReference Include=\"..\\MyLibrary\\MyLibrary.csproj\" />\n  </ItemGroup>\n</Project>\n```\n\n## Tools and Automation\n\n| Tool | Usage |\n|------|-------|\n| `dotnet try-convert` | Automated legacy-to-SDK conversion. Install: `dotnet tool install -g try-convert` |\n| .NET Upgrade Assistant | Full migration including API changes. Install: `dotnet tool install -g upgrade-assistant` |\n| Visual Studio | Right-click `packages.config` → *Migrate packages.config to PackageReference* |\n| Manual migration | Often cleanest for simple projects — follow the checklist above |\n\n**Recommended approach:**\n\n1. Run `try-convert` for a first pass\n2. Review and clean up the output manually\n3. Build and fix any issues\n4. Enable modern features (nullable, implicit usings)\n5. Consolidate shared settings into `Directory.Build.props`",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/msbuild-modernization"
  ],
  "tags": [
    "dotnet-msbuild",
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-msbuild/skills/msbuild-modernization/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-msbuild/skills/msbuild-modernization/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}