Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Patterns for managing MSBuild item groups: Include/Remove/Update semantics, item metadata, batching with %(Metadata), transforms, per-item filtering, and cross-product batching pitfalls. USE FOR: diagnosing and fixing item group anti-patterns in .csproj files, reviewing item management for correctness, fixing CS2002 duplicate file warnings from SDK globbing, fixing targets that run more times than expected due to cross-product batching, fixing Include vs Update misuse on SDK-globbed items, fixin
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-18 | ✗→✓ | ▲ Improved | -15% | 0% |
| case-01 | ✓→✓ | = Same ✓ | -1% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 3% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 159% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 83% | 0% |
Canonical patterns for working with item groups, from Microsoft.Common.CurrentVersion.targets.
| Operation | Purpose | When to use | |---|---|---| | Include | Add new items to the group | Creating items with identity + metadata | | Remove | Remove items matching a pattern | Excluding files or clearing a group | | Update | Modify metadata on existing items | Adding/changing metadata without re-adding |
xml<ItemGroup> <Compile Include="Generated\*.cs"> <AutoGen>true</AutoGen> </Compile> </ItemGroup>
xml<ItemGroup> <!-- Remove specific items --> <Reference Remove="$(AdditionalExplicitAssemblyReferences)" /> <!-- Set subtraction: prior minus current --> <_CleanOrphanFileWrites Include="@(_CleanPriorFileWrites)" Exclude="@(_CleanCurrentFileWrites)" /> <!-- Clear an entire group --> <_Temporary Remove="@(_Temporary)" /> </ItemGroup>
xml<ItemGroup> <EmbeddedResource Update="@(EmbeddedResource)" Condition="'%(NuGetPackageId)' == 'Microsoft.CodeAnalysis.Collections'"> <GenerateSource>true</GenerateSource> <ClassName>Microsoft.CodeAnalysis.Collections.SR</ClassName> </EmbeddedResource> </ItemGroup>
Update does not add items — it only modifies items already in the group.
When %(Metadata) appears in target attributes or task parameters, MSBuild batches execution per unique metadata value.
xml<Target Name="GenerateSatelliteAssemblies" Inputs="$(MSBuildAllProjects);@(_SatelliteAssemblyResourceInputs)" Outputs="$(IntermediateOutputPath)%(Culture)\$(TargetName).resources.dll"> <!-- Runs once per unique Culture value --> </Target>
xml<Copy SourceFiles="@(_SourceItems)" DestinationFiles="@(_SourceItems->'$(OutDir)%(TargetPath)')"> </Copy>
xml<ItemGroup> <_ResxOutput Include="@(EmbeddedResource->'%(OutputResource)')" Condition="'%(EmbeddedResource.WithCulture)' == 'false'" /> </ItemGroup>
%(Metadata) in Condition or Outputs → target batches per unique value.%(Metadata) in task parameters → task batches per unique value.%() from different item groups in the same expression — this causes a cross-product (see Common Pitfalls).Transforms create new item lists by applying an expression to each item:
xml<!-- Transform file paths to destinations --> <Copy SourceFiles="@(IntermediateAssembly)" DestinationFiles="@(IntermediateAssembly->'$(OutDir)%(Filename)%(Extension)')"/> <!-- Transform with separator for display --> <Message Text="Files: @(Compile->'%(Filename)', ', ')" />
xml<ItemGroup> <Compile Include="**\*.cs" Exclude="Generated\**;Tests\**" /> </ItemGroup>
Exclude only works on Include — it cannot be used with Update or Remove.
xml<!-- Condition on ItemGroup — all or nothing --> <ItemGroup Condition="'$(NetCoreBuild)' == 'true'"> <PackageReference Include="System.IO.Pipelines" /> </ItemGroup> <!-- Condition on individual items --> <ItemGroup> <PackageReference Include="System.IO.Pipelines" Condition="'$(NetCoreBuild)' == 'true'" /> </ItemGroup>
xml<ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" PrivateAssets="all" /> <PackageReference Include="StyleCop.Analyzers" PrivateAssets="all" /> </ItemGroup>
Referencing %(Metadata) from two different item groups creates O(N×M) executions:
xml<!-- BAD: Cross-product of @(Source) × @(Config) --> <Exec Command="process %(Source.Identity) with %(Config.Identity)" /> <!-- GOOD: Reference one group via batching, the other via property --> <Exec Command="process %(Source.Identity) with $(ConfigFile)" />
Write to $(IntermediateOutputPath) (obj/), not the source directory. Source-tree generation pollutes version control and can cause duplicate compilation via globs.
Every file created during a target must be added to @(FileWrites) for dotnet clean support.
Other measured skills in the registry, with their headline benchmark lift.