Click here to Skip to main content
15,880,392 members
Articles / DevOps / TFS

Visual Studio Add Ins - In Depth

Rate me:
Please Sign up or sign in to vote.
4.96/5 (17 votes)
5 Sep 2013CPOL15 min read 67.7K   1.1K   44   10
A more in depth look at Visual Studio Add Ins with a fully worked example.

Introduction 

Visual Studio add ins are a very quick and easy way to extend the functionality of Visual Studio. They aren't as cool as their big brother, Visual Studio packages and you get a lot more control with packages but they do provide a quick and easy way to get your functionality out there. Creating an add in is simple using the wizard, just make a few selections and Visual Studio sets up the solution and adds an example button to the tools menu, you just fill in the blanks to execute your code.

I think that there are however a few important subtleties and concepts that are not made clear and that you should be aware of when creating an add in and want to provide a smooth and problem free user experience. Things like, UI not being created, UI not being updated or being created multiple times in the same menu. I came up against these things when creating the Compare to Branch extension for Team Foundation Server and wanted to share my experience here so that others don't have to go through the same head scratching I did. There is a fully worked example in the code that uses all the elements discussed here, hopefully this will provide a template so others can just focus on implementing functionality.

Contents

 

Compare to Branch Extension for TFS

Compare to Branch is an add in I wrote for Visual Studio 2010/2012 and TFS 2010/2012 that lets you easily compare files and folders across branches in Visual Studio. Just right click on an item in the source control explorer and a list of branch related items appears in a popup allowing you to easily compare that item across branches. I wrote it to try and help me better visualise the differences between a growing number of released and supported versions of a project I am working on at the moment. The ability to compare across branches was always available to TFS users by using the compare dialog but the number of button clicks to do it was too much I thought. Compare to Branch reduces the process to just 2 mouse clicks.

Get it from the Visual Studio Gallery here.


Image 1

A Note About the Example Code

The example code is written in VS 2010 but will work fine is VS2012 as well. I have included the project user files as they contain debug settings you will need (e.g. what version of devenv.exe to run on debug). If these don't match your system or you need to change them then you can in the project settings, debug tab.

Each add in project also contains a file named - ForTesting.addin. These xml files need to be copied to the add in directly of the version of Visual Studio you want to debug. e.g. %MYDOCUMENTS%\Visual Studio 2010\Addins. You also need to update the <Assembly> node in the addin files to point to the addin dll your project is building and the <Version> node to reference the correct version of Visual Studio you are targeting.

When starting a new instance of Visual Studio to debug your addin I find it is best to rename the extension of these files before starting Visual Studio. Then undo the rename and open the solution. This prevents Visual Studio taking out a lock on your debug dlls and preventing you from rebuilding them.

I have not tested this code against Visual Studio 2008 and lower.

Anatomy of a Wizard Created Add In

Fire up Visual Studio and create a new project of type 'Visual Studio Add-in' (under the Other Project Types->Extensibility). Click through the wizard but be sure to set it to create an example button under the tools menu and load your add in on start up.

Image 2

When Visual Studio starts up it examines the set of folders for .AddIn files. The contents of this file tells Visual Studio that there is an add in to load and what to do with this add in. The folder hierarchy VS uses can be seen in the options dialog.

Image 3


The wizard created project puts an .AddIn file called 'MyAddin- For Testing.AddIn' in %VSMYDOCUMENTS%\Addins (%MYDOCUMENTS%\Visual Studio 2010\Addins on my PC) and this is the addin file that Visual Studio will parse when we try to debug our add in.

Hit F5 and note that the project is set to start another instance of Visual Studio on debugging. This instance starts up, reads the For Testing.AddIn file, loads our debug assembly and we see our new button created in the Tools menu.


Image 4

Debugging the code we can see how the UI is created in the OnConnection method of the Connect class. We can update that code to tweak the UI as we wish and there are a few functions we need to implement ourselves. An Exec function that is called when the add in is invoked and a QueryStatus function that sets the UI status of our button. It seems that is all there is left to do but there are a few important things to understand first.

