Home > Articles > Programming > Windows Programming

Creating Visual Studio .NET Add-Ins

  • Print
  • + Share This
Walk through the steps for creating and registering an add-in, and for adding functionality to make it useful. Author Paul Kimmel brings his real-world experience and insider knowledge to this discussion of the extensibility and customizability of the Visual Studio .NET environment.
This article is excerpted from Chapter 4, "Macros and Visual Studio Extensibility," of Visual Basic .NET Unleashed, by Paul Kimmel (Sams Publishing; ISBN 067232234x).
This chapter is from the book

This chapter is from the book

As you might have gathered by now, a lot of information is available in the general and project-neutral extensibility models. What are all of these automation objects for? They are there to help language extenders—people like you—create Add-Ins among other things.

Because the object model is so vast, you will have to rely heavily on the help files to begin scratching the surface of extensibility; more than likely, several tool vendors will develop in-house expertise, and a developer from one of these shops will write an entire book on this subject.

In this section we will look at the necessary, basic considerations for creating VS .NET add-ins and will create some simple add-ins. Of course any add-in could potentially be a complex application by itself—consider Visual SourceSafe.

Creating an Add-In Project

Macros extend the IDE by providing automated solutions that can be run in the IDE. The biggest difference between an add-in and a macro is that add-ins are compiled into a separate .DLL and macros are not. The tasks you automate with macros or add-ins can be identical. The reason you would choose an add-in over a macro is one of distribution. If you write an extension that you want to sell, and you want to prevent customers from pillaging your source code, you will create an add-in project; otherwise a macro project will suffice.

The essential ingredients necessary to create add-ins are a DLL or class library, a project, and a class that implements the IDTExtensibility2 interface. There are other interfaces you can implement that will make your add-in more useful, but IDTExtensibility2 is the primary interface that an add-in must implement.

Fortunately, there is an add-in wizard that automates the basic steps necessary to create an add-in. Granted, the add-in created by the wizard is a do-nothing add-in, but it will get you jump started. Follow the numbered steps listed following to create an add-in project that will compile and run.

  1. Start a new project by choosing File, New Project, Other Projects Extensibility Projects, Visual Studio.Net Add-In. Click OK. Click Next at the Welcome screen.

  2. On Page 1 of 6, select Create an Add-In Using Visual Basic. Click Next.

  3. Page 2 of 6 uses the defaults, which define the Macros IDE and Visual Studio .NET as hosts for the add-in. Click Next.

  4. On Page 3 of 6 name the add-in MyAddin and enter a description; for example, My First Add-In. Click Next.

  5. On Page 4 of 6 we want to indicate that the add-in can be invoked from the Tools menu, the add-in should load when the host loads, and the add-in should be available to all users. (Check all check boxes except My Add-In Will Never Put Up a Modal UI.) Click Next.

  6. On Page 5 of 6, check the check box if you want to add About box information. (If you do, modify the About box information accordingly.) Click Next.

  7. On Page 6 of 6, review the Summary information, and if it is correct, click Finish.

The wizard will create an add-in project—in our example named MyAddIn—and a Windows Installer project named MyAddInSetup. (Using the AddInNameSetup naming convention.) The add-in project will have references, an assemblyinfo.vb module, and a module named connect.vb that contains the add-in class, Connect. Connect implements the IDTExtensibility2 interface and, because we indicated that we wanted integration with the IDE's Tools menu, the IDTCommandTarget interface.

The add-in will compile and run as is, but it performs no real task at the present time. If you run the add-in from the IDE, it will compile and package the setup file MyAddInSetup.msi. (Creating the setup file takes a while, so be patient.) When the add-in project runs, it starts a new instance of the IDE with—in our case—the add-in added as a menu operation in the Tools menu. Go ahead and try it.


