A very useful and illuminating article:
It also expertly articulates a common feeling:
It brings me great joy that the title of this blog post is both technically accurate and evokes the type of comic-book language I use when trying to figure out MSBuild variable syntax.
It expounds on how to express discrete items, lists, and dictionaries as well as how to refer to them; even implicitly looping over collections.
At the high-level:
- $: Refers to scalar values. Renders an empty-string for collections.
- @: Can be used to pass collections or flatten a collection into semicolon-delimited values if used in a scalar context (like embedding in a message task). Note that the individual values will first be normalized by expanding by semicolon and then be flattened by semicolon. So, extraneous semicolons will in fact be removed.
- %: Expand the given task into N-tasks for each item in a collection.
It’s worth mentioning that %(VariableName) will not expand in such a way that it would result in doing the same thing twice.
For example, I have a list of two assemblies and two XML files of the same two names and would like to print them:
<Target Name="BeforeBuild"> <ItemGroup> <File Include="$(MSBuildProjectDirectory)\AncillaryAssemblies\*.*" /> </ItemGroup> <Message Text="Inner file: %(File.Filename)" /> </Target>
1> Inner file: EntityFramework 1> Inner file: EntityFramework.SqlServer
However, if I were to print the extensions, as well:
<Target Name="BeforeBuild"> <ItemGroup> <File Include="$(MSBuildProjectDirectory)\AncillaryAssemblies\*.*" /> </ItemGroup> <Message Text="Inner file: %(File.Filename) %(File.Extension)" /> </Target>
1> Inner file: EntityFramework .dll 1> Inner file: EntityFramework.SqlServer .dll 1> Inner file: EntityFramework.SqlServer .xml 1> Inner file: EntityFramework .xml
Something to keep in mind.