An Automatic Build Number Incrementer for Visual Studio .NET
Environment: Visual Studio .NET
Introduction
Each of us who migrate to VS.NET encounters a few bumps in the road. One bump that caught me off guard was that my macros no longer worked.
My existing build number incrementing macro was a combination of bits and pieces of code scavenged from countless authors over the years. Although my build numbering system is not complicated, and does not require features found in similar macros, it will provide a solid foundation to start from.
Goals
These are my requirements for automating build numbers:
- On each release build, automatically increment a build number contained in a separate file that will be included into my project.
- Automatically track build date and number in a log file.
Code Discussion
My first task was to find a replacement for the .Application_BeforeBuildStart(). event handler used in VS 6. The designers of VS.NET provide us with four easy-to-use build events:
- OnBuildBegin
- OnBuildDone
- OnBuildProjConfigBegin
- OnBuildProjConfigDone
The naming convention used for the event handlers is somewhat misleading. We don't want to use OnBuildBegin because it is called one time, even if we are building multiple configurations (release, debug, and so forth), making it hard to increment the build number for release builds only. OnBuildProjConfigBegin is called for each configuration being built, and supplies a ProjectConfig string containing the name of the project configuration used (release, debug, and so on).
Most of the work is done in the OnBuildProjConfigBegin event handler with the aid of two helper macros:
- WriteToLogFile
- WriteToOutputBuildPane
Both helper methods could be folded into the OnBuildProjConfigBegin event handler, or removed if not required.
Code
' ------------------------------------ ' OnBuildProjConfigBegin event handler ' ------------------------------------ Private Sub BuildEvents_OnBuildProjConfigBegin( ByVal Project As String, ByVal ProjectConfig As String, ByVal Platform As String, ByVal SolutionConfig As String) Handles BuildEvents.OnBuildProjConfigBegin ' abort if build type is debug If InStr(1, ProjectConfig, "Debug", 1) Then Exit Sub ' get ver filename Dim res_filename As String res_filename = DTE.Solution.FullName res_filename = Path.ChangeExtension(res_filename, ".ver") ' open VERSION FILE and increment build number Dim msg_text As String If File.Exists(res_filename) Then Dim line As String Try Dim sr As StreamReader = New StreamReader(res_filename) line = sr.ReadLine() sr.Close() Catch ex As Exception Module1.WriteToOutputBuildPane(vbCrLf & _ "Version file read failed : " & ex.Message & vbCrLf) End Try line = Right(line, line.Length - 18) Try Dim sw As StreamWriter = File.CreateText(res_filename) sw.WriteLine("#define BUILD_NUM {0}", line + 1) sw.Close() Catch ex As Exception Module1.WriteToOutputBuildPane(vbCrLf & _ "Version file write failed : " & ex.Message & vbCrLf) End Try msg_text = "Build number : " & line + 1 & ", " & Now Module1.WriteToOutputBuildPane(vbCrLf & msg_text & vbCrLf) Module1.WriteToLogFile(msg_text) Else Try Dim sw As StreamWriter = File.CreateText(res_filename) sw.WriteLine("#define BUILD_NUM 1") sw.Close() Catch ex As Exception Module1.WriteToOutputBuildPane(vbCrLf & _ "Version file write failed : " & ex.Message & vbCrLf) End Try msg_text = "Build number : 1, " & Now Module1.WriteToOutputBuildPane(vbCrLf & msg_text & vbCrLf) Module1.WriteToLogFile(msg_text) End If End Sub ' ---------------------------------- ' write text message to a log file ' ---------------------------------- Sub WriteToLogFile(ByVal msg_text As String) Dim log_filename As String log_filename = DTE.Solution.FullName log_filename = Path.ChangeExtension(log_filename, ".log.txt") Try Dim sw As StreamWriter = File.AppendText(log_filename) sw.WriteLine(msg_text) sw.Close() Catch ex As Exception MsgBox("Log file write failed : " & ex.Message) End Try End Sub ' ---------------------------------------------------------------- ' write a text message to the build pane of the output tool window ' ---------------------------------------------------------------- Sub WriteToOutputBuildPane(ByVal msg_text As String) ' Create a tool window handle for the Output window. Dim win As Window = DTE.Windows.Item(EnvDTE.Constants. _ qvsWindowKindOutput) ' Create handles to the Output window and its build pane. Dim OW As OutputWindow = win.Object Dim OWp As OutputWindowPane OWp = OW.OutputWindowPanes.Item("Build") ' Add a line of text to the output pane. OWp.OutputString(msg_text & vbCrLf) End Sub
Integration
Note! If you do not have a macro project, create one before continuing.
You need a macro project opened and displayed in the VS Macros IDE.

