MSBuild Target Batching (For Each) Simplified
It’s actually quite simple:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Default">
<ItemGroup>
<ProjectsToPublish Include="AdminConsole\AdminConsole.csproj" />
<ProjectsToPublish Include="AdminService\AdminService.csproj" />
</ItemGroup>
<Target Name="Default">
<CallTarget Targets="PublishProjectOutput" />
</Target>
<Target Name="PublishProjectOutput" Inputs="@(ProjectsToPublish)" Outputs="%(Identity).Dummy">
<Message Text="@(ProjectsToPublish)" />
</Target>
</Project>
We set up an item group containing the items that we’d like to process. Our “Default” target is just to demonstrate that
we don’t need to do anything clever with CallTarget
.
In order to get MSBuild to run the PublishProjectOutput
target for each item in the item group, the necessary magic is
in the Inputs
and Outputs
stuff. The trick is that the value in Outputs
is the item metadata of the value in
Inputs
. That is: %(Identity)
is actually treated as if it was %(ProjectsToPublish.Identity)
. MSBuild batches where
the metadata values match. Since Identity
is unique, each batch will contain a single item, giving us the “for each”
behaviour we’re looking for.
In order to get MSBuild to run the target at all, we need to specify the output files that will be generated. If we
were to specify just %(Identity)
, MSBuild would decide that the outputs were up-to-date and would skip the target. So
we dirty up the output by adding a fake extension to the filename. What this is doesn’t particularly matter.
%(Identity).Quack
would work just as well (as long as you don’t habitually have files called Foo.csproj.Quack
, of
course).