Have you ever opened up a .csproj file (the project file created by Visual
Studio for C#) and wondered what all that gobbligook was all about?
This is a quick overview introduction to MSBuild projects so that you can
read and understand Visual Studio project XML syntax right away.
Once created, open up the .csproj file and take look. Here's a default
console app.
<Import
Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Commo
n.props')" />
<PropertyGroup>
<ProjectGuid>{64AD1366-8340-4451-973F-36CC0CC76280}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ConsoleApplication5</RootNamespace>
<AssemblyName>ConsoleApplication5</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
</ItemGroup>
<!-- To modify your build process, add your task inside one of the targets below
and uncomment it.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
</PropertyGroup>
You read these properties out anywhere in the MSBuild script (such as the
Condition here but elsewhere too) using the syntax " $(PropertyName)". You
can even read the same property while updating itself to append it .. for
example ..
<PropertyGroup>
<PropertyA>A</PropertyA>
<PropertyB>B</PropertyB>
<PropertyAB>$(PropertyA).$(PropertyB)</PropertyAB>
<PropertyA>$(PropertyA)X</PropertyA>
</PropertyGroup>
The syntax of the contents of the .props file is exactly the same as the
project, given the necessary <Project> container element.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyCustomProperty>my value</MyCustomProperty>
</PropertyGroup>
</Project>
Property Functions
As mentioned, properties can be read into attributes or values using the
syntax $(PropertyName). You can also evaluate .NET 4+ functions in properties
using the syntax $([CLASS]::METHOD(arguments)) or $([CLASS]::PROPERTY). For
example,
<PropertyGroup>
<BuildTime>$([System.DateTime]::Now</BuildTime>
</PropertyGroup>
Items
As we know, Visual Studio projects normally consist of file manifests. The
assumption made by some, prior to learning this detail about MSBuild
scripts, is that a project is just a manifest plus some properties that get
passed on to a compiler, or something, whatever. Well, the manifest part is
certainly true. The format of this manifest is, of course, the combination of
the <ItemGroup> blocks.
<ItemGroup>
</ItemGroup>
<ItemGroup>
</ItemGroup>
As with <PropertyGroup> tags, ItemGroup tags are simply combined and their
being broken up into multiple groups is largely inconsequential. Having
multiple groups split as such does offer some flexibility to add conditions or
metadata on specific groups, however.
The tag names used for items under <ItemGroup> are the item types. These
also correlate to the Build Action property that you see in Visual Studio in the
Solution Explorer for a particular file. The "None" item type calls for the
"None" Build Action. The "Compile" item type calls for the file to be compiled,
using the compiler associated with the project (i.e. csc, which is the C#
compiler). Generally these are added to a list of parameters for one csc
invocation, not typically isolated to separate invocations. "Content" marks it
as content. And so the list goes on.
<Reference> items obviously aren't file manifest items, but are still part of the
manifest.
All item types have an Include=".." attribute. Some item types call for
additional attributes or even nested tags, but these go beyond the scope of
this article. As with properties, item types can be custom set for your own
use.
Targets
You can think of targets as named sets of instructions of tasks. They are like
named methods or functions or subroutines.
</Target>
I call this "pseudocode" because the above function has no real world
invocation scenario, but it sure reads understandably.
As with the .props file mentioned earlier, the syntax of the contents of the
imported file is just the same as the project XML syntax including
the <Project> container element.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Target>
</Project>
Nested Properties and Items
A custom <Target> can declare its own <PropertyGroup> and its
own <ItemGroup> (and, of course, declare things within them). The scope of
properties or property overrides when declared within the <Target> is applied
by that <Target> when it executes.
For example:
<PropertyGroup>
</PropertyGroup>
<Target Name="MyTarget">
</Target>
<Target Name="AnotherTarget">
<PropertyGroup>
</PropertyGroup>
</Target>
If the above example was pasted directly into your project, the resulting
output in the Output log window would look like this:
Tasks
In the Targets section above where an example Target was declared, the
example included a task. That task was <Message>. MSBuild ships out-of-the-
box with a large number of built-in tasks that you can execute in your targets.
You can find them documented here.
Community Tasks
You can also make use of a number of additional tasks created by the
MSBuild community. Particularly of interest is the project known as "MSBuild
Community Tasks" which is available here.
Getting started with MSBuild Community Tasks
To get started with MSBuild Community Tasks, these are the steps I found
worked for me.
2. The solution now has a new .build virtual folder. Open the .targets file.
3. In the <PropertyGroup> block at the top, remove the first property, then
modify the second property so that only the filename of the DLL is in
the string (no directory, no parenthesis).
Project="$(MSBuildProjectDirectory)\..\.build\MSBuild.Community.Tasks.tar
gets" />
Note that this assumes that the project directory is contained in the
solution directory, otherwise you can eliminate the "..".
5. Now you can add one or many of the documented community tasks.
Try <Beep /> to validate things are working.
UsingTask Declarations
The <UsingTask> declaration enables you to use tasks in your target(s) that
you would otherwise not have access to. You can use the <UsingTask> XML
element to declare one of at least a couple different things:
<UsingTask TaskName="TransformXml"
AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\Web\Microsof
t.Web.Publishing.Tasks.dll" />
<ParameterGroup />
<Task>
</Code>
</Task>
</UsingTask>
<Target Name="TestBuild">
<Hello />
</Target>
<ItemGroup>
<Attendee Include="Fred">
<Guest>Ruth</Guest>
</Attendee>
<Attendee Include="John">
<Guest>Michael</Guest>
</Attendee>
</ItemGroup>
<Target>
</Target>
In this example, a custom task called Rsvp is being invoked twice. MSBuild
will iterate over all of the different items in the list referenced
by %(Attendee.Identity)and pass each one in as a parameter in a separate
invocation of the task. The .Identity reference refers to the value of
the Include="" attribute of the item.
Default Targets
When Visual Studio builds, it iterates over the semicolon-delimited list of
values in the DefaultTargets attribute of the <Project> node of the project file.
So if you want your custom target to always execute whenever Visual Studio
builds the project (or whenever the MSBuild shell command is executed
pointing at your project without specifying the target name) then you might
consider just putting your target in the DefaultTargets attribute by appending
a semicolon and your target's name.