Click here for a larger image.
- Each macro project has an EnvironmentEvents page. Double-click to open the page.
- Select BuildEvents from the class name dropdown list.
- Select OnBuildProjConfigBegin from the method name dropdown list.
- Add two imports statements to the list of name spaces to use.
The .System. namespace is required for exceptions and .SystemIO. is for the stream reader and writer classes. - Add the working code to the OnBuildProjConfigBegin method.
- Double click to open the Module1 page.
Module1 is a default page name the IDE uses. If you use a different name, be sure to resolve namespace issues in the OnBuildProjConfigBegin method. - Add the two helper methods.
- Add the .System and .SystemIO. imports statements to the list of namespaces to use.

Click here for a larger image.
Sample Output
The following samples are the result of building a small MFC dialog based application.
Contents of the version file: (MfcApp.ver)
#define BUILD_NUM 2Contents of the output build pane: (batch - rebuild all)
Build number : 2, 7/16/2003 4:07:35 PM
------ Rebuild All started: Project: MfcApp, Configuration:
Release Win32 ------
Deleting intermediate files and output files for project 'MfcApp',
configuration 'Release|Win32'.
Compiling...
stdafx.cpp
Compiling...
MfcAppDlg.cpp
MfcApp.cpp
Generating Code...
Compiling resources...
Linking...
LINK : warning LNK4089: all references to 'OLEAUT32.dll'
discarded by /OPT:REF
Build log was saved at "file://d:\MfcApp\Release\BuildLog.htm"
MfcApp - 0 error(s), 1 warning(s)
------ Rebuild All started: Project: MfcApp,
Configuration: Debug Win32 ------
Deleting intermediate files and output files for project 'MfcApp',
configuration 'Debug|Win32'.
Compiling...
stdafx.cpp
Compiling...
MfcAppDlg.cpp
MfcApp.cpp
Generating Code...
Compiling resources...
Linking...
Build log was saved at "file://d:\MfcApp\Debug\BuildLog.htm"
MfcApp - 0 error(s), 0 warning(s)
---------------------- Done ----------------------
Rebuild All: 2 succeeded, 0 failed, 0 skipped
Contents of the log file : (MfcApp.log.txt)
Build number : 1, 7/16/2003 4:07:35 PM Build number : 2, 7/16/2003 4:19:07 PM
Conclusions
The process of converting macros from VS 6 to VS.NET was both challenging and rewarding. I was somewhat surprised how nicely the DTE automation object model, VB.NET, and the .NET framework worked together.

Comments
You can do this in more easy way
Posted by RIANON on 05/29/2008 07:27amI use VS2008, and I hacve tryed to use your code and foun I think more easy way: ' ------------------------------------ ' OnBuildProjConfigBegin event handler ' ------------------------------------ 'Private Sub BuildEvents_OnBuildProjConfigBegin(ByVal Project As String, ByVal ProjectConfig As String, ByVal Platform As String, ByVal SolutionConfig As String) Handles BuildEvents.OnBuildProjConfigBegin ' ' abort if build type is debug ' If InStr(1, ProjectConfig, "Debug", 1) Then Exit Sub ' 'Find our project ' Dim prjcts As Projects ' Dim prj As Project ' Dim i As Integer ' Dim j As Integer ' Dim s As String ' prjcts = DTE.Solution.Projects ' For i = 1 To prjcts.Count ' prj = prjcts.Item(i) ' If prj.UniqueName = Project Then ' 'Start setting of project properties we need ' For j = 1 To prj.Properties.Count ' Try ' s = "Index: " & j & " Name: " & prj.Properties.Item(j).Name & " Value: " & prj.Properties.Item(j).Value.ToString() ' Logger.WriteToOutputBuildPane(s) ' Catch ex As Exception ' s = "Index: " & j & " Name: " & prj.Properties.Item(j).Name & " Err: " & ex.Message ' Logger.WriteToOutputBuildPane(s) ' End Try ' Next j ' End If ' 'Set assembly version ' prj.Properties.Item(37).Value = "100.100.123.334" ' Next i 'End Sub COPY COMMENTED CODE and uncomment it, you can easy set any properties, I whanted to add new properties but I do not know how to do this((( Thank you... I was need only changing file version - it is simple)))Replynot good with multiproject solutions
Posted by Legacy on 07/23/2003 12:00amOriginally posted by: Valerio
I've just tested it with my solution which contains over 10 projects: when I am building one project, whole solution build number and log is changing.
May be better to read/update not
> log_filename = DTE.Solution.FullName
but something applicable for project?
I've just fixed it to the:
log_filename = Path.GetDirectoryName(DTE.Solution.FullName) + "\" + Project
and pass additional Project parameter to the WriteToLogFile:
Sub WriteToLogFile(ByVal Project As String, ByVal msg_text As String)
and now it's more applicable, what's you think?
Reply