.NN#12: Pre-build Events and Errors
I’ve written about pre-build events before on another web site. These are a great tools for when you need to perform an action prior to a project being built; however, there can be a few gotchas to keep in mind.
Using Pre-build Events with Command Line DevEnv
As part of the article linked above I indicated that the pre and post build events would not fire if you executed the build using the devenv command line. This is true for VS.Net 2003; however, this is not true for VS.Net 2005 & 2008. Both 2005 and 2008 versions of Visual Studio .Net use MSBuild and the pre and post build events are stored in the project file as Build Tasks. This means that executing a build from the command line using devenv will fire the pre and post build events as expected for 2005 and 2008 projects.
Handling Multiple Commands and Errors in Pre-build
Multiple commands within a pre-build event may not report errors as you would expect. A pre build event is actually translated to a MSBuild Exec Task. It is important to note that a Exec task simply calls CMD.exe and passes the commands along. For a pre-build event this is anything that is in the pre-build event text box. The commands on separate lines are treated as separate commands. As the documentation indicates for the Exec task, the downside is that you don’t get any way to retrieve the results of these commands, so even though the output (if they have any) can be captured to your log file since it is written out during the build, but you can’t act on it.
Since the Exec task calls cmd.exe and passes the set of commands over only the exit code from the last command is really captured. This is very important if you have multiple commands in your pre-build event that MUST occur before your build works. Here is a way to reproduce what I’m talking about:
- Create a new C# console project.
- Click on the project properties and pull up the Build Events tab.
- In the Pre-build event command line text box add the following:
foo.cmd
- Attempt to build the project, note that the build will fail.
- Edit the Pre-build event command line text box to look as follows:
foo.cmd
dir
- Attempt to build and note that the build will succeed.
The foo.cmd isn’t magically working now, it is just that the dir command was successful so the ExitCode for the Exec Task is 0. This is reported back to MSBuild which believes the entire Task was successful and continues. If you check your build output window you’ll notice that the error that foo.cmd isn’t recognized did occur, it was just masked by the second successful command.
My recommendation if you want multiple commands in your pre-build event is to include logic in your command line that ensures everything runs correctly and returns an exit code of nonzero if something fails:
foo.cmd
IF %ERRORLEVEL% NEQ 0 GOTO ERROR
dir
IF %ERRORLEVEL% NEQ 0 GOTO ERROR
GOTO END
:ERROR
EXIT /B 1
:END
Note that the checking of the %ERRORLEVEL% occurs after each command. If there is an error it will get sent to the error label, otherwise it continues. After the last command and error check a GOTO END is included to bypass the error label and exit. Note that the Error Label performs a command EXIT /B 1. This allows cmd.exe to return an exit code from the batch file, which as long as it is a non-zero value will cause the Exec Task to be considered a failure.
When an error occurs it may be a little hard to figure out what happened, so make sure you take a look at the build output and don’t rely on the error list which will just indicate that the command exited with exitcode 1.
I could have put this in a batch file and called the batch file from the pre-build event task; however, if I need to substitute any of the “macros” or tokens that msbuild knows about I would have to pass those as parameters to the batch file.
I ran into this issue when we included a call to sqlcmd.exe in one of our pre-build events. We passed several TSQL commands to the sqlcmd utility and noticed that even though one of them was failing if the last command worked then the pre-build event was considered successful. Turns out that sqlcmd also has a /b switch that tells it to abort the batch on any error, which in turn causes the error to be reported immediately and not masked. Once we discovered this I started investigating what happens to normal commands within the pre-build event as well and noticed the same behavior.
Options
Like I said earlier, you can put all this into the pre-build event using logic to report any errors immediately. You could also manually modify the project file to include tasks that occur pre build that are more specific to your needs (which is beyond the scope of this post).
Until next time…