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. Every time I try to dive in and do something non-trivial with MSBuild, I get tripped up over PropertyGroups, ItemGroups, Item Metadata, and the differences in syntax between $(This), @(This), and %(This). I figure I’m probably not alone, so in this post I will try to help my future self, and everyone else that can’t keep this stuff straight.
Property Groups and the Dollar Sign
Property groups are used to capture one or more simple, scalar values. The XML nodes within the property group can be named whatever you would like. Properties can reference the value of other properties in their own values by using the $(PropertName) syntax. This same syntax can be used in other tasks to extract the value of a property. A fairly standard example would be something like this:
<PropertyGroup> <BaseFolder>c:\base\folder</BaseFolder> <SettingsFile>$(BaseFolder)\settings\settings.xml</SettingsFile> </PropertyGroup> <Message Text="Using settings file found at $(SettingsFile)"/>
This gives the following output:
Using settings file found at c:\base\folder\settings\settings.xml
Item Groups and the At Symbol
Item groups are used to capture a single variable that has multiple values, akin to arrays or dictionaries in other languages. An ItemGroup is declared thusly:
<ItemGroup> <MyItems Include="First" /> <MyItems Include="Second;Third;" /> <MyItems Include=";;;;Fourth;;" /> </ItemGroup> <Message Text="My items using dollar: $(MyItems)"/> <Message Text="My items using at symbol: @(MyItems)"/>
Which gives this output:
My items using dollar: My items using at symbol: First;Second;Third;Fourth
As you can see you cannot use the $(MyItems) syntax for ItemGroups; you just get an empty string. Instead, you can use the @(MyItems) to expand the ItemGroup into a semi-colon delimited list. MSBuild is also happy to take the input of an ItemGroup as a semi-colon delimited list to add multiple values to the group in one shot. This comes in handy when passing values between scripts or from the command line. Note that I included some extra semi-colons to prove that MSBuild isn’t just concatenating strings, but is assembling the items into a collection.
Item Metadata and the Percent Sign
Item groups can be used not just as collections of scalar values, but also as collections of key/value dictionaries. These key/value pairs are called Item Metadata in MSBuild. Here is an example:
<ItemGroup> <People Include="Joe"> <Email>firstname.lastname@example.org</Email> </People> <People Include="Bill"> <Email>email@example.com</Email> </People> <People Include="Oscar"> <Email>firstname.lastname@example.org</Email> </People> </ItemGroup> <Message Text="Processing person %(People.Identity) with email %(People.Email)"/>
This gives the output:
Processing person Joe with email email@example.com Processing person Bill with email firstname.lastname@example.org Processing person Oscar with email email@example.com
Note the use of the %(ItemGroup.MetadataKey) syntax. The special key “Identity” refers to the value used in the Include attribute of the item node. Also note that with a single <Message/> task I get output for all three items in the group. This is because of the percent sign syntax which expands the task it is used in and executes that task for every item in a group. This is one of the key features of MSBuild that make it extremely powerful for processing batches of items.
Item Groups and Well-known Metadata
MSBuild is primarily designed for building code from source files. Because of this, it is heavily optimized for processing groups of files. Item groups can easily be created from the file system, and groups of files will automatically contain well-known metadata keys that contain additional information about each file. An example:
<ItemGroup> <FilesToProcess Include=".\theFiles\*.*"/> </ItemGroup> <Message Text="Processing file %(FilesToProcess.Filename)%(FilesToProcess.Extension), created %(FilesToProcess.CreatedTime)"/>
Which gives the output:
Processing file File1.txt, created 2012-08-27 15:46:19.3351955 Processing file File2.txt, created 2012-08-27 15:46:19.3351955 Processing file File3.txt, created 2012-08-27 15:46:19.3351955
Clear as mud?
Hopefully that helps clear things up a bit. MSBuild is a very powerful scripting language, but it definitely requires a bit of paradigm shifting from “normal” languages such as C# in order to use it efficiently. Usually it only takes me a few minutes to re-figure out all of this syntax, but this post should help in the future.