When I wrote on MSBuild earlier this week, I hadn't yet gotten deep enough into one of what I find to be the most confusing parts of the entire system. MSBuild defines two types of variables, Properties (which are interfaced with code exactly as that sounds), and ItemGroups (which are Properites on the class of type ITaskItem[].
The difference, at first, was unclear to me. But it really is quite simple, ItemGroups are arrays of values, and can be generated from a bunch of Include and Exclude attributes at various parts of a MSBuild file. Properties, however, can only have a single value, and what value they are given are processed only at runtime, they are assigned to either the value in the outermost file, or based on an output property. The syntax is, therefore, similar but different.
If you run the above MSBuild file, the output will be Item1;Item3
, because I've told it to exclude Item2. Compare that with the following:
What would you expect to print? You will probably expect to see Item1;Item3;Item4
. You would be wrong. Item2 is not excluded. Apparently, the exclude is only applied to the Include list, before the Include list is added to the ItemGroup. Incidentally, you'll see the exact same behavior if the ItemName tag is in a different ItemGroup. Remember, ItemGroups are additive. Personally, I dislike this behavior quite a bit, I would prefer to be able to exclude items from wherever. The documentation says you can remove items from the list altogether, but you have to use the "Remove" attribute instead of the "Exclude" attribute, however this doesn't work for me. I get errors. Arguably, it's a performance benefit not to remove those items from an Item Group, but I'm not entirely convinced, there need only be one attribute for this, and it should apply to the entire ItemGroup.
Then there is the issue of these types being processed when the MSBuild project starts instead of when they are used. For instance, for our web deployment, I deploy our project into a temporary directory, where I perform necessary modifications to the files before publishing, and then publish. As such, I wrote the following:
This should allow me to do a recursive copy of the files from $(TempBuildDir). But, as I've already indicated, this fails because the code will expand this variable when $(TempBuildDir) is not only empty, but non-existent. End result: no copying. Okay, so I'll use the Output Property on the Copy command, and then use that!
Now, DeployFiles should contain a list of the files, and I can use that same %(RecursiveDir) hack to copy it, right?!
No. I'm not sure exactly how the %(RecursiveDir) hack works, but suffice to say, the way DeployFiles was build, is clearly not the same way as the @(Files) Item is built. Oh, it copied all the files, but it did so without the directory structure, so it was entirely useless.
Because of these problems, I ended up just using xcopy, which is included as standard with all modern versions of Windows.
I didn't want to use the Exec Task, but I had little choice. When I get some time, I'll likely write a DirCopy task to take care of this for us.
Properties are much simpler. The syntax is different, but it's clean.
Value
And refernence now to $(PropertyName) will expand to Value. There are much simpler, but only apply to single items. The only caveat worth remembering here is that a user is free to redefine any Property in a MSBuild file that executes or includes your file, but by using properties, you clearly intend (and rightly so) that value to be changeable, and that's a good thing.
Hopefully this post helps a few of you understand the use of Variables, particularly ItemGroups. They're very powerful, but I know that I've found that they don't always operate in the way I would expect.