Temporary and Permanent UI

Lets look at the .AddIn xml file in a little more detail.

XML
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
    <HostApplication>
        <Name>Microsoft Visual Studio</Name>
        <Version>10.0</Version>
    </HostApplication>
    <Addin>
        <FriendlyName>FindInSolutionExplorer</FriendlyName>
        <Description>Easily find files in the solution explorer</Description>
        <Assembly>C:\Users\Ham\Documents\Visual Studio 2010\Projects\FindInSolutionExplorer\FindInSolutionExplorer\bin\FindInSolutionExplorer.dll</Assembly>
        <FullClassName>FindInSolutionExplorer.Connect</FullClassName>
        <LoadBehavior>5</LoadBehavior>
        <CommandPreload>1</CommandPreload>
        <CommandLineSafe>1</CommandLineSafe>
    </Addin>
</Extensibility>

Most of settings should be self explanatory. See this msdn article for more info. The file tells Visual Studio where to load our add in assembly from and the class name and namespace of our connect class. The important settings in my opinion are LoadBehaviour and CommandPreLoad.

LoadBehaviour specifies whether we want the add in to load when Visual Studio starts up. If it is set to 0 then the user will have to manually load the addin from the Add-in Manager dialog (available from the tools menu) before they want to use it.

CommandPreLoad greatly affects the behavior of how our UI is created. If it is set to 0 then the UI is created every time the add in loads and deleted every time the add in is unloaded or Visual Studio is shut down. In this sense the UI is temporary.

If it is set to 1 then Visual Studio creates and persists the UI the first time it encounters the .addin file and runs the OnConnection method. All subsequent starts of Visual Studio will not run the add in OnConnection method and recreate or update the UI. In this sense the UI is considered permanent. The idea behind this is to optimise the start up time of Visual Studio. If CommandPreLoad is true then LoadBehaviour becomes largely irrelevant, it can be used to control whether other add in dependant resources are loaded/created but not when the UI is created.

This distinction between permanent and temporary UI is very important and greatly affects how you code and manage your add in. Lets look at the two methods in more detail with a few examples.

Temporary UI

The example solution contains a project demonstrating temporary UI. Note that CommandPreLoad = 0 in the .addin file. The most important bits of the code are

C#
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
    applicationObject = (DTE2)application;
    addInInstance = (AddIn)addInInst;
 
    switch (connectMode)
    {
        case ext_ConnectMode.ext_cm_UISetup:
            // We should never get here, this is temporary UI
            break;
 
        case ext_ConnectMode.ext_cm_Startup:
            // The add-in was marked to load on startup
            AddTemporaryUI();
            break;
 
        case ext_ConnectMode.ext_cm_AfterStartup:
            // The add-in was loaded by hand after startup using the Add-In Manager
            // Initialize it in the same way that when is loaded on startup
            AddTemporaryUI();
            break;
    }
}
 
