One of the most commonly asked questions that I get is “How can I extend the build process for my solution?” In fact I was asked this soo much that 4 years ago (wow I’ve been dealing with MSBuild for far too long:) ) I wrote a blog post on Use MSBuild to build Solution files (no longer recommended technique) and that post to this day is one of the most frequently visited ones. Well in the 4+ years that have passed I’m glad to say that things have changed. The problem is that almost no one knows about it. Hopefully this blog post will be the start to that changing.

What’s under the covers

In all versions of Visual Studio, the solution file is not an MSBuild file. Which sucks for many of us. Essentially the solution build was like a black box.

Blackbox

Even in VS2010 it still kinda is, but there is a hook for us to extend it built in. Let me explain what happens when you build a solution using msbuild.exe. When you build a solution using msbuild.exe the solution file is converted to an MSBuild file in memory then that MSBuild file is used to build the solution itself. When you build from the command line here is the rough outline of the MSBuild project that gets built.



  
  
  

  
  
  
  
  
                    
  

OK here I have left out the targets and properties because they are irrelevant to this discussion. Take a look at these import statements. There are two pairs of import statements ones that are imported before the build is defined and the imports which are after the build definition. Instead of looking at the first import statement lets look at the second one, because I think its more compelling and its easier to demo.

This statement is pretty simple, if a file with the name of before.ExtendSolutionBuild.sln.targets exists in the same directory as the ExtendSolutionBuild.sln file then it will be imported in at the top of the build file. Also there is a corresponding entry for after.ExtendSolutionBuild.sln.targets. In stead of getting into all the nitty gritty of why two exists let me just boil it down. You should place properties/items inside of the before targets file, and inside of the after .targets file you should place targets but its OK to put properties/items here as well. The most important thing to keep a note of is that you cannot place anything inside of the before file which relies on any properties/items defined in the build because they will not be available. Because of this most times you will just need an after targets file. So in my case I will create a file named after.ExtendSolutionBuild.sln.targets, this follows the pattern after.{SolutionFile}.sln.targets where {SolutionFile} is the name of the solution file mine happens to be ExtendSolutionBuild. Every solution has 4 targets that will be defined.

4 Targets on Every Solution file

  • Build
  • Rebuild
  • Clean
  • Publish

So let’s say that I want to extend the build file and inject two targets; GenerateCode and RunCodeAnalysis. Obviously I want the GenerateCode target to run before anything is built and the RunCodeAnalysis to be run after everything is built. So in my after.ExtendSolutionBuild.sln.targets file I place the following content.



  
  
    
  

  
    
  
  

You can see here that I am using BeforeTargets=”Build” to make sure that the GenerateCode target is run before the Bulid target and also the RunCodeAnalysis target uses AfterTargets=”Build”. So now let’s see what happens when I build my solution from the command line using msbuild.exe.

ExtendSolution01

From the figure above you can see that the GenerateCode target was executed before the build process started and then the RunCodeAnalysis target was executed after the build. With MSBuild 3.5 and earlier this was simply not possible, and you would have had to write another build file to do these kinds of things for you. If you are building solution files from the command then you might be interested in the technique. I would be interested to hear you feedback and to see how you guys would use this feature.

ImportBefore/ImportAfter

Now let’s talk briefly about the two that we skipped. If you want an MSBuild file to be imported for every solution that is build using msbuild.exe for a given machine then you can place that file in one of the two folders below.

  • $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore\
    • %ProgramFiles32%\MSBuild\4.0\SolutionFile\ImportBefore\
  • $(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter\
    • %ProgramFiles32%\MSBuild\4.0\SolutionFile\ImportAfter\

So if you place any file in either of those folders (which don’t exist by default) then they will automatically be imported for every solution file which builds on that machine using msbuild.exe. Typically you do not run into many times where you want to do this, but the times that you do then it is critical and really hard to work around without something like this. This is useful if you have a build machine and you want to perform a set of actions for every build.

Resources

Sayed Ibrahim Hashimi | @SayedIHashimi


Comment Section




Comments are closed.