Cleaning MsBuild ItemGroup Includes

Here's a neat solution (that I totally stole from Sayed Ibrahim Hashimi ) to a problem that I sometimes run into when writing longer or multi-file msbuild scripts.

If you create an ItemGroup inside an MsBuild target:

    <ItemGroup>
    	<MyFiles Include="a.cs" />
    	<MyFiles Include="b.cs" />
    	<MyFiles Include="c.cs" />
    </ItemGroup>

You can then use the @(MyFiles) syntax to process each of the files. Probably your expectation is that the MyFiles collection only includes a, b and c.

Now, if you accidently recycle the name "MyFiles", say by:

  • Adding adding a new ItemGroup explicitly
  • Changing Target dependencies change the order of execution
  • Changing Target dependencies change the number of targets that are executed
  • You include a new script with a matching ItemGroup

you include you can run into trouble. You may no longer have a fresh list with just the items you think you have.

This example:

    <!-- Global group -->
    <ItemGroup>
    	<MyFiles Include="a.cs" />
    	<MyFiles Include="b.cs" />
    	<MyFiles Include="c.cs" />
    </ItemGroup>

    <!--
    	Lots of other lines of script in between,
    	DoStuff may even be in an Imported file
    -->

    <Target Name="DoStuff">

    	<!-- Local group -->
    	<ItemGroup>
    		<MyFiles Include="d.cs" />
    		<MyFiles Include="e.cs" />
    		<MyFiles Include="f.cs" />
    	</ItemGroup>

    	<Message Text="@(MyFiles)" />

    </Target>

will print a.cs b.cs ... f.cs where you might be expecting only d.cs e.cs f.cs.

Sayed Ibrahim Hashimi uses a technique where the first line of the ItemGroup empties the current content (if any) then builds the list required.

    <ItemGroup>
    	<MyFiles Remove="@(MyFiles)" />
    	<MyFiles Include="d.cs" />
    	<MyFiles Include="e.cs" />
    	<MyFiles Include="f.cs" />
    </ItemGroup>

Using this convention means you are always guaranteed the content of the list will be what you intended.

Note

This technique only works if the item group is inside a target. Static item groups (what you might think of as global item groups) don't support this behaviour.