The add-in menu item may stop appearing in the Tools menu after you test the add-in to debug it. If this happens, close all instances of the IDE and double-click ReCreateCommands.reg in your Add-In directory. This step reloads the registry settings and the menu should reappear when you restart VS .NET.

Our vanilla add-in implements the IDTExtensibility2 and IDTCommandTarget interfaces resulting in the code in Listing 4.9.

Listing 4.9 The add-in module created by the Visual Studio .NET Add-In Wizard (repaginated to fit this page).

1: Imports Microsoft.Office.Core
2: Imports EnvDTE
3: Imports System.Runtime.InteropServices
5: #Region " Read me for Add-in installation and setup information. "
6: ' When run, the Add-in wizard prepared the registry for the Add-in.
7: ' At a later time, if the Add-in becomes unavailable for reasons such as:
8: '  1) You moved this project to a computer other than which it was 
originally created on.
9: '  2) You chose 'Yes' when presented with a message asking if you wish 
to remove the Add-in.
10: '  3) Registry corruption.
11: ' you will need to re-register the Add-in by building the 
MyAddinSetup project 
12: ' by right clicking the project in the Solution Explorer, then 
[ic:ccc]choosing install.
13: #End Region
15: <GuidAttribute("F61C62A7-A0F2-4660-87C7-67BCE5C0BF96"), _
16:  ProgIdAttribute("MyAddin.Connect")> _
17: Public Class Connect
19:  Implements IDTExtensibility2
20:  Implements IDTCommandTarget
22:  Dim applicationObject As EnvDTE.DTE
23:  Dim addInInstance As EnvDTE.AddIn
25:  Public Sub OnBeginShutdown(ByRef custom() As Object) _
26:   Implements IDTExtensibility2.OnBeginShutdown
28:  End Sub
30:  Public Sub OnAddInsUpdate(ByRef custom() As Object) _
31:   Implements IDTExtensibility2.OnAddInsUpdate
33:  End Sub
35:  Public Sub OnStartupComplete(ByRef custom() As Object) _
36:   Implements IDTExtensibility2.OnStartupComplete
38:  End Sub
40:  Public Sub OnDisconnection( _
41:   ByVal RemoveMode As ext_DisconnectMode, _
42:   ByRef custom() As Object) _
43:   Implements IDTExtensibility2.OnDisconnection
45:  End Sub
47:  Public Sub OnConnection(ByVal application As Object, _
48:   ByVal connectMode As ext_ConnectMode, _
49:   ByVal addInInst As Object, ByRef custom() As Object) _
50:   Implements IDTExtensibility2.OnConnection
52:   applicationObject = CType(application, EnvDTE.DTE)
53:   addInInstance = CType(addInInst, EnvDTE.AddIn)
54:   If connectMode = ext_ConnectMode.ext_cm_UISetup Then
55:    Dim objAddIn As AddIn = CType(addInInst, AddIn)
56:    Dim CommandObj As Command
58:    'IMPORTANT!
59:    'If your command no longer appears on the appropriate 
60:    ' command bar, you add a new or modify an existing command, 
61:    ' or if you would like to re-create the command, close 
62:    ' all instances of Visual Studio .NET and double click 
63:    ' the(file) 'ReCreateCommands.reg' in the folder 
64:    ' holding the source code to your Add-in.
65:    'IMPORTANT!
66:    Try
67:     CommandObj = _
68:      applicationObject.Commands.AddNamedCommand(objAddIn, _
69:       "MyAddin", "MyAddin", "Executes the command for MyAddin", 
True, 59, Nothing, _
70:       1 + 2)
71:     '1+2 == vsCommandStatusSupported+vsCommandStatusEnabled
73:     CommandObj.AddControl( _
74:      applicationObject.CommandBars.Item("Tools"))
75:    Catch e As System.Exception
76:    End Try
77:   End If
78:  End Sub
80:  Public Sub Exec(ByVal cmdName As String, _
81:   ByVal executeOption As vsCommandExecOption, _
82:   ByRef varIn As Object, ByRef varOut As Object, _
83:   ByRef handled As Boolean) Implements IDTCommandTarget.Exec
85:   handled = False
86:   If (executeOption = _
87:    vsCommandExecOption.vsCommandExecOptionDoDefault) Then
89:    If cmdName = "MyAddin.Connect.MyAddin" Then
90:     handled = True
91:     Exit Sub
92:    End If
93:   End If
94:  End Sub
96:  Public Sub QueryStatus(ByVal cmdName As String, _
97:   ByVal neededText As vsCommandStatusTextWanted, _
98:   ByRef statusOption As vsCommandStatus, _
99:   ByRef commandText As Object) _
100:    Implements IDTCommandTarget.QueryStatus
102:   If neededText = _
103:    EnvDTE.vsCommandStatusTextWanted.vsCommandStatusTextWantedNone _
104:    Then
106:    If cmdName = "MyAddin.Connect.MyAddin" Then
107:     statusOption = _
108:      CType(vsCommandStatus.vsCommandStatusEnabled + _
109:       vsCommandStatus.vsCommandStatusSupported, _
110:       vsCommandStatus)
111:    Else
112:     statusOption = vsCommandStatus.vsCommandStatusUnsupported
113:    End If
114:   End If
115:  End Sub
116: End Class