/// <summary>
/// Setup the temporary UI for this addin
/// </summary>
private void AddTemporaryUI()
{
    object[] contextUIGuids = new object[] { };
    Commands2 commands = (Commands2)applicationObject.Commands;
    try
    {
        tempCommand = commands.Item(
            string.Format("{0}.{1}", addInInstance.ProgID, MY_COMMAND_NAME), -1);
    }
    catch
    {
        // command doesn't exist
    }
 
    if (tempCommand == null)
    {
        tempCommand = commands.AddNamedCommand2(
            addInInstance,
            MY_COMMAND_NAME,
            MY_COMMAND_NAME,
            MY_COMMAND_NAME,
            true,
            59,
            ref contextUIGuids,
            (int)(vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled));
    }
 
    CommandBars cmdBars = (CommandBars)applicationObject.CommandBars;
 
    if (tempCommand != null)
    {
        // Add a button to the code window context window
        CommandBar codeWindowCommandBar = cmdBars["Code Window"];
        if (codeWindowCommandBar != null)
        {
            tempCodeWindowCommandBarButton = (CommandBarButton)tempCommand.AddControl(
                codeWindowCommandBar, codeWindowCommandBar.Controls.Count + 1);
            tempCodeWindowCommandBarButton.Caption = "Example button in the code window menu";  
        }
 
        // Add a popup and some buttons to the solution explorer
        CommandBar solutionExplorerItemCommandBar = cmdBars["Item"];
        if (solutionExplorerItemCommandBar != null)
        {
            tempCommandBarPopup = (CommandBarPopup)solutionExplorerItemCommandBar.Controls.Add(
                MsoControlType.msoControlPopup, System.Type.Missing, System.Type.Missing,
                solutionExplorerItemCommandBar.Controls.Count + 1, true);
            tempCommandBarPopup.Caption = "Example Popup";
 
            tempSECommandBarButton1 = (CommandBarButton)tempCommand.AddControl(
                tempCommandBarPopup.CommandBar, tempCommandBarPopup.Controls.Count + 1);
            tempSECommandBarButton1.Caption = "Example button 1 in the solution explorer item menu";
 
            tempSECommandBarButton2 = (CommandBarButton)tempCommand.AddControl(
                tempCommandBarPopup.CommandBar, tempCommandBarPopup.Controls.Count + 1);
            tempSECommandBarButton2.Caption = "Example button 2 in the solution explorer item menu";
        }
    }
}
 
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
    switch (disconnectMode)
    {
        case ext_DisconnectMode.ext_dm_HostShutdown:
        case ext_DisconnectMode.ext_dm_UserClosed:
            if ((tempSECommandBarButton1 != null))
            {
                tempSECommandBarButton1.Delete(true);
            }
            if ((tempSECommandBarButton2 != null))
            {
                tempSECommandBarButton2.Delete(true);
            }
            if ((tempCodeWindowCommandBarButton != null))
            {
                tempCodeWindowCommandBarButton.Delete(true);
            }
            if (tempCommandBarPopup != null)
            {
                tempCommandBarPopup.Delete();
            }
            if (tempCommand != null)
            {
                tempCommand.Delete();
            }
            break;
    }
}     

The most important things to note here are

  • The OnConnection method will only ever recieve a value of ext_cm_Startup or ext_cm_AfterStartup depending on whether the add in was loaded manually by the user via the addin manager dialog or automatically on startup. The ext_cm_UISetup case will never happen, that is only relevant for permanent UI ( CommandPreLoad = 1)
  • Have a look at AddTemporaryUI to see how to add UI elements to different parts of Visual Studio.
  • The OnDisconnection method explicitly deletes all the controls and commands we created.

It is possible to see the temporary nature of the UI. If you hit F5 and start the project the add in will load and the UI will be created. If you then manually unload the add in via the add in manager dialog, OnDisconnection is called and the UI gets deleted and disappears from Visual Studio. Reload the addin and AddTemporaryUI is called and the UI recreated. If you fail properly dispose of the the controls and commands in OnDisconnection then when you next load the addin using the add in manager you will see multiple instances of UI being created, very unwelcome.

Image 5

Permanent UI 

If you go down the route of permanent UI then your .addin file will look something like this

XML
<LoadBehavior>1</LoadBehavior>
<CommandPreload>1</CommandPreload> 

If you debug the PermanentUI example project, you will see OnConnection gets called with connectMode equal to ext_cm_UISetup. Here we setup our permanent UI, a button on the tools menu and a new button on the standard Visual Studio toolbar. Note, we don't dispose of anything in OnDisconnection. The idea here is the commands and UI are created once during connection and that is it. The next time Visual Studio loads it reads the commands and associated UI from a store and saves the initialisation cost of creating them. The code is much more simple than the temporary case.

