Visual Studio Hacks
Articles General

Customize Your Project Build Process

by javery on March 1, 2008

While the Visual Studio build process is familiar for all .NET developers, not everyone realizes the build customization capabilities that can be accomplished with just a little work. You can easily put together tasks that occur before, after, and even during a build.

NOTE: With the release of Visual Studio 2005, a new build tool—called MSBuild—is used within the Visual Studio environment. This tool provides many build process tasks and is similar to NAnt in capability. For pre- and post build operations for automated builds I would suggest looking at MSBuild tasks rather than the project level pre- and post-build commands.

Pre- and Post-Build Event Commands

In nonweb projects, you can create pre- and post-build event commands. As their names suggest, these are commands that you can customize to run just prior to or after a build. You can even control whether the post-build event executes at all depending on the outcome of the build. In the previous version of Visual Studio these commands could only be found in C# projects, but Visual Studio 2005 now exposes them for VB.Net as well. For C# projects you can find the settings on their own tab in the Project Properties pages, while for Visual Basic projects they are found via a Build Events button on the Compile tab in the My Project properties window.

Project build events for C# projects

Project build events for C# projects

Project build events for VB.NET projects.

Project build events for VB.Net projects Note the build Events button in the bottom right corner of the window.

Just type in the command you want to occur for each event type. You can click on an Edit Event button under each event type to help “build” up your command using a set of tokens or “Macros”. These variables help provide some shortcuts to including pathnames, project output locations, and other information into your event commands. Note that the variables are sensitive to the type of build you are compiling, so if you switch to a release build, the variables referencing the output directory will be correctly pointed at the \bin\release directory (or wherever you have set up the output directory to be).

Macro examples

Name Description
$(TargetPath) The full directory path to the output directory, including the project output filename. Example: c:\CSharpWinFormApp\bin\debug\CSharpWinFormApp.exe
$(TargetDir) The full directory path to the output directory. Example: c:\CSharpWinFormApp\bin\debug\
$(ProjectDir) The full directory path to where the project file exists. Example: c:\CSharpWinFormApp\

The build event command lines are simply command-line operations that will be executed at the specified time. The commands are saved in the project file and are Targets configured in MSBuild to be used by the build process to execute the commands. Note that Visual Studio 2005 does not copy these commands out into batch files during a build like its predecessor did.

Note that a failing build event will be shown in the build output and listed in the Build Error task list along with any other build errors. A failing pre-build event will cause the build to stop, while a failing post-build event will still mark the build as a failure even if the build of the code succeeded without errors. You can choose to have the post-build event occur every build, only if the build succeeded, or when the project output (the resulting .dll or .exe) actually changes.

A great example of using a custom post-build event is when you have multiple configuration files. Whe n you add an app.config file to your project, it will automatically be copied to the output directory of the project and renamed to match up with the project output file (e.g., CSharpWinFormApp.exe.config), but the IDE will not do the same with other custom configuration files. What if your project had multiple .config files to break up the settings? For example, you have app.config to store application configuration but then you also have an EndUser.config file to store user-specific settings that you didn’t want to mix with the application settings. By default, this configuration file would not be copied to your output directory, and the application would receive an exception if it went looking for the file during execution. To get around this, a custom post-build event can copy the EndUser.config file to the output directory every time a build succeeds.

In the Build Events, choose to run a Post-Build event when a successful build occurs and add a Post-Build event command line of:

copy "$(ProjectDir)EndUser.config" "$(TargetDir)"

Note the use of quotes around the variables and commands—the quotes are there because there may be spaces in the file and pathnames. Also, the variables that generate pathnames will append an ending slash automatically. After a successful build of this project the current version of the EndUser.config file in the project directory will be copied to the output directory.

Copying files after a build is just one example of how these build events can be useful. More complex operations can be performed as well. For example, a Windows script or executable could be run when a build completes to kick off a set of test scripts against the newly built output. For automated builds in Visual Studio 2005 these types of operations would be configured as MSBuild Tasks, but within the IDE the pre- and post-build commands can make a life a lot easier.

Handling Build Events

Some of the limitations of the custom pre- and post-build command events are that they are available for only nonweb projects and are only project specific. They don’t give you the flexibility to deal with building a multiproject solution or provide a way of handling more complex build outcomes other than a successful build, a failed build, or a changed project output. For more advanced handling of build events, we can turn to the Visual Studio Macro Environment. By using the IDE build events we can configure some operations to occur before, during or after a build no matter what project we are building or what type of project it is.

To hook into the IDE build events, all we need to do is create some simple macros. Open up your Macro IDE (from Tools ? Macros ? Macro IDE or Alt-F11). If you haven’t used the Macro IDE before, the first thing you’ll notice is that it resembles the regular VS.NET IDE, except the Macro IDE is used solely for the creation, editing, and compiling of macros that can be used within your Visual Studio IDE.

Open up the MyMacros project and see if it already contains an EnvironmentEvents module. If it doesn’t, go ahead and copy the one from the Samples directory into your MyMacros project. The code looks like this:


Option Strict Off
Option Explicit Off
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Public Module EnvironmentEvents
#Region "Automatically generated code, do not modify"
'Automatically generated code, do not modify
'Event Sources Begin
<system.contextstaticattribute()>Public WithEvents
	DTEEvents As EnvDTE.DTEEvents
<system.contextstaticattribute()>Public WithEvents
	DocumentEvents As EnvDTE.DocumentEvents
<system.contextstaticattribute()>Public WithEvents
	WindowEvents As EnvDTE.WindowEvents
<system.contextstaticattribute()>Public WithEvents
	TaskListEvents As EnvDTE.TaskListEvents
