Controlling MSBuild with Environment Variables

Filed under MSBuild

It’s relatively trivial to control MSBuild from within Visual Studio by simply selecting different configurations (typically there are two, DEBUG and RELEASE, though you can create as many as you need).

However, for large projects, switching configurations is not a speedy process, and maintaining multiple configurations can be a chore.

I’ve been working toward automating the build process at my company and one element that I really wanted to implement was selective control of the build process.

For instance, in some cases, I might want to build locally, from within VS, and only build the app itself, but in other cases, I might want to build the Windows portion of the app AND the web portion, and in still other cases, I might want to build the entire app AND compile the InstallShield files to build an MSI installer file.

Obviously, there would come a point where the combinations would get unwieldy using configurations.

I was looking for a solution when I realized that MSBuild maps ALL environment variables by default as “properties” that are easily queried and used in conditions within the build script.

Then, I just needed a way to easily set those environment variables.

I’ve long known that the SET batch command only sets environment variables for that particular DOS session, so that wouldn’t work for this purpose. But in researching this, I discovered the SETX command, exactly like SET, only it sets global environment variables instead of those in the current session. This means that if you invoke, say:

SETX MyVar “This is a test”

The variable MyVar will not be created in the current DOS session, but, close it, and open an new DOS session, and it will be set there!

The Concept

So, to automate the selection, all I should have to do is:

  1. Define the environment vars and their uses
  2. Create batch files that set those variables
  3. Create one more batch file to clear all the variables
  4. Add the batch files to my VS project somewhere

Then, when I want to set the variables one way or another, just run the batch file from within VS, then start a build.

The Batch Files

All that’s required in the batch files is a simple SETX for each variable you need to set.

REM Enabled Building Install
SETX BuildInstall "true"

You might also want to include a bat file that clears all the variables (to “reset” things, if you will), but otherwise, this is all that’s necessary.

To make these BAT files easy to use I created a “Folder” in my solution, dropped the BAT files into that folder and then defined an “Open With” action to open the bat files with CMD.EXE.

Unfortunately, it wasn’t quite that simple. If you right click on a BAT file that you’ve added to the solution, you should see an “Open With” option on the menu. Click it and you’ll get the “Open With” dialog.

Click Add on that dialog and you’ll get something that looks like this

image

The problem is, you can’t specify command line parameters in with the program name. It’s just the name.

To get around that, I created a “RUNBAT.BAT” file, and put it somewhere on my PATH. It’s contents are simple:

REM Just run a passed in bat file 
cmd.exe /c "%1"

Essentially, it’s just a wrapper around CMD.EXE that invokes whatever BAT file name is passed in on the command line.

But now, you can point that “Program Name” field in the “Add Program” dialog to your RUNBAT.BAT file, and presto! You can open that BAT file with just a right click, “Open With”

Even better, select your RUNBAT program entry and click the “Set as Default” button, and now, when you double click the BAT file from within Solution Explorer, the BAT file will automatically be run.

One Final Trick

Unfortunately, that won’t quite be enough to make things work. The thing is, Visual Studio is smart, sometimes too smart for it’s own good. In this case, it’s all about caching. VS caches all the environment vars at the time that it loads, and when you invoke a build, it supplies those cached env vars, and not the currently active set. That would mean you’d have to exit and reload VS each time you wanted to change any env vars to control the build. Definitely not a workable solution!

But there is a way around this.

The Microsoft MSBuild Extensions Pack contains an “EnvironmentVariable” task that explicitly retrieves or sets environment vars from the active env var buffer, and NOT from VS’s cache.

Use it from your MSBuild script like this:

<Target Name="PerformBuild">
     <MSBuild.ExtensionPack.Computer.EnvironmentVariable TaskAction="Get" Variable="ForceBuild" Target="User">
         <Output PropertyName="ForceBuild" TaskParameter="Value"/>
     </MSBuild.ExtensionPack.Computer.EnvironmentVariable>
....

You use the Get action, name the variable, and be sure to use the “User” target (this retrieves User level environment variables, which is where the SETX command will save them). Then, it stores the variable’s value in the property named by the “PropertyName” argument.

You now have the value of that variable in a property that you can easily use in conditions on tasks, or other properties, or whatever. In this example, I check if the “ForceBuild” property that was just retrieved above, is blank, if it is, then the ForceBuild property value is set to “false”.

<PropertyGroup>
<ForceBuild Condition="'$(ForceBuild)' == ''">false</ForceBuild>

Accessing the environment variables in this fashion, your build script will see the values of those variables as they’ve been set by your BAT files, even if those BAT files were executed from within Visual Studio.

It’s pretty simple, clean, and easily maintained from within Visual Studio. All qualities I really like!

If you know of a simpler way, I’d love to hear about it!

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*