Msbuild Antipatterns
runceel/ReactiveProperty
Catalog of MSBuild anti-patterns with detection rules and fix recipes.
Guide for modernizing and migrating MSBuild project files to SDK-style format.
$ npx skills add microsoft/testfx --skill msbuild-modernization -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install microsoft/testfx msbuild-modernization --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/microsoft/testfx.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/msbuild-modernization .claude/skills/msbuild-modernization && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "msbuild-modernization" agent skill from https://github.com/microsoft/testfx/tree/main/.agents/skills/msbuild-modernization into .claude/skills/msbuild-modernization/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "msbuild-modernization", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/microsoft/testfx/tree/main/.agents/skills/msbuild-modernizationType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add microsoft/testfx --skill msbuild-modernization -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install microsoft/testfx msbuild-modernization --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/microsoft/testfx.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.agents/skills/msbuild-modernization .agents/skills/msbuild-modernization && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "msbuild-modernization" agent skill from https://github.com/microsoft/testfx/tree/main/.agents/skills/msbuild-modernization into .agents/skills/msbuild-modernization/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "msbuild-modernization", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add microsoft/testfx --skill msbuild-modernization -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install microsoft/testfx msbuild-modernization --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/microsoft/testfx.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.agents/skills/msbuild-modernization .cursor/skills/msbuild-modernization && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "msbuild-modernization" agent skill from https://github.com/microsoft/testfx/tree/main/.agents/skills/msbuild-modernization into .cursor/skills/msbuild-modernization/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "msbuild-modernization", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/microsoft/testfx.git --path .agents/skills/msbuild-modernization--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add microsoft/testfx --skill msbuild-modernization -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install microsoft/testfx msbuild-modernization --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/microsoft/testfx.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.agents/skills/msbuild-modernization .gemini/skills/msbuild-modernization && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "msbuild-modernization" agent skill from https://github.com/microsoft/testfx/tree/main/.agents/skills/msbuild-modernization into .gemini/skills/msbuild-modernization/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "msbuild-modernization", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install microsoft/testfx msbuild-modernizationInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add microsoft/testfx --skill msbuild-modernization -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/microsoft/testfx.git skills-src && mkdir -p .github/skills && cp -r skills-src/.agents/skills/msbuild-modernization .github/skills/msbuild-modernization && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "msbuild-modernization" agent skill from https://github.com/microsoft/testfx/tree/main/.agents/skills/msbuild-modernization into .github/skills/msbuild-modernization/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "msbuild-modernization", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add microsoft/testfx --skill msbuild-modernization -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install microsoft/testfx msbuild-modernization --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/microsoft/testfx.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.agents/skills/msbuild-modernization .opencode/skills/msbuild-modernization && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "msbuild-modernization" agent skill from https://github.com/microsoft/testfx/tree/main/.agents/skills/msbuild-modernization into .opencode/skills/msbuild-modernization/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "msbuild-modernization", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
msbuild-modernizationGuide for modernizing and migrating MSBuild project files to SDK-style format.
Msbuild Modernization is an agent skill from microsoft/testfx, published by the product's own GitHub organization. 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…
Its SKILL.md is about 4.3k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Legacy modernization. It works with .NET, npm and C++. The repository describes itself as: This repository holds the source code of Microsoft.Testing.Platform (MTP), a lightweight alternative to VSTest, as well as MSTest adapter and framework. The licence is MIT.
7 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 44b9dcc. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
dotnetFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
schemas.microsoft.comAlso links to:
learn.microsoft.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Msbuild Modernization loads about 4.3k tokens when it runs. Until then it costs about 177 tokens; SKILL.md has 619 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from microsoft/testfx at commit 44b9dcc, republished under its MIT licence (© microsoft). 619 words, ~4,346 tokens.
.claude/skills/msbuild-modernization/SKILL.md (or your agent's skills folder).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.
<!-- 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>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.
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 |
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>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>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:
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:
<!-- 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>).
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+)<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):
<?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>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>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" />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>| 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© microsoft, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .agents/skills/msbuild-modernization of microsoft/testfx.
Open the folder on GitHubat commit 44b9dcc
We found 4 copies of this SKILL.md (exact, near-identical or edited) in other folders, from 3 other GitHub owners. This page covers the copy in microsoft/testfx, which our catalogue first saw on October 7, 2026.
Msbuild Modernization next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Msbuild Modernization this skillmicrosoft/testfx | 1k | 3 repos | ~4.3k | Automated safety check: Pass | MIT | |
| Msbuild Antipatternsrunceel/ReactiveProperty | 944 | — | ~3.7k | Automated safety check: Pass | MIT | |
| Update .NET Supported OS Matrixdotnet/core | 22k | — | ~4.1k | Automated safety check: Pass | MIT | |
| Bump LibdatadogDataDog/dd-trace-dotnet | 573 | — | ~1.7k | Automated safety check: Pass | Apache-2.0 | |
| Migrate Dotnet8 To Dotnet9dotnet/skills | 5.6k | 2 repos | ~3.9k | Automated safety check: Pass | MIT | |
| P/Invoke and LibraryImport for .NETdotnet/skills | 5.6k | 1 repos | ~4.8k | Automated safety check: Pass | MIT |
runceel/ReactiveProperty
Catalog of MSBuild anti-patterns with detection rules and fix recipes.
dotnet/core
Audits and updates the supported-os.json files for .NET releases, checking them against upstream lifecycle data and regenerating the markdown with the release-notes tool.
DataDog/dd-trace-dotnet
Update/bump the libdatadog native library version in dd-trace-dotnet.
dotnet/skills
Migrate a .NET 8 project to .NET 9 and resolve all breaking changes.
dotnet/skills
Helps write, review and debug .NET declarations that call native C and C++ libraries, covering signatures, string marshalling, memory lifetime and SafeHandle.
DataDog/dd-trace-dotnet
Stack Trace Crash Analysis for dd-trace-dotnet. An agent skill from DataDog/dd-trace-dotnet.
microsoft/testfx
Guide for organizing MSBuild infrastructure with Directory.Build.props, Directory.Build.targets, Directory.Packages.props, and Directory.Build.rsp.
microsoft/testfx
Analyze MSBuild binary logs to diagnose build failures. An agent skill from microsoft/testfx.
microsoft/testfx
Project-wide code coverage and CRAP (Change Risk Anti-Patterns) score analysis for .NET projects.
microsoft/testfx
Guide for optimizing MSBuild incremental builds. An agent skill from microsoft/testfx.
microsoft/testfx
Catalog of MSBuild anti-patterns with detection rules and fix recipes.
microsoft/testfx
Guide for interpreting ResolveProjectReferences time in MSBuild performance summaries.
Categories
Guide for modernizing and migrating MSBuild project files to SDK-style format. Msbuild Modernization is an agent skill from microsoft/testfx, published by the product's own GitHub organization. Guide for modernizing and migrating MSBuild project files to SDK-style format.
Msbuild Modernization fits situations like: : 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.
Run `npx skills add microsoft/testfx --skill msbuild-modernization -a claude-code`. Or copy the skill folder (.agents/skills/msbuild-modernization in microsoft/testfx) into .claude/skills/msbuild-modernization in your project. Claude Code loads it when a task matches its description.
Run `npx skills add microsoft/testfx --skill msbuild-modernization -a codex`. Or copy the skill folder (.agents/skills/msbuild-modernization in microsoft/testfx) into .agents/skills/msbuild-modernization in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add microsoft/testfx --skill msbuild-modernization -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/msbuild-modernization, .gemini/skills/msbuild-modernization, .github/skills/msbuild-modernization and .opencode/skills/msbuild-modernization in your project.
Going by SKILL.md and its folder, Msbuild Modernization needs the command-line tools its instructions call (dotnet).
SKILL.md names 2 domains. In commands or code: schemas.microsoft.com; the agent is likely to contact it when it follows the instructions. As links in the text: learn.microsoft.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Msbuild Modernization is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 4.3k tokens (SKILL.md is roughly 17k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Msbuild Modernization: Msbuild Antipatterns (runceel/ReactiveProperty, 944 stars), Update .NET Supported OS Matrix (dotnet/core, 22k stars), Bump Libdatadog (DataDog/dd-trace-dotnet, 573 stars) and Migrate Dotnet8 To Dotnet9 (dotnet/skills, 5.6k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
microsoft (a GitHub organization, an official publisher) maintains it in microsoft/testfx, which has 1,047 GitHub stars. The repository holds 44 skills in this directory. The repository was last updated on October 7, 2026.
Source: microsoft/testfx on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.