VSIX Tutorial-Adding a simple command
Add supporting classes and files
- Create a new class "PkgCmdIDList" inside a file called "PkgCmdID.cs". This will contain the id of the command that will be added.
// PkgCmdID.cs
// MUST match PkgCmdID.h
using System;
namespace Company.MyExtension
{
static class PkgCmdIDList
{
public const uint cmdidGenerateIni = 0x100;
};
}
- Add the following attributes to the MyExtensionPackage class.
- ProvideMenuResource attribute is needed to let the shell know that this package exposes some menus.
- ProvideAutoLoad attribute is used to load the extension menu only when all the solution exists and fully loaded.
[ProvideMenuResource("Menus.ctmenu", 1)]
[ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_string)]
- Add an empty vsct file to the project. This for now will contain only the minimal stuff.
<?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"> </CommandTable>
- Close the solution and edit the MyExtension.csproj. Find the ItemGroup containing the MyExtension.vsct and replace the "<Content Include="MyExtension.vsct" />" set it as following.
- VSCTCompile: Use the VSCT compiler to "compile" the file
- ResourceName: The menu id, note that it's the same we used into ProvideMenuResource
<ItemGroup>
<VSCTCompile Include="MyExtension.vsct">
<ResourceName>Menus.ctmenu</ResourceName>
</VSCTCompile>
</ItemGroup>
Create the menu
Here comes troubles. The menu structure is inside the .vsct file. And is organized as follow, logically, for our project
<Commands>
<Group guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE">
<Menu guid="guidMyExtensionCmdSet" id="myExtensionSubMenu" priority="0x0000" type="Menu">
<Group guid="guidMyExtensionCmdSet" id="myExtensionSubMenuGroup" priority="0x0600">
<Button guid="guidMyExtensionCmdSet" id="cmdidGenerateIni" priority="0x0100" type="Button">
</Button>
</Group>
</Menu>
</Group>
</Commands>
- The guid
- sets the "collection" of items (Commands, Groups, Menus, Buttons and Symbols)
- Must be present in the Symbols section as a guid
- Note that all the items declared are part of the guidMyExtensionCmdSet that is our custom command set
- The id
- sets the single item inside the collection. Must be indicated inside the GuidSymbol collection
- Must be present inside the GuidSmbol section inside its IDSymbol tag
The command table, the .vsct will then contain
<!--This is the file that defines the IDs for all the commands exposed by VisualStudio. -->
<Extern href="stdidcmd.h"/>
<!--This header contains the command ids for the menus provided by the shell. -->
<Extern href="vsshlids.h"/>
<Commands package="guidMyExtensionPkg">
<Menus>
<!-- Define a new menu below the mainItemMenuGroup-->
<Menu guid="guidMyExtensionCmdSet" id="myExtensionSubMenu" priority="0x0000" type="Menu">
<Parent guid="guidMyExtensionCmdSet" id="mainItemMenuGroup"/>
<Strings>
<ButtonText>My Extension</ButtonText>
</Strings>
</Menu>
</Menus>
<Groups>
<!-- Define the mainItemMenuGroup, child of the item menu -->
<Group guid="guidMyExtensionCmdSet" id="mainItemMenuGroup" priority="0x0600">
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_ITEMNODE"/>
</Group>
<!-- Define the myExtensionSubMenuGroup, child of the myExtensionSubMenu -->
<Group guid="guidMyExtensionCmdSet" id="myExtensionSubMenuGroup" priority="0x0600">
<Parent guid="guidMyExtensionCmdSet" id="myExtensionSubMenu"/>
</Group>
</Groups>
<Buttons>
<!-- Define the button cmdidGenerateIni inside the myExtensionSubMenuGroup-->
<Button guid="guidMyExtensionCmdSet" id="cmdidGenerateIni" priority="0x0100" type="Button">
<Parent guid="guidMyExtensionCmdSet" id="myExtensionSubMenuGroup" />
<Strings>
<ButtonText>Generate Ini</ButtonText>
</Strings>
</Button>
</Buttons>
</Commands>
<Symbols>
<!-- This is the package guid. -->
<GuidSymbol name="guidMyExtensionPkg" value="{14796bd1-9ea5-4eff-b4c0-bee11efbb734}" />
<!-- This is the guid used to group the menu commands together -->
<GuidSymbol name="guidMyExtensionCmdSet" value="{6abb0b81-9bb9-450d-849d-554a6501b931}">
<IDSymbol name="mainItemMenuGroup" value="0x1020" />
<IDSymbol name="myExtensionSubMenu" value="0x2020"/>
<IDSymbol name="myExtensionSubMenuGroup" value="0x1021" />
<IDSymbol name="cmdidGenerateIni" value="0x0100" />
</GuidSymbol>
</Symbols>
Note that all the guids are declared. The guidMyExtensionPkg is the guid of the MyExtension project and is set inside the "guidMyExtensionPkgString" in Guids.cs. The same goes for the guidMyExtensionCmdSet that has the variable "guidMyExtensionCmdSetString"
Note too that the "cmdidGenerateIni" has the same value as in the PkgCmdID.cs file.
The "guidSHLMainMenu" is present inside the two .h files declared a the top of the file. as the id IDM_VS_CTXT_ITEMNODE. You can find more id on MSDN

Connecting the action
- Go inside Initialize function after the base.Initialize the following call. It creates a command id associated with the guidMyExtensionCmdSet and with the cmdidGenerateIni command id.
- When invoked it will call the MenuItemCallback function
OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if (null != mcs)
{
// Create the command for the menu item.
CommandID menuCommandID = new CommandID(GuidList.guidMyExtensionCmdSet, (int)PkgCmdIDList.cmdidGenerateIni);
MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID);
mcs.AddCommand(menuItem);
}
- Now the callback must be created
private void MenuItemCallback(object sender, EventArgs e)
{
}
- We would like to show the path of the file clicked inside the callback and for this we must add a call to EnvDTE
- We retrieve with GetService the DTE instance
- We retrieve the selected item if present.
- We retrieve the ProjectItem if present. This is the abstraction of a project item :P
- We get the property "FullPath" that contains the path of the item. EnvDTE objects are COM objects, thus not all properties and methods are directly exposed, since the trick of the Properties.
private void MenuItemCallback(object sender, EventArgs e)
{
var dte = GetService(typeof(SDTE)) as DTE;
if (dte.SelectedItems.Count <= 0) return;
foreach (SelectedItem selectedItem in dte.SelectedItems)
{
if (selectedItem.ProjectItem == null) return;
var projectItem = selectedItem.ProjectItem;
var fullPathProperty = projectItem.Properties.Item("FullPath");
if (fullPathProperty == null) return;
var fullPath = fullPathProperty.Value.ToString();
MessageBox.Show(string.Format("Required '{0}'.", fullPath));
}
}
Note that we use the foreach. Since SelectedItems does not expose directly an array.
Conclusion
Now everything is in place!

You can download the code