Versioning Controlled Build

Last week I received a new laptop at work and since I hadn't upgraded to Visual Studio 2015 it was the prefect time to do so. After installing Visual Studio I reinstalled all my favorite extensions and tools. However one extension wasn't available, Versioning Controlled Build (it automates versioning of .NET projects). This extension is fairly old and dates back to 2004 and has been upgraded since to work with all Visual Studio versions, except for Visual Studio 2015. I viewed the comments in de CodeProject article and saw many requests for a 2015 version dating back to July 2015. The reason why the add-in probably wasn't updated is because add-ins have been deprecated in Visual Studio 2015. The new way is to create a VSPackage (VSIX extension file). Shoot, such a nice tool, how am I gone manage my project versions now? 

Searched for extensions in the Visual Studio gallery but all extensions I found got bad reviews or didn't seem to work as well as Versioning Controlled Build. I then remembered that the source code of Versioning Controlled Build is open source. I'm a freaking programmer aren't I? Why not keep using what I like by creating the extension myself. Nice project for the weekend!

Downloaded all the sources from codeproject.com and saw that it all dated back to the .NET 1.1/2.0 era. Man will this even work out? A few projects didn't load but it turned out I didn't need them. There was only 1 external reference and that project (Multitab Color Picker) was also written by Julijan Sribar and open sourced on codeproject. I don't know him but I like this guy! The projects turned out to build just fine so let's continue and write a VSIX extension ourselves.

New VSIX Project

Starting a VSIX package is simple. Open Visual Studio, create a new project and select Extensiblity | VSIX Package. I then copied all the files I needed into the new project, forms, resources, business logic etc. Tried to build it and a nice "Build succeeded" appeared in the status bar. All I had to do now was tie the logic to buttons in the main toolbar. That turned out to be the hardest part. I hadn't created an add-in or extensions for Visual Studio before. After some research and looking at some examples I understood that the way buttons are shown in the toobar is stored in the vsct file. This is an XML formatted file which describes how and where buttons should appear in the interface. Below is the code I ended up with. I'm adding a VCB menu button next to the Help button. In this menu is a group of 8 buttons that are tied to commands.