C#
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
    applicationObject = (DTE2)application;
    addInInstance = (AddIn)addInInst;
 
    MessageBox.Show(string.Format("OnConnection with connectMode: {0}", connectMode));
    switch (connectMode)
    {
        case ext_ConnectMode.ext_cm_UISetup:
            AddPermanentUI();
            break;
 
        case ext_ConnectMode.ext_cm_Startup:
            // Do nothing, permanent UI
            break;
 
        case ext_ConnectMode.ext_cm_AfterStartup:
            // Do nothing, permanent UI
            break;
    }
}
 
/// <summary>
/// Setup the permanent UI for this addin
/// </summary>
private void AddPermanentUI()
{
    object[] contextUIGuids = new object[] { };
    Commands2 commands = (Commands2)applicationObject.Commands;
    try
    {
        permCommand = commands.Item(
            string.Format("{0}.{1}", addInInstance.ProgID, MY_COMMAND_NAME), -1);
        MessageBox.Show("Permanent UI Example: Command already exists");
    }
    catch
    {
        // command doesn't exist                
    }
 
    if (permCommand == null)
    {
        MessageBox.Show("Permanent UI Example: Creating command");
        permCommand = commands.AddNamedCommand2(
            addInInstance,
            MY_COMMAND_NAME,
            MY_COMMAND_NAME,
            MY_COMMAND_NAME,
            true,
            59,
            ref contextUIGuids,
            (int)(vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled));
    }
 
    CommandBars cmdBars = (CommandBars)applicationObject.CommandBars;
 
    if (permCommand != null)
    {
        MessageBox.Show("Permanent UI Example: Creating UI");
 
        // Add button to the tools menu
        CommandBar menuBarCommandBar = ((CommandBars)applicationObject.CommandBars)["MenuBar"];
        CommandBarControl toolsControl = menuBarCommandBar.Controls["Tools"];
        CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
        CommandBarButton toolsMenuCommandBarButton = (CommandBarButton)permCommand.AddControl(
                    toolsPopup.CommandBar, 1);
        toolsMenuCommandBarButton.Caption = "Example button on the tools menu";
 
        // Add button to the standard tool bar
        CommandBar standardCommandBar = cmdBars["Standard"];
        CommandBarButton standardCommandBarButton = (CommandBarButton)permCommand.AddControl(
                    standardCommandBar, standardCommandBar.Controls.Count + 1);
        standardCommandBarButton.Style = MsoButtonStyle.msoButtonIcon;
    }
}

But i'm sure you thinking, 'every time I debug my project I can see OnConnection being called with a value ext_cm_UISetup and my UI getting created. This is because the default debugger settings are to start devenv with the command line /resetaddin PermanentUI.Connect. The /resetaddin switch destroys all data persisted for this addin and forces it to be recreated during OnConnection. Try starting Visual Studio normally or removing this command from the debugger settings. There are message box popups telling you what is happening so you can see when and how the commands and UI are getting created. You will see how AddPermanentUI is not called if Visual Studio is started normally. 

This leads to an interesting point. How do you update the UI in your addin. If you released a new version how could you be sure running it was going to update the UI as you wish. The solution is to reset your addin before you install the new version and the easiest way to do that is by calling

VBScript
devenv.exe /ResetAddIn Namespace.Connect /Command File.Exit

from your installer. I would also recommend that the install process starts and closes an instance of Visual Studio at the end of  the install process to force-ably create and persist the UI. This will reduce the instance of errors around setting up your add in for the first time.

My preference is for temporary UI. It is much more intuitive from a development point of view. If you need to use permanent UI for performance reasons I recommend you test all scenarios around upgrading etc thoroughly. There are further little gotchas surrounding the use of permanent UI if you create command bars and I would point you in the direction of mztools, a great resource for these types of issues.

Controls and Commands

You can see from the example so far that the way add ins guide you to add UI and functionality to Visual Studio is by creating commands and then from those commands generating one or more controls. The controls are then tied to the commands and Visual Studio handles all the plumbing from that point on.

