Milan Nankov – Blog

Writing quality MSBuild scripts 2012/04/07

Let’s get straight to the point. Builds in one form or another are fundamental part of shipping software and if you are serious about software you have to be serious about your build process and your build scripts in particular. You can have the most brilliant piece of software rotting in a shadowy corner of your source control system due to a problem with your builds. This should not happen.

Before we begin I would like to point out that most of the information here applies to scripts in general. So even if you are not using MSBuild you might find some useful information.

Let’s take a look at a script that was written without any guidelines in mind. Let’s quickly browse through the following script and try to figure out that it is supposed to do.

This is actually a very, very badly written script which is difficult to read,  hard to change, and easy to break.  Here is a rule of thumb – if you cannot understand everything that a script does in a single scan then you probably have a badly written script.

Now that we have set a bar for what constitutes a bad script, maybe you have  seen worse, let me list several tips or guidelines, if you will, that can help you write better scripts.

  • Name script files correctly – This might sound as a no-brainer but I have seen my share of strangely-named scripts that perform tasks with no relation to their names whatsoever. Use descriptive names like GetLatestBinariesFromDropServer instead of GetFiles.
  • Split scripts into meaningful chunks (targets ) – You really want to have parts of a script that perform a specific task to be in a separate target. This will give you several benefits. First, the name of the target will give a bit more information about the purpose of the code that is contained within a target. For example, if you encounter a target named CopyFilesToOutputDir you will not even have to look at what is contained in the target itself – it is self-explanatory. In addition to that you will be able to change the flow of your script more easily. Once you have all building blocks – your targets – you can easily change their execution order without having to rearrange huge chunks of code. Another thing that comes to mind as a benefit is the ability to state the flow of execution in the beginning of the scripts – see Show the flow of the script first.
  • Provide meaningful names to targets – Although having separate targets is great, having inappropriate names for targets might be confusing. Consider a target named Copy. If you encounter such target you will have to make a lot of assumptions and you will also have to go back an forth through the script to figure out all of the details. Consider using more descriptive names like CopyPreparedFilesToOutputDir.
  • Introduce properties – Consider introducing properties for all information that is subject to change and/or is used more that once in the script. If you take look at our first script you will notice that $(OutDir)\PreparedSource appears in many places and defining a variable for this value will come in handy.
  • Provide meaningful names to properties and items – All guidelines that hold for target names hold for item and property names as well.
  • Extract semicolon-delimited item definitions – In case you have defined an item element using semicolons it might be a good idea to actually extract each individual item into a separate definition. Once again consider the script file above. More specifically the SourceCodeFiles item definition (line 7). Take a look at the Exclude attribute – it lists several items that should be excluded. Although this is valid, I believe, that this is actually hard to read and modify. What’s more is that the inconvenience of this approach increases with the number of specified items. You will be better of by defining each item separately which will enable you to add and remove items easily.
  • Check if required properties are set – this is one of the most important verification that you have to make. A simple one-line check for a required property can spare you hours of debugging. What will happen if the the SourceDir property of our sample script is not set? Probably nothing bad but the script will not function at all – there is no sense in running this script without supplying SourceDir. If that is the case make sure that this property is set.  Placing such checks will provide even more information to the user of your scripts – once he runs the script he will be notified of all required properties.
  • Check if properties have correct values – What if a script expects just two possible values for a given property. Let’s say WPF or Silverlight. If the script will not function property when another value is passed you should also make sure that this cannot happen. Imagine that someone tries to use your script with a value of Win8. If your script is not intended to be used with values different from WPF or Silverlight make sure that the user is notified  – you will save him a lot of suffering. This might not seem like a very important thing to check since the user can always open the script and check the valid property values. Well, imagine that you have 20 scripts that are invoked in sequence and that the user has changed script number 1 to pass Win8 down the chain of scripts. Is the user required to check all 20 scripts? No! Save him a lot of time by checking your inputs and print errors when something is not correct.
  • Write comments only when needed – If you name your properties, items, and targets you will not have to place additional comments to explain what your script does – it will be self explanatory. Comments are difficult to maintain so you should only use them when they are really needed. One such example is the portion of our script that invokes FileUpdate (Line 19). The code is really cryptic and even if you extract it into a separate target it might not be enough.  What this code does is to actually remove all properties that start with Scc from all project files – those properties control source control integration. In addition to that it also removed the so called TeamFoundationVersionControl section from all solution files – which also controls source control integration on the solution level. As you see, providing some additional information about this code might code in handy.
  • Print build information – MSBuild is pretty good at printing information about the various targets and tasks that are being executed so providing your own output messages might not be necessary but in some cases it might be a good idea. Usually you might want to print the values of the key properties that are used. Let’s say that a file is not copied to the directory that you have specified.  Is that because a property that you have specified is incorrect? Is it because some internal property was not generated correctly? In any case printing the values of your main properties will allow you to quickly spot problems.
  • Arrange targets so that they follow the execution flow of the scripts – Placing the target that will be executed last in the beginning of the script might not be a good idea. If you arrange your targets correctly it will be easier for people to understand the flow of your script and it will also enable them to navigate more efficiently. Imagine that you have to navigate to the section of the script where files get zipped. If you are working with a script that actually zips files you might expect that this is one of the last steps of the scripts and that you should be able locate the code in the bottom section of the file.
  • Show the flow and the high-level operations at the beginning of the script – This, I believe, is one of the most helpful hints that you can provide. It is like providing a “summary” of the script so that users can quickly grasp its purpose and flow. Luckily most XML-based build systems like MSBuild would allow you to do that in one way or another. For example, MSBuild allows you to specify a list of targets to be executed as a simple property where each target is separated with “;”. You can define one such property that lists the main targets of a scripts thus helping the user understand the flow and he execution steps of a script in a concise manner.  In addition to the obvious readability benefits, this approach will also allow you to easily change the execution order of the targets.

Now that we have defined several guidelines for creating MsBuild scripts let’s modify the original script and see how it goes.


The first thing that might strike you is that the updated script is about twice as long as the previous version. Nevertheless, it is about infinite times better. Simply reading the first couple of lines will give you enough information to understand the overall flow of the scripts. In a blink of an eye you will understand that this scripts has required properties and there are checks for those. You will understand that the source files are first copied to some directory and then source control information is removed. Finally, we see that the files are zipped. That is a wealth of information to gather from mere 5 lines of code. Compare that to the original script – you had to go through the whole script to try to figure out its flow and purpose. Even if you did that you could have missed the meaning of the FileUpdate operations.

I really hope that the tips that I have listed will help you write better builds scripts that are easy to understand, easy to use, and easy to modify.

Let me know if you agree or disagree with my guidelines. Can we improve our script even further?

Comments (0)