Keep in mind that GUIDs will always be unique. Thus, the value of GuidAttribute on line 15 on your computer will vary.

As is immediately apparent from the listing, a few of these procedures are a little murky and all of the statements are long, but when you evaluate each one they are pretty straightforward. All of the code implements the two interfaces previously mentioned. The IDTExtensibility2 methods are implemented as empty methods, and the IDTCommandTarget methods have the necessary code needed to place a menu item in the Tools menu and respond when it is clicked. The next two subsections discuss the IDTCommandTarget and IDTExtensibility2 interfaces.

Implementing the IDTExtensibility2 Interface

IDTExtensibility2 is the basic add-in interface. There are five methods that you must implement—although they can be empty methods—to define an add-in. These are described in Table 4.1.

Table 4.1 IDTExtensibility2 Interface Methods That Have To Be Implemented To Create an Add-In

Interface Method



Called when the Add-In Manager list changes


Called when the IDE is shut down before OnDisconnection


Called when an add-in is loaded


Called when an add-in is unloaded


Called when the IDE has completed startup

From the descriptions, it is apparent that these methods are provided to allow you an opportunity to initialize and release resources at opportunistic points during your add-in's lifecycle. If you need some processing to occur before your add-in is run, implement OnConnection or OnStartupComplete. If you need processing to occur before your add-in or the IDE is shut down, implement OnDisconnection or OnBeginShutDown. Otherwise, leave the implementation of these interface methods blank, as demonstrated in Listing 4.9.

OnConnection is implemented to create (named) Command object on lines 67 through 70, allowing the command to be invoked from the Command window, and adds the command to the Tools menu—in the example, on lines 73 and 74.

Implementing the IDTCommandTarget Interface

The IDTCommandTarget interface implements the menu item for the add-in. The Exec method is called when the menu item is clicked and QueryStatus returns the status of the command specified in the arguments, indicating whether the command is available.

The code implementing the IDTCommandTarget is very specific, suggesting that it is easiest if you let the wizard generate the add-in initially. You could, however, use the wizard-generated code as a template for creating add-ins manually.

Registering Add-Ins

To register your VB .NET add-in, open a command prompt window and run the Regasm.exe application with the /codebase switch, passing it the name of your add-in DLL.

Regasm.exe is installed in the \Winnt\Microsoft.Net\Framework\ directory by default. Because we indicated that the add-in should be available to all users in step 5 in the section "Creating an Add-In Project," the add-in keys are added to the registry in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.0\AddIns\MyAddIn.Connect.