We can , if needed, bypass this command system and directly create the controls ourselves. This might be useful where the command based Exec and QueryStatus system doesn't fit your needs and you need an extra level of control e.g. perhaps you need to dynamically generate UI. The code would look something like this

C#
CommandBarButton newControl = (CommandBarButton)codeWindowCommandBar.Controls.Add(
                MsoControlType.msoControlButton, Type.Missing, Type.Missing, 1, Type.Missing);
newControl.Caption = "button created with no command";
newControl.Click += new _CommandBarButtonEvents_ClickEventHandler(newControl_Click);
 
void newControl_Click(CommandBarButton Ctrl, ref bool CancelDefault)
{
    // Your code here
} 

Be sure to clean up any controls you create in the OnDisconnection method if using the TemporaryUI approach. Actually this approach will fail using the Permanent UI model. This is because only controls linked to commands get persisted so these controls you create manually will be lost the next time Visual Studio runs. Another reason to stick with Temporary UI.

Icons

There are two ways to configure the icon used in your GUI. You can pick an icon from the Microsoft Office System and specify the integer of this icon in AddNamedCommand2. Note the number 59, this corresponds to the smiley face icon. For information about how to find the ID numbers for standard icons, see Listing Button Faces in the Command Bar for the Microsoft Office System on the MSDN Web site.

C#
commands.AddNamedCommand2(
        addInInstance,
     MY_COMMAND_NAME,
     MY_COMMAND_NAME,
     MY_COMMAND_NAME,
     true,
     59,
     ref contextUIGuids,
     (int)(vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled)); 

Or using the same method, you can pass in an object of type Bitmap from your assembly resources. There is an example of this in the fully worked example below.

C#
commands.AddNamedCommand2(
      addInInstance,
      MY_COMMAND_NAME,
      MY_COMMAND_NAME,
      MY_COMMAND_NAME,
      false,
      Resources.MyBitmap,
      ref contextUIGuids,
      (int)(vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled));

Logging Errors from your Add In

Logging is going to be essential if you are going to diagnose problems with your add in on a users computer. To this end I wrote the AddInLogger class, which gives the ability to log messages and errors to the event log or alternatively (if you are debugging say), to the output window in Visual Studio.

Example usage is as follows.

C#
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
    applicationObject = (DTE)application;
    addInInstance = (AddIn)addInInst;
 
    bool logtoOutputWindow = System.Diagnostics.Debugger.IsAttached;
    logger = new AddInLogger(applicationObject, "MyAddIn", logtoOutputWindow);
 
    logger.LogMessage(string.Format("OnConnection() called with connectMode: '{0}'", connectMode));
    try
    {
        // Your initialisation code here
    }
    catch (Exception ex)
    {
        logger.LogError(ex.ToString());
    }
}

Here we create an instance of the logger class. If we are debugging, all logs are written to a new pane named "MyAddIn" in the output window. Otherwise are logs are written to the event log under an event source named "MyAddIn".

Note: If you are logging to the event log then you have to make sure an event source of name "MyAddIn" exists. You can't rely on your application code creating this if it doesn't exist as it is likely it won't have the permissions to do so. You can create the event source manually using powershell but best practice is to create it in your installer (as we will see next)

VBScript
New-EventLog -LogName Application -Source MyAddin 

The AddInLogger code looks like this and should be self explanatory

