Home > Articles > Programming > Windows Programming

Creating Visual Studio .NET Add-Ins

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.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information

To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.


Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.


If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information

Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.


This site is not directed to children under the age of 13.


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information

If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information

Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents

California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure

Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact

Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice

We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020