<?xml version="1.0" encoding="utf-8"?>
<CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <Extern href="stdidcmd.h" />
  <Extern href="vsshlids.h" />

  <Commands package="guidVcbCommandPackage">

    <Menus>
      <Menu guid="guidVcbCommandSet" id="TopLevelMenu" priority="0xFFFF" type="Menu">
        <Parent guid="guidSHLMainMenu" id="IDG_VS_MM_WINDOWHELP" />
        <Strings>
          <ButtonText>VCB</ButtonText>
        </Strings>
      </Menu>
    </Menus>

    <Groups>
      <Group guid="guidVcbCommandSet" id="TopLevelMenuGroup" priority="0x0600">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenu" />
      </Group>
    </Groups>

    <Buttons>
      <Button guid="guidVcbCommandSet" id="GuiCommandId" priority="0x0100" type="Button">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenuGroup" />
        <Icon guid="guidImages" id="bmpPicGui" />
        <CommandFlag>DefaultInvisible</CommandFlag>
        <CommandFlag>DynamicVisibility</CommandFlag>
        <Strings>
          <ButtonText>Versioning Controlled Build GUI</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidVcbCommandSet" id="BuildVersionsCommandId" priority="0x0101" type="Button">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenuGroup" />
        <Icon guid="guidImages" id="bmpPicBuild" />
        <CommandFlag>DefaultInvisible</CommandFlag>
        <CommandFlag>DynamicVisibility</CommandFlag>
        <Strings>
          <ButtonText>Build with Versions Updated</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidVcbCommandSet" id="RebuildVersionsCommandId" priority="0x0102" type="Button">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenuGroup" />
        <Icon guid="guidImages" id="bmpPicRebuild" />
        <CommandFlag>DefaultInvisible</CommandFlag>
        <CommandFlag>DynamicVisibility</CommandFlag>
        <Strings>
          <ButtonText>Rebuild with Versions Updated</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidVcbCommandSet" id="SaveUpdatedVersionsCommandId" priority="0x0103" type="Button">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenuGroup" />
        <Icon guid="guidImages" id="bmpPicSaveUpdated" />
        <CommandFlag>DefaultInvisible</CommandFlag>
        <CommandFlag>DynamicVisibility</CommandFlag>
        <Strings>
          <ButtonText>Save Updated Versions</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidVcbCommandSet" id="SaveVersionsCommandId" priority="0x0104" type="Button">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenuGroup" />
        <Icon guid="guidImages" id="bmpPicSave" />
        <CommandFlag>DefaultInvisible</CommandFlag>
        <CommandFlag>DynamicVisibility</CommandFlag>
        <Strings>
          <ButtonText>Export Versions to File...</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidVcbCommandSet" id="PrintVersionsCommandId" priority="0x0105" type="Button">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenuGroup" />
        <Icon guid="guidImages" id="bmpPicPrint" />
        <CommandFlag>DefaultInvisible</CommandFlag>
        <CommandFlag>DynamicVisibility</CommandFlag>
        <Strings>
          <ButtonText>Print Versions</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidVcbCommandSet" id="ConfigureCommandId" priority="0x0106" type="Button">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenuGroup" />
        <Icon guid="guidImages" id="bmpPicConfigure" />
        <Strings>
          <ButtonText>Configure</ButtonText>
        </Strings>
      </Button>
      <Button guid="guidVcbCommandSet" id="AboutCommandId" priority="0x0107" type="Button">
        <Parent guid="guidVcbCommandSet" id="TopLevelMenuGroup" />
        <Strings>
          <ButtonText>About</ButtonText>
        </Strings>
      </Button>
    </Buttons>

    <!--The bitmaps section is used to define the bitmaps that are used for the commands.-->
    <Bitmaps>

      <Bitmap guid="guidImages" href="Resources\GuiCommand.png" usedList="bmpPicGui, bmpPicBuild, bmpPicRebuild, bmpPicSaveUpdated, bmpPicSave, bmpPicPrint, bmpPicConfigure" />
    </Bitmaps>

  </Commands>

  <VisibilityConstraints>
    <VisibilityItem guid="guidVcbCommandSet" id="GuiCommandId" context="UICONTEXT_SolutionHasSingleProject" />
    <VisibilityItem guid="guidVcbCommandSet" id="GuiCommandId" context="UICONTEXT_SolutionHasMultipleProjects" />
    <VisibilityItem guid="guidVcbCommandSet" id="BuildVersionsCommandId" context="UICONTEXT_SolutionHasSingleProject" />
    <VisibilityItem guid="guidVcbCommandSet" id="BuildVersionsCommandId" context="UICONTEXT_SolutionHasMultipleProjects" />
    <VisibilityItem guid="guidVcbCommandSet" id="RebuildVersionsCommandId" context="UICONTEXT_SolutionHasSingleProject" />
    <VisibilityItem guid="guidVcbCommandSet" id="RebuildVersionsCommandId" context="UICONTEXT_SolutionHasMultipleProjects" />
    <VisibilityItem guid="guidVcbCommandSet" id="SaveUpdatedVersionsCommandId" context="UICONTEXT_SolutionHasSingleProject" />
    <VisibilityItem guid="guidVcbCommandSet" id="SaveUpdatedVersionsCommandId" context="UICONTEXT_SolutionHasMultipleProjects" />
    <VisibilityItem guid="guidVcbCommandSet" id="SaveVersionsCommandId" context="UICONTEXT_SolutionHasSingleProject" />
    <VisibilityItem guid="guidVcbCommandSet" id="SaveVersionsCommandId" context="UICONTEXT_SolutionHasMultipleProjects" />
    <VisibilityItem guid="guidVcbCommandSet" id="PrintVersionsCommandId" context="UICONTEXT_SolutionHasSingleProject" />
    <VisibilityItem guid="guidVcbCommandSet" id="PrintVersionsCommandId" context="UICONTEXT_SolutionHasMultipleProjects" />
  </VisibilityConstraints>

  <Symbols>

    <!-- This is the package guid. -->
    <GuidSymbol name="guidVcbCommandPackage" value="{69ed5368-af96-43e4-a44d-a0d32097a717}" />
    <GuidSymbol name="guidVcbCommandSet" value="{2291da24-92e5-4ea4-bdb7-72a9b5ac7d59}">
      <IDSymbol name="TopLevelMenu" value="0x0100" />
      <IDSymbol name="TopLevelMenuGroup" value="0x0200" />
      <IDSymbol name="GuiCommandId" value="0x0300" />
      <IDSymbol name="BuildVersionsCommandId" value="0x0301" />
      <IDSymbol name="RebuildVersionsCommandId" value="0x0302" />
      <IDSymbol name="SaveUpdatedVersionsCommandId" value="0x0303" />
      <IDSymbol name="SaveVersionsCommandId" value="0x0304" />
      <IDSymbol name="PrintVersionsCommandId" value="0x0305" />
      <IDSymbol name="ConfigureCommandId" value="0x0306" />
      <IDSymbol name="AboutCommandId" value="0x0307" />
    </GuidSymbol>

    <!-- Other Guids for the package -->
    <GuidSymbol name="guidImages" value="{7d45f4a0-6a92-45e6-9f30-77cde4426d1b}">
      <IDSymbol name="bmpPicGui" value="1" />
      <IDSymbol name="bmpPicBuild" value="2" />
      <IDSymbol name="bmpPicRebuild" value="3" />
      <IDSymbol name="bmpPicSaveUpdated" value="4" />
      <IDSymbol name="bmpPicSave" value="5" />
      <IDSymbol name="bmpPicPrint" value="6" />
      <IDSymbol name="bmpPicConfigure" value="7" />
    </GuidSymbol>

  </Symbols>