The help topic ms-help://MS.VSCC/MS.MSDNVS/vsintro7/html/vxconAdd-InRegistration.htm titled "Add-In Registration" provides additional details on the keys that are added to the registry and appropriate values for those keys.

Here is an example demonstrating the proper registration of an add-in named MyAddIn.dll. (The example assumes that the current directory contains the add-in .DLL.)

C:\WINNT\Microsoft.NET\Framework\v1.0.3215\Regasm.exe myaddin.dll /codebase

(The specific location of Regasm.exe may vary by computer and version of the .NET Framework.)


The best way to ensure that add-ins are properly registered and installed is to run the Add-In setup project when you are finished testing and debugging your add-in.

Regasm.exe assigns a .NET assembly a ClassID and adds a registry entry. The reason for all of this has to do with the Add-Ins Manager. The Add-Ins Manager is an Automation server. Yes, that's right—old COM technology still lingers in a few places.

The /codebase switch puts the full path to the Add-In into the registry.

Implementing the IDTToolsOptionsPage Interface

Implement the IDTToolsOptionsPage interface when you want to define a custom Tools Options Page for your add-in. You will have to implement the following interface methods to create the Options page: GetProperties, OnAfterCreated, OnCancel, OnHelp, and OnOK.

GetProperties needs to return a DTE.Properties collection containing all of the properties on the custom page. OnAfterCreated is called after the page is created. OnCancel, OnHelp, and OnOK are called in response to the user clicking those buttons, respectively.

Adding Behaviors to the Add-In

Now that we have created an empty add-in, we need to add some code to define a behavior. Our add-in behavior is invoked when the user clicks the Tools, MyAddIn menu item or when the user types MyAddin.Connect.MyAddIn in the Command window. Either of these methods yields control to the Exec method we implemented, hence our new behavior needs to be initiated in the Exec method.

Building on the behavior we contrived in an earlier section, we can insert code to run the copyright-insertion behavior from the Add-In menu. That is, our add-in will insert a copyright tag.

To modify the MyAddIn add-in to use the InsertCopyrightTag macro, we will need to ensure that the macro project containing that macro is loaded. Alternatively, we can copy the macro code into the MyAddIn project and avoid a dependency on the macro. Listing 4.10 demonstrates the former approach; it is assumed that the macro project is loaded and we will execute the macro to actually perform the add-in behavior for us.

Listing 4.10 Excerpt from Listing 4.9 showing the modifications to the add-in created by the wizard.

80:  Public Sub Exec(ByVal cmdName As String, _
81:   ByVal executeOption As vsCommandExecOption, _
82:   ByRef varIn As Object, ByRef varOut As Object, _
83:   ByRef handled As Boolean) Implements IDTCommandTarget.Exec
85:   handled = False
86:   If (executeOption = _
87:    vsCommandExecOption.vsCommandExecOptionDoDefault) Then
89:    If cmdName = "MyAddIn.Connect.MyAddIn" Then
91:     DoExecute()
93:     handled = True
94:     Exit Sub
95:    End If
96:   End If
97:  End Sub
120:  Private Sub DoExecute()
121:   ' Need to ensure the Copyright macro project is defined.
122:   applicationObject.ExecuteCommand( _
123:    "Macros.MyMacros.Copyright.InsertCopyrightTag")
124:  End Sub

Listing 4.10 is excerpted from Listing 4.9. Replace the Exec implementation and add the DoExecute method to complete the add-in MyAddIn. Line 91 invokes the DoExecute behavior when the Exec method is called. DoExecute is implemented in terms of the InsertCopyrightTag defined earlier in this chapter. Line 122 of DoExecute uses the EnvDTE.DTE.ExecuteCommand method to run the macro. ExecuteCommand works as if you had typed the statement in the Command window.

Because macros are implemented with VB .NET, you could literally cut and paste the macro code into the add-in as an alternative to invoking the macro.

  • + Share This
  • 🔖 Save To Your Account