C#
// <summary>
/// Helper class to do all the logging
/// Assumes an event source of name 'eventSource' exists
/// Run the following powershell snippet to set it up if debugging 'New-EventLog -LogName Application -Source My_Addin'
/// 'Remove-EventLog -Source MyAddin'
/// On real installations the event source is created by the installer
/// </summary>
internal class AddInLogger
{
    private string eventSource = string.Empty;
    private OutputWindowPane outputWindowPane = null;
    private bool logtoOutputWindow = true;
 
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="_applicationObject"></param>
    /// <param name="_eventSource"></param>
    /// <param name="_logtoOutputWindow"></param>
    internal AddInLogger(DTE _applicationObject, string _eventSource, bool _logtoOutputWindow)
    {
        try
        {
            logtoOutputWindow = _logtoOutputWindow;
            eventSource = _eventSource;
            if (logtoOutputWindow)
            {
                // Create an output pane for this addin
                Window window = _applicationObject.Windows.Item(Constants.vsWindowKindOutput);
                OutputWindow outputWindow = (OutputWindow)window.Object;
                outputWindowPane = null;
 
                for (int i = 1; i <= outputWindow.OutputWindowPanes.Count; ++i)  
                {
                    if (outputWindow.OutputWindowPanes.Item(i).Name.Equals(eventSource, 
                        StringComparison.CurrentCultureIgnoreCase))
                    {
                        outputWindowPane = outputWindow.OutputWindowPanes.Item(i);
                        break;
                    }
                }
 
                if (outputWindowPane == null)
                    outputWindowPane = outputWindow.OutputWindowPanes.Add(eventSource);
            }
        }
        catch
        {
            // Swallow it, never let errors in logging stop the add in
        }
    }
 
    /// <summary>
    /// Log trace
    /// </summary>
    /// <param name="message"></param>
    internal void LogMessage(string message)
    {
        try
        {
            if (logtoOutputWindow)
            {
                outputWindowPane.OutputString(string.Format("{0}\n", message));
            }
            else
            {                
                EventLog.WriteEntry(eventSource, message);
            }
        }
        catch
        {
            // Swallow, never let errors in logging stop the add in
        }
    }
 
    /// <summary>
    /// Log an error
    /// </summary>
    /// <param name="message"></param>
    internal void LogError(string message)
    {
        try
        {
            if (logtoOutputWindow)
            {
                outputWindowPane.OutputString(string.Format("Error: {0}\n", message));
            }
            else
            {
                EventLog.WriteEntry(eventSource, message, EventLogEntryType.Error);
            }
        }
        catch
        {
            // Swallow, never let errors in logging stop the add in
        }
    }
} 

Writing an Installer for your Add In

The old setup and deployment projects are gone from Visual Studio 2012 and onwards so nowadays we are forced to use WIX. It sometimes feels like black magic finding the right markup to do what you want but it does start to make sense after a while (download the latest version of WIX from the homepage)

The markup below creates a very simple installer for our add in and can be used as a template for your add in installers if you wish. The steps are

  • Check the versions of Visual Studio you are supporting are present
  • Create the event log source for your logging
  • Deploy the add in dll(s) and addin file to the addin directory of a particluar version of Visual Studio
  • Use XmlUtil to update the Visual Studio version number in the addin file

I won't go into too much detail about the WIX markup, it should be quite self explanatory. Be sure to replace all GUIDS with your own or you might run into some problems. The installer also supports automatic upgrades. When you want to release a new version just update the product version number and create a new product GUID.

If you copy and paste then please be sure to re generate all the GUIDS or you might find yourself in trouble.