<system.contextstaticattribute()>Public WithEvents
	FindEvents As EnvDTE.FindEvents
<system.contextstaticattribute()>Public WithEvents
	OutputWindowEvents As EnvDTE.OutputWindowEvents
<system.contextstaticattribute()>Public WithEvents
	SelectionEvents As EnvDTE.SelectionEvents
<system.contextstaticattribute()>Public WithEvents
	BuildEvents As EnvDTE.BuildEvents
<system.contextstaticattribute()>Public WithEvents
	SolutionEvents As EnvDTE.SolutionEvents
<system.contextstaticattribute()>Public WithEvents
	SolutionItemsEvents As EnvDTE.ProjectItemsEvents
<system.contextstaticattribute()>Public WithEvents
	MiscFilesEvents As EnvDTE.ProjectItemsEvents
<system.contextstaticattribute()>Public WithEvents
	DebuggerEvents As EnvDTE.DebuggerEvents
<system.contextstaticattribute()>Public WithEvents
	ProjectsEvents As EnvDTE.ProjectsEvents
<system.contextstaticattribute()>Public WithEvents
	TextDocumentKeyPressEvents As EnvDTE80.TextDocumentKeyPressEvents
<system.contextstaticattribute()>Public WithEvents
	CodeModelEvents As EnvDTE80.CodeModelEvents
<system.contextstaticattribute()>Public WithEvents
	DebuggerProcessEvents As EnvDTE80.DebuggerProcessEvents
<system.contextstaticattribute()>Public WithEvents
	DebuggerExpressionEvaluationEvents
	As EnvDTE80.DebuggerExpressionEvaluationEvents
'Event Sources End
'End of automatically generated code
#End Region
End Module

This sets up the types of IDE events you can hook into. Now if you open the EnvironmentEvents module in your MyMacros folder, you can select the BuildEvents from the Class Name drop-down (the left drop-down box above the code). Then from the right Method Name drop-down, you can select one of four build events to hook into:

OnBuildBegin Will fire when any build operation is fired from the IDE. It fires only once for a full solution or multiproject build operation.
OnBuildDone Will fire when a build operation completes. This event fires only once for a full solution or multiproject build operation.
OnBuildProjConfigBegin Will fire when a project build begins. This event is used to catch each project build event within a solution or multiproject build operation.
OnBuildProjConfigDone Will fire when a project build completes. This event is used to catch the completion of each project build within a solution or multiproject build operation.

NOTE: These events fire even if build errors or warnings occur, and the default behavior of Visual Studio is to build all projects in a solution even if projects earlier in the build have failed.

For an example, you can add the capability to stop the build process when a project fails and display a message box in the IDE to note when the build process is complete (in case someone isn’t paying attention to the Build output).

Canceling a failed build

The normal behavior of Visual Studio is to build every project in a solution, even if one or more of those projects fails. If you have a half-dozen projects in your solution, then this can sometimes take a little while. It is much faster if the build quits when any of the projects fails. By handling a build event, you can ensure that.

Start by selecting the OnBuildProjConfigDone event from the Method Name drop-down above the code. The Macro IDE will automatically generate the method signature to hook the event:

Private Sub BuildEvents_OnBuildProjConfigDone( _
ByVal Project As String, _
ByVal ProjectConfig As String, _
ByVal Platform As String, ByVal SolutionConfig As String, _
ByVal Success As Boolean) _
Handles BuildEvents.OnBuildProjConfigDone
End Sub

Since this event will fire whenev er a project completes a build, you can easily stop the rest of the build from occurring by using the ExecuteCommand method of the DTE object to send a Build.Cancel command to the IDE. The DTE object is a reference to the Visual Studio IDE, and using DTE.ExecuteCommand method to cancel the build is the same as hitting Ctrl-Break during a build or typing Build.Cancel into the IDE command window. Using the Success parameter of the event signature, you can determine whether you want to stop the build. The code looks like this:

Private Sub BuildEvents_OnBuildProjConfigDone( _
ByVal Project As String, ByVal ProjectConfig As String, _
ByVal Platform As String, ByVal SolutionConfig As String, _
ByVal Success As Boolean) _
Handles BuildEvents.OnBuildProjConfigDone
If Success = False Then
'The build failed...cancel any further builds.
DTE.ExecuteCommand("Build.Cancel")
End If
End Sub
Alert the user on a successful build

Now, the idea was to capture the end of the build process to alert the user that the build was complete. For that you hook into the OnBuildDone event the same way you did for the project build complete. This time, you’ll use a standard Windows message box to display an informative alert:

Private Sub BuildEvents_OnBuildDone( _
ByVal Scope As EnvDTE.vsBuildScope, _
ByVal Action As EnvDTE.vsBuildAction) _
Handles BuildEvents.OnBuildDone
'Alert that we finished building!
System.Windows.Forms.MessageBox.Show("Build is complete!")
End Sub

These are some pretty simple examples, but you have the power of the .NET Framework in your hands for any of these events so you are limited only by what you can come up with to handle. You can share these with other developers by just sending them the code to include in their own macro project or take it a step further and create a Visual Studio add-in that hooks these events and distribute it that way. If you take a look at the other IDE events you can tap into, you will understand why the Visual Studio IDE is so extensible.

Something to consider when you are implementing custom build events is that they are handled within the IDE only. Build events implemented using the extensibility of the IDE (macros or add-ins) will not fire if the build is run from the command line and they are executed for all project built with the IDE. However, pre- and post-build event commands will execute only for the project they are added to and even if the build is being run via the command line with devenv.exe /build or with MSBuild. This could be important if you want to automate your build process on a build machine.

blog comments powered by Disqus

Previous post: Managing Font Sizes

Next post: Optimize Visual Studio for Presentations