</CommandTable>

The buttons are connected to commands in C# files based on the values that are assigned to them in combination with a unique command set guid.

/// <summary>
/// Command menu group (command set GUID).
/// </summary>
public static readonly Guid CommandSet = new Guid("2291da24-92e5-4ea4-bdb7-72a9b5ac7d59");

public BaseCommand(Package package, int commandId, bool isDynamic = true)
{
  if (package == null)
  {
    throw new ArgumentNullException("package");
  }

  this.package = package;

  OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
  if (commandService != null)
  {
    var menuCommandID = new CommandID(CommandSet, commandId);
    var menuItem = new OleMenuCommand(this.MenuItemCallback, menuCommandID);
    if (isDynamic)
    {
      menuItem.BeforeQueryStatus += MenuItem_BeforeQueryStatus;
    }
    commandService.AddCommand(menuItem);
  }
}

Each command overrides the BaseCommand class and has it's own unique value. Below is an example of the AboutCommand that's connected to the About button.

internal sealed class AboutCommand : BaseCommand
{

  /// <summary>
  /// Initializes a new instance of the <see cref="AboutCommand"/> class.
  /// Adds our command handlers for menu (commands must exist in the command table file)
  /// </summary>
  /// <param name="package">Owner package, not null.</param>
  private AboutCommand(Package package) : base(package, 0x0307, false) { }

  /// <summary>
  /// Gets the instance of the command.
  /// </summary>
  public static AboutCommand Instance
  {
    get;
    private set;
  }

  /// <summary>
  /// Initializes the singleton instance of the command.
  /// </summary>
  /// <param name="package">Owner package, not null.</param>
  public static void Initialize(Package package)
  {
    Instance = new AboutCommand(package);
  }

  /// <summary>
  /// This function is the callback used to execute the command when the menu item is clicked.
  /// See the constructor to see how the menu item is associated with this function using
  /// OleMenuCommandService service and MenuCommand class.
  /// </summary>
  /// <param name="sender">Event sender.</param>
  /// <param name="e">Event args.</param>
  protected override void MenuItemCallback(object sender, EventArgs e)
  {
    base.PreMenuItemCallback();

    AboutBox.Show(new WindowAdapter(m_devEnvApplicationObject.MainWindow.HWnd));
  }
}

MenuItemCallback is the method called after the button has been clicked. After that it's all code by Julijan Sribar and he explained all his code in his excellent article on codeplex.com.

The end result of a few hours work is a working Visual Studio 2015 extension that does exactly as I was used to in Visual Studio 2010, 2012 and 2013. I posted the code on GitHub and submitted the extension in the Visual Studio gallery

Versioning Controlled Build