XML
<?xml version="1.0" encoding="UTF-8"?>
<!-- 
When we release a new version we must
1) Update assemlby version number
2) Update product version number here
3) Create a new Product Id
-->
 
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Id="NEW-GUID-HERE"
           Version="1.0.0.0"
           Name="My Add In"
           Language="1033"
           Manufacturer="Company Name"
           UpgradeCode="NEW-GUID-HERE">
    <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
 
    <Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
 
    <!-- Specify your licence here -->
    <!--<WixVariable Id="WixUILicenseRtf" Value="$(var.ProjectDir)\licence.rtf" />-->
    
    <!-- Use a Minimal UI -->
    <UI Id="UserInterface">
    </UI>
    <UIRef Id="WixUI_Minimal" />
 
    <MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes"/>
 
    <!-- Determine what versions of Visual Studio are installed-->
    <Property Id="VS2010INSTALLATIONFOLDER" Secure="yes">
      <RegistrySearch Id="VS2010InstallationFolderRegistrySearchId" Type="raw"
                      Root="HKLM" Key="SOFTWARE\Microsoft\VisualStudio\10.0" 
                      Name="InstallDir">        
      </RegistrySearch>
    </Property>
    <Property Id="VS2012INSTALLATIONFOLDER" Secure="yes">
      <RegistrySearch Id="VS2012InstallationFolderRegistrySearchId" Type="raw"
                      Root="HKLM" Key="SOFTWARE\Microsoft\VisualStudio\11.0" 
                      Name="InstallDir">        
      </RegistrySearch>
    </Property>
 
    <Condition Message="This add-in requires Visual Studio 2010 or 2012 to be already installed">
      VS2012INSTALLATIONFOLDER OR VS2010INSTALLATIONFOLDER
    </Condition>
 
    <!-- Create the event log source -->
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Component Id="EventLog" Guid="NEW-GUID-HERE">
        <Util:EventSource xmlns:Util="http://schemas.microsoft.com/wix/UtilExtension"
              Log="Application" Name="MyAddIn" 
                          EventMessageFile="[NETFRAMEWORK20INSTALLROOTDIR]EventLogMessages.dll"/>
      </Component>
 
      <Directory Id="CommonAppDataFolder">
        <Directory Id="MicrosoftDirId" Name="Microsoft">
          <Directory Id="VisualStudioDirId" Name="VisualStudio">
 
            <!-- VS 2010 files -->
            <Directory Id="Dir100Id" Name="10.0">
              <Directory Id="Dir100AddInsId" Name="AddIns">
                <Component Id="MyAddIn2010Resources" Guid="NEW-GUID-HERE">
                  <File Id="MyAddInDll2010" Name="MyAddIn.dll" Vital="yes" 
                        Source="..\MyAddIn\bin\MyAddIn.dll"/>
                  <File Id="MyAddInAddIn2010" Name="MyAddIn.Addin" Vital="yes" 
                        Source="..\MyAddIn\MyAddIn.Addin"/>
                  <!-- Update the .addin file to specify version 10.0 of Visual Studio -->
                  <Util:XmlFile xmlns:Util="http://schemas.microsoft.com/wix/UtilExtension" 
                                Id="VS2010SetVersion" Action="setValue" File="[#MyAddInAddIn2010]" 
                                ElementPath="/Extensibility/HostApplication/Version" 
                                Value="10.0" Sequence="1" />
                </Component>
              </Directory>
            </Directory>
 
            <!-- VS 2012 files -->
            <Directory Id="Dir110Id" Name="11.0">
              <Directory Id="Dir110AddInsId" Name="AddIns">
                <Component Id="MyAddIn2012Resources" Guid="NEW-GUID-HERE">
                  <File Id="MyAddInDll2012" Name="MyAddIn.dll" Vital="yes" 
                        Source="..\bin\MyAddIn.dll"/>
                  <File Id="MyAddInAddIn2012" Name="MyAddIn.Addin" Vital="yes"
                        Source="..\MyAddIn.Addin"/>
                  <!-- Update the .addin file to specify version 11.0 of Visual Studio -->
                  <Util:XmlFile xmlns:Util="http://schemas.microsoft.com/wix/UtilExtension" 
                                Id="VS2012SetVersion" Action="setValue" File="[#MyAddInAddIn2012]" 
                                ElementPath="/Extensibility/HostApplication/Version" 
                                Value="11.0" Sequence="1" />
                </Component>
              </Directory>
            </Directory>
 
          </Directory>
        </Directory>
      </Directory>
    </Directory>
 
 
    <Feature Id="ProductFeature" Title="My Add In" Level="1" >
      <ComponentRef Id="EventLog"/>
 
      <!-- If VS2010 present then install add in to 2010-->
      <Feature Id="VS2010" Title="Visual Studio 2010"
               Description="Installs the add-in for Visual Studio 2010" Level="1" AllowAdvertise="no">
        <Condition Level="0">NOT VS2010INSTALLATIONFOLDER</Condition>
        <ComponentRef Id="MyAddIn2010Resources"/>
      </Feature>
 
      <!-- If VS2012 present then install add in to 2012-->
      <Feature Id="VS2012" Title="Visual Studio 2012"
         Description="Installs the add-in for Visual Studio 2012" Level="1" AllowAdvertise="no">
        <Condition Level="0">NOT VS2012INSTALLATIONFOLDER</Condition>
        <ComponentRef Id="MyAddIn2012Resources"/>
      </Feature>
 
    </Feature>
  </Product>
</Wix>

A Complete Example - The Find in Solution Explorer Add In

The example code contains a full example that implements all the ideas above. It adds a 'Find In Solution Button' to the code window context menu. When clicked the file is located and selected in the solution explorer. Its a quick and easy way to locate where files are in the solution explorer. I'm sure I use to have this functionality from another extension in an old dev environment but I can't remember what extension provided it so I decided to write my own. You just right click in a file and click the button, the file should then selected and shown in the solution explorer.

Image 6

I hope example can be used as a template to quickly create other addins. You need to just

  • Update OnConnection to create the UI you require
  • Update the Exec and QueryStatus methods for your functionality
  • Update the installer to suit your needs 

You can find the code to search the solution explorer in the example project. It's actually a tricky little problem, much more difficult than it should be. The code is not 100% production ready but serves it purpose here and works *most* of the time.

Wrapping Up

Before you dive into developing add ins you need to ask yourself whether add ins are the right framwork to develop your Visual Studio extension. The other option is Visual Sudio Packages. Visual Studio Packages are the big brother to Add Ins. They give you much much greater control over Visual Studio than add ins can but this is at the cost of complexity. There is also provide much better support of Packages in the Visual Studio Gallery and via nuget.

I think if I had the choice again I would write Compare To Branch, as a package. It got quite complicated to control the UI the way I wanted to (because it is dynamic) and packages would let me better optimise the way it works.

I still think add ins have their place. If you need to something quick and simple then add ins are the way to go. All you need is a dll and an xml file copied to right location on your computer and you are off.

History

  • 01/09/2013 - First Draft

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
I like to code and work mainly in .NET although i'm moving into other areas nowadays.
When i'm not coding I like to ride my bike

TFS Productivity Pack

Blog

Comments and Discussions

 
GeneralMy vote of 5 Pin
Assil3-Mar-16 0:50
professionalAssil3-Mar-16 0:50 
Suggestionif you get an invalid parameter error from AddNamedCommand2() Pin
jsprenkl19-Oct-15 8:42
jsprenkl19-Oct-15 8:42 
QuestionCould you explain the difference between addin and vs packages? Pin
leiyangge29-Dec-14 19:52
leiyangge29-Dec-14 19:52 
AnswerRe: Could you explain the difference between addin and vs packages? Pin
Shining Dragon1-Jan-15 20:05
Shining Dragon1-Jan-15 20:05 
SuggestionGood Pin
SajeeshCheviry21-Nov-13 5:31
SajeeshCheviry21-Nov-13 5:31 
GeneralRe: Good Pin
Shining Dragon21-Nov-13 6:33
Shining Dragon21-Nov-13 6:33 
GeneralWell written article! Pin
Espen Harlinn24-Oct-13 5:56
professionalEspen Harlinn24-Oct-13 5:56 
GeneralRe: Well written article! Pin
Shining Dragon27-Oct-13 8:02
Shining Dragon27-Oct-13 8:02 
QuestionAddin instalation Pin
kiquenet.com19-Sep-13 20:54
professionalkiquenet.com19-Sep-13 20:54 
AnswerRe: Addin instalation Pin
Shining Dragon19-Sep-13 21:24
Shining Dragon19-Sep-13 21:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.