ASP.NET Core, DevOps, DevTools, MVC

MSBuild… what? I just right-click on Publish

What is MSBuild? I had no idea. It has the word “build” in it, it must build something. Oh, I also remember that in the early stages of .NET Core there was a lot of discussion about project.json and the all DNX thing…. Then they decided to keep this MSBuild thingy. In the end we are all using Visual Studio and the only thing I needed to know is how to set up a Publish definition, right-click on publish and voila! My app was ready to ship. Untill…

While doing some DevOps work, I wanted to build the app only once but transform the config file multiple times based on the environment. So I started digging into this MSBuild. What is it? Here is Microsoft definition: MSBuild is the Microsoft Build Engine, a platform for building applications.
It really does a lot for our apps but all its work is hidden behind the “greatest GUI” of all time: Visual Studio.

I was going into this discovery with the idea that MSBuild would work like some of the other build process. Run a command with some flags and the build is done. Instead, the story is slightly different. It is a bit like the Angular CLI with schematics. You use schematics to define custom actions or re-define existing actions. In MSBuild you use a build file (xml format) to define the sequence of actions you need to do. That is what the .csproj are. Just build definitions. When you run MSBuild and point to a .sln file, it knows how to go through each .csproj and run the build processes as needed. Let’s see how it works and play with it.

Where is MSbuild in our machines?

The MSBuild command comes with Windows installation here: C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe based on the .NET Framework. But this version is kind of bare bone, it lucks a bunch of extensions you might need for application specific builds (like we.config transformation).

Visual Studio installation brings in a more complete version of it here: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe (actual location may vary based on your VS version and installation settings). This version includes a bunch of extensions saved in these folders:

First a bit of configuration:

Let’s start by making sure MSBuild work. Open PowerShell (or your command line of choice) and just run the MSBuild that comes with Windows with the -version flag:

As you can see all is good, MSBuild responded with its version. Now let’s see the version available with Visual Studio install:

Much newer version (obviously).

We will be using this version for the exercises below.

Hello World

This example comes from here here.

Go to your working directory of choice and create a new file named HelloWorld.build. I use VS Code but you can use your editor of choice. The content of this new file is:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0"  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Target Name="HelloWorld">
        <Message Text="Hello"></Message>
        <Message Text="World"></Message>
    </Target>
</Project>

MSBuild has 2 main concepts in executing instructions:

  • Target
  • Task

The first is a set of instructions/command to complete a larger unit of work. A task is the smallest unit of work, usually just one instruction. All instructions are wrapped in a Project tag. A target is called via a flag on the MSBuild invocation /t:TargetName (see below). In this case we just print on the console “Hello” and “World”. Let’s do it.

In PowerShell run this command:

& "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe" HelloWorld.build /t:HelloWorld

And:

How about flow control and variables?

But of course they are possible. A new variable is just a custom xml tag and condition can be build in a PropertyGroup tag. Create another build definition file and call it example2.build with this content:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0"  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup Condition="'$(Name)' == ''">
        <OutMsg>Please let us know your name!</OutMsg>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Name)' != ''">
        <OutMsg>Welcome $(Name)!</OutMsg>
    </PropertyGroup>
    <Target Name="Condition">
            <Message Text="$(OutMsg)"></Message>
    </Target>
</Project>

Then run it with:

& "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe" example2.build /t:Condition /p:Name=""

And then assign your first name to Name:

& "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe" example2.build /t:Condition /p:Name="Emanuele"

You know already the result:

How about that web.config transformation?

In this case, we will use a task (small unit of work) and it is a preexisting task that comes with the Visual Studio installation. So we must import such task. Here is the build definition:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0"  xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Staging|AnyCPU'">
        <BuildConfig>Staging</BuildConfig>
    </PropertyGroup>
    <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
        <BuildConfig>Release</BuildConfig>
    </PropertyGroup>
    <UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
    <Target Name="TransformWebConfig">
        <TransformXml Source="Configuration/Web.config"
                      Transform="Configuration/Web.$(BuildConfig).config"
                      Destination="Web.config"
                      StackTrace="true"/>
    </Target>
</Project>

I called this definition web_configs.build.

We then need the we.config files to be transformed. I created 3 files inside a folder called Configuration:

  • web.config
  • web.Staging.config
  • web.Release.config

Here is the xml in each file. Nothing new just regular web.config with environment transformations.

<!-- web.config -->
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <connectionStrings>
        <add name="entities" connectionString="Debug" />
    </connectionStrings>
</configuration>

<!-- web.Staging.config -->
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <connectionStrings>
        <add name="entities" connectionString="Staging" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
    </connectionStrings>
</configuration>

<!-- web.Release.config -->
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <connectionStrings>
        <add name="entities" connectionString="Release" xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
    </connectionStrings>
</configuration>

Let’s now run this build definition with this command:

& "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe" web_configs.build /t:TransformWebConfig /p:Platform=AnyCPU /p:Configuration=Staging

As expected a new transformed web.config file is created in the working directory:

With this expected content:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <connectionStrings>
        <add name="entities" connectionString="Staging" />
    </connectionStrings>
</configuration>

You can run the Release version of the above command, just change the Configuration property value.

Well, I started this little journey a bit worry about the complexity of MSBuild, but once I understood a couple of the basic concepts, it wasn’t that hard to use. Keep in mind that you can do a lot with MSBuild, from copying files around, to run node, npm or other command line scripts.

I would love to hear some interesting use of MSBuild you have done. Feel free to leave a comment below.