Introduction
Very often I've found myself unable to distinguish between several versions of my smaller programs, because I rarely bother to change version numbers every time I make a tiny change in code. Visual Basic 6 had the option to automatically increment the build/revision version number at each compile, and I thought this can be a good thing sometimes... However, I couldn't find any really satisfactory working solution for this.
Background
What I did find was a slew of tricks (including pre/post-build commands etc.) that were sometimes more cumbersome than changing the version manually, and one or two add-in ideas that made the changes programmatically. One of them was called VAV and it was a pre-alpha SourceForge project; my source code uses the name VAVE, which I admit I blatantly ripped off by adding 'Enhanced' to its name, 'VisualStudio Assembly Versionner'.
However, what that code did was not at all to my liking. The base idea was perfect - hooking into commands from the environment. But the actual work was being done in strange and unusual ways, while Visual Studio's object model was there all along to handle the details.
Using the Code
The actual code is concentrated in the add-in class, which Visual Studio names by default 'Connect
'. The class implements IDTExtensibility2
and IDTCommandTarget
, which allow it to be loaded as an add-in and respond to calls from the IDE, respectively.
First and foremost, the primary job of the add-in is incrementing the revision number on each build. But since I'm the kind of person that provides options even for a 'Hello world' application, this couldn't be it. It would be relatively simple to provide a command which increments the build number (third part of the version number) and resets the revision, and the same thing could be done for the minor version, the second part of the version information.
Incrementing the revision is done automatically by hooking into certain commands provided by Visual Studio; first and foremost, the Build.BuildSolution
and Build.RebuildSolution
, and Build.BuildOnlyProject
and Build.RebuildOnlyProject
commands are quite obvious. The Build.BuildSelection
and Build.RebuildSelection
build the selected project, i.e., the project currently being highlighted in the Solution Explorer (or to which the current open file belongs) - this is the command issued by the 'Build <project name>' buttons and item menus. Furthermore, the revision could also be incremented when starting a debug session, since that does a build too (Debug.Start
and Debug.StartWithoutDebugging
), but only if the developer actually wants that.
Hooking into a command is done by holding a reference to that command (while it isn't really necessary right now, I thought I could use it, but more on that later) and to an associated CommandEvents
object:
Private _cmdBuildSolution As Command
Private WithEvents cmdBuildSolutionEvents As CommandEvents
When the class connects to the IDE, namely in the OnConnection
sub, the objects are set to their respective instances:
_cmdBuildSolution = commands.Item("Build.BuildSolution")
cmdBuildSolutionEvents = _applicationObject.Events.CommandEvents(_cmdBuildSolution.Guid, _
_cmdBuildSolution.ID)
and the CommandEvent
's two events are being pointed to our two handlers:
AddHandler cmdBuildSolutionEvents.BeforeExecute, _
New _dispCommandEvents_BeforeExecuteEventHandler(AddressOf OnBeforeBuildAny)
AddHandler cmdBuildSolutionEvents.AfterExecute, _
New _dispCommandEvents_AfterExecuteEventHandler(AddressOf OnAfterBuild)
Once all commands are pointed the right way, issuing that command in the IDE executes our OnBeforeBuildAny
sub, then its actual command, then our OnAfterBuild
sub. I used the OnAfterBuild
only to output messages to the output window, since outputting anything before there's any default pane in the output tool-window is slightly problematic. The OnBeforeBuildAny
simply accesses the solution object of the IDE, iterates through all the projects, and increments the version properties of each ('AssemblyVersion' and 'AssemblyFileVersion') - that simple. Of course, it does some extra checks and considers some extra options I threw in:
Private Sub Update(ByVal part As VersionParts)
If Not _enabled Then Exit Sub
Dim build as SolutionBuild = _applicationObject.Solution.SolutionBuild
If _release AndAlso build.ActiveConfiguration.Name <> "Release" Then
Exit Sub
End If
Dim pp As Project
_queuedMsg = ""
For j As Integer = 1 To _applicationObject.Solution.Projects.Count
Try
pp = _applicationObject.Solution.Projects.Item(j)
Dim v1 As New Version(pp.Properties.Item("AssemblyVersion").Value.ToString)
Dim v2 As New Version(pp.Properties.Item("AssemblyFileVersion").Value.ToString)
Dim vv1 As Version, vv2 As Version
Select Case part
Case VersionParts.Build
vv1 = New Version(v1.Major, v1.Minor, v1.Build + 1, 0)
vv2 = New Version(v2.Major, v2.Minor, v2.Build + 1, 0)
Case VersionParts.Minor
vv1 = New Version(v1.Major, v1.Minor + 1, 0, 0)
vv2 = New Version(v2.Major, v2.Minor + 1, 0, 0)
Case VersionParts.Major
vv1 = New Version(v1.Major + 1, 0, 0, 0)
vv2 = New Version(v2.Major + 1, 0, 0, 0)
Case VersionParts.None
Exit Sub
Case Else
vv1 = New Version(v1.Major, v1.Minor, v1.Build, v1.Revision + 1)
vv2 = New Version(v2.Major, v2.Minor, v2.Build, v2.Revision + 1)
End Select
pp.Properties.Item("AssemblyVersion").Value = vv1.ToString
_queuedMsg &= " ---> VAVE: Updated assembly version for project " & pp.Name & _
" to " & vv1.ToString & " ---" & vbCrLf
pp.Properties.Item("AssemblyFileVersion").Value = vv2.ToString
_queuedMsg &= " ---> VAVE: Updated output file version for project " & pp.Name & _
" to " & vv2.ToString & " ---" & vbCrLf
pp.Save()
Catch
_queuedMsg &= " ---> VAVE: Failed to increment version for one
---> of the projects. ---" & vbCrLf
End Try
Next
End Sub
As I said, I couldn't help myself and threw in a couple of extra options; there are two extra commands the add-in handles, 'Increment Build' and 'Increment Minor', a dialog that helps set options, and the actual options: enable/disable all functionality, increment revision only on build or on build and start with or without debugging, and only increment when the selected configuration is Release.
Points of Interest
Visual Studio boasts an extended extensibility model, but the documentation for it is terribly scant. These few lines of code took much trial and error, Reflection and tracing, but I finally took it to a point where I'm satisfied with what it does.
There are several things that might not please everybody, of course: since the BeforeExecute
event happens before compiling, the version gets incremented even if the build fails for some reason (well, there really isn't a way around that, is there, if we want the build result's properties changed, unless we want to modify the result assemblies directly?); adding the commands to the Tools menu is actually done quite clumsily, but I was sick of inconsistent behaviors - if anyone can get it to work like it's supposed to, give me a pointer; when incrementing the revision, all the projects in the current solution are processed, which admittedly isn't very nice; and of course, if anyone enhances this thing with more options/functionality, post here and I'll incorporate changes whenever possible.
Also note that compiling and testing the add-in from the source might need adjusting the build settings, since after building, the results should be copied to one of Visual Studio's add-in locations and the IDE started with the /resetaddin <addin name> switch. My post-build command looks like this:
xcopy "$(TargetPath)" "C:\Documents and Settings\[user]\My Documents\
Visual Studio 2005\Addins\" /Y &
xcopy "$(ProjectDir)VAVE.Addin" "C:\Documents and Settings\[user]\My Documents\
Visual Studio 2005\Addins\" /Y
History
- 2007-10-21: Initial draft.