Extreme CI Part 3: The Visual Studio Macro.

How I run a personal CI build every time I build my code in Visual Studio.


This week we’ll finish the series by creating the Visual Studio macro that automatically runs the Personal CI process every time we build the code.

The Series

In this series I show you how to take continuous integration to the extreme by building a personal CI server that runs all your tests and adds a commit to source control every time you build in Visual Studio.

Setting Up the External Command in Visual Studio

Last week we created a batch file to commit and push our changes to the CI server. Now we need to execute that batch file from within Visual Studio. We’ll do it using an external command. To set up the external command in Visual Studio:

  1. Select Tools –> External Tools… from within Visual Studio. I am using Visual Studio 2008, for 2010, it may be a bit different.
  2. When the “External Tools” dialog shows, click “Add” and fill in the following:
    • Title: Commit and Push to Personal CI
    • Command: $(SolutionDir)\..\CommitAndPushToPersonalCI.bat
    • Initial directory: $(SolutionDir)\..\
    • Use Output window: checked

When you are done, the dialog should look like this:


Let me clarify a few things.

Why did we use $(SolutionDir)\..\?

The $(SolutionDir) is the directory containing the current solution, so when I am specifying $(SolutionDir)\..\, I am specifying the parent directory of the solution’s directory. This is because I have the following folder structure.

  +-MyProject  <-- Root of Git Repository
    +-src      <-- Solution Location

I want to commit and push from the root of my repository so that anything I have added to the lib or any other folders will be commited. However, my solution is in the src folder, which is one level deeper than I want to commit from. You may need to adjust your settings based on your project structure.

What’s up with the Use Output window setting?

Checking the “Use Output window” option redirects the console output of the batch file to the output window in Visual Studio.

Ok, let’s move on.

Now your tools menu should contain the new command and look like this.


And when running your custom command, its results appear in the output window.


Now lets take the final step and create the Visual Studio macro that runs this every time we build.

Create the Visual Studio Macro

To create the macro, we need to open up the Macros IDE.

Select Tools –> Macros –> Macros IDE… from within Visual Studio, or press Alt+F11. The Macros IDE will launch.


Open the EnvironmentEvents module under the MyMacros project. It should look like the screenshot above. Take the following code and insert it after the automatically generated code, and before the End Module.

Dim BuildWasSuccessful As Boolean
Dim BuildingSolution As Boolean
Dim RestrictToSolutionName As String
Dim PersonalCICommandName As String

Private Sub BuildEvents_OnBuildBegin(ByVal Scope As EnvDTE.vsBuildScope, ByVal Action As EnvDTE.vsBuildAction) Handles BuildEvents.OnBuildBegin

    'Initialize variables when the build starts.
    BuildWasSuccessful = True
    BuildingSolution = True

    'Only commit to personal CI for the specified solution name.
    RestrictToSolutionName = "MyProject"

    'Name of the external Personal CI command.
    PersonalCICommandName = "Tools.ExternalCommand3"

End Sub

Private Sub BuildEvents_OnBuildDone(ByVal Scope As EnvDTE.vsBuildScope, ByVal Action As EnvDTE.vsBuildAction) Handles BuildEvents.OnBuildDone

    'Don't run it if we just did a clean, since its not a build.
    If Action = vsBuildAction.vsBuildActionClean Then
        BuildingSolution = False
        Exit Sub
    End If

    'Only run autocommit if the build succeeded.
    If BuildWasSuccessful And DTE.Solution.FullName.Contains(RestrictToSolutionName) Then
        'Run the command to autocommit to git.
    End If

    BuildingSolution = False

End Sub

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
        'Set the build to failed.
        BuildWasSuccessful = False

        'The build failed...cancel any further builds.
    End If

    'Only commit project level builds if we are not building the whole solution
    If Success = True And BuildingSolution = False And DTE.Solution.FullName.Contains(RestrictToSolutionName) Then
        'Run the command to autocommit to git.
    End If

End Sub

To get it to work with your project, you need to change two variables: RestrictToSolutionName and PersonalCICommandName.


This value is used to restrict the personal CI process to one solution. If it executes on a solution that is not set up properly, it will fail. If you want to use this script for multiple solutions, you will need to change it. If you do, send me a copy and I’ll update this post and give you the credit.


This is the name of the ExternalCommand you created in visual studio. It is tricky to get the right name because it depends upon the number of external commands you have. It does not use the name you gave it. Pity.

The command names are in the form “Tools.ExternalCommandN” where N is a number. One way to determine the name is to look at your Tools menu and see what position that command is in. In the case of our example, it is the third custom tool in the tools menu, so it is “Tools.ExternalCommand3”. If that doesn’t work, you can create a custom toolbar and add the external commands to it one at a time. When you view the toolbar, you will see the real name of the command and can determine which is yours.

One More Thing

Once you set those properties and save the script, try building your project. It should automatically commit the code and push it up to the CI repository.

Now all you have to do it hook up a CI Server to it. I leave that as an exercise to you. It is beyond the scope of this article. But, you’re a good developer. You’ve setup a CI server before, haven’t you?


I pray this series has helped you tighten up your feedback loop so that when tests fail, you know…fast. My challenge to you is, how can we tighten it up even further? I am open to suggestions.


This entry was posted in .NET, Agile, Continuous Integration, Testing, Visual Studio and tagged , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

2 Trackbacks

Post a Comment

Your email is never published nor shared. Required fields are marked *

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>