MSBuild is the glue that holds the .NET ecosystem together. As a powerful build system, it can do anything and everything to compose your applications. The tool has been around since the inception of .NET, but with .NET Core enhancements to the csproj files, it has become less scary to dive in and make modifications to your build process. In this post, I’ll show you the one trick that should make your .NET build pipeline easier, primarily if you use continuous integrations (CI) providers like AppVeyor, Windows Azure Kudu, or GitHub Actions.

Getting Started

We have a project file for each .NET Core project we start. Let’s look at an original file for a console application.

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.0</TargetFramework>
    </PropertyGroup>
   
</Project>

As you can see, the file is quite understandable. We want our artifact to be an executable program, and that we are targeting .NET Core 3.0.

Access To All Environment Variables

When working within this file, you may see variables accessed via the $() syntax. MSBuild gives you access to all environment variables during your build. We can utilize environment variables to execute targets conditionally. Let’s modify our csproj file with some targets and conditions.

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.0</TargetFramework>
    </PropertyGroup>
    
    <Target Name="Production" Condition="$(Game) == 'Pong'" BeforeTargets="Build">
        <Message Importance="high" Text="Ping!" />
    </Target>

    <Target Name="Development" Condition="$(Game) == 'Ping'" BeforeTargets="Build">
        <Message Importance="high" Text="Pong!" />
    </Target>

</Project>

If the environment variable of Game is Pong, then our build output should have a message of Ping!. If Game is Ping, then we should have an output message of Pong!. Let’s try it out. In a command-line window, we can pass in the environment variable for Game.

➜  ConsoleApp1 Game=Ping dotnet build
Microsoft (R) Build Engine version 16.4.0+e901037fe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 28.26 ms for /Users/khalidabuhakmeh/Projects/dotnet/ConsoleApp1/ConsoleApp1/ConsoleApp1.csproj.
  ConsoleApp1 -> /Users/khalidabuhakmeh/Projects/dotnet/ConsoleApp1/ConsoleApp1/bin/Debug/netcoreapp3.0/ConsoleApp1.dll
  Pong!

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.06

Now, for Pong.

➜  ConsoleApp1 Game=Pong dotnet build
Microsoft (R) Build Engine version 16.4.0+e901037fe for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 29.22 ms for /Users/khalidabuhakmeh/Projects/dotnet/ConsoleApp1/ConsoleApp1/ConsoleApp1.csproj.
  ConsoleApp1 -> /Users/khalidabuhakmeh/Projects/dotnet/ConsoleApp1/ConsoleApp1/bin/Debug/netcoreapp3.0/ConsoleApp1.dll
  Ping!

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.05

As you can see with the two outputs above, we were able to execute build targets based on environment variables selectively. There is an array of options in MSBuild conditions and is viewable at Microsoft’s documentation site.

Environment Variables Everywhere!

In a previous blog post, I talked about running NPM scripts via MSBuild targets, and you should check it out. Building on that post, we can utilize environment variables in our build environment, to selectively run commands to build for different target environments. AppVeyor, a continuous integration service, injects many environment variables for your use. Windows Azure injects all app settings in the portal into your Kudu build environment, and so does GitHub actions.

Conclusion

Microsoft’s simplification of the csproj file makes it easier to reason and modify your .NET Core build process. Utilizing environment variables and MSBuild conditions means we can tailor our build artifacts for our destination environments. Most, if not all, CI services inject many variables that MSBuild can take advantage of, and it is quite simple to build complex conditions on these exposed variables.