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
In order to get MSBuild to run the
PublishProjectOutput target for each item in the item group, the necessary magic is in the
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).