Easily extend your IDE with Extensibility feature for Visual Studio 2015

Marcin Kawalerowicz / Wednesday, March 9, 2016

 

 

Visual Studio 2015 is a very advanced IDE with a great number of useful options, but sometimes you may find that there are not enough features to meet your needs. Certain operations may be even more automated or you might prefer to have more project types or languages supported. Sounds familiar? If so, there is an easy way to deal with such situations.

A while back, there were Macros and Add-Ins to make our IDE more developer friendly and meet our needs. In Visual Studio 2015, those ways of extending the environment are not supported anymore; instead there is Visual Studio Extensibility (VSX or VSIX). This possibility was released first in the 2005 version and now is mature enough to give us a great experience with building our own plugins.

Thanks to the Extensibility feature we can extend menus and commands, create customized tool windows, editors, project templates, extend user settings, properties in the Property Window, create Visual Studio Isolated Shell apps and many more. Basically the only limitations are our needs and imagination. In this article we will give you a brief look at the capabilities of Visual Studio Extensions.

To start with extensions development we need to have Visual Studio 2015 with the Visual Studio SDK. You can get Visual Studio Community for free here: Visual Studio Community 2015 Download. During installation, just select Visual Studio Extensibility Tools and it will be installed together with other parts of the IDE.

If you already have Visual Studio 2015 installed just open it, go to File/New Project and expand Visual C#/Extensibility. Choose Visual Studio Extensibility Tools and follow the instructions.

MSBuild extension (automated custom build)

Today we are going to make a simple but still very useful extension which will let us use a custom *.proj file to build solutions using MSBuild. First let’s create a VSIX Project. To do so, go to File/New/Project, and then choose Extensibility/ VSIX Project and specify the name. In this case it will be CustomBuilder (fig.1).

Figure 1

Visual Studio has generated a Getting Started Visual Studio Extensions help page. You can delete these files because you don’t need to use them (unless you’d like to read it). Delete index.html and stylesheet.css but keep source.extension.vsixmanifest – we’re going to use it later.

Add Package

Now we need to add a package, by adding new item to our project (fig.2).

Figure 2

And in the Add New Item window select Visual C# Items/Extensibility/VSPackage and create new Visual Studio Package. Name it CustomBuilderPackage.cs.(fig 3)

Figure 3

Add Command

We need one more item in the project – command. To add it, complete the same steps as for package, but in the Add New Item window choose Custom Command instead of Visual Studio Package. The name will be CustomBuildCommand.cs (fig 4)

Figure 4

Loading package after opening solution

We want our package to be available only while a solution is opened. To restrict the user from using the option, add attribute ProvideAutoLoad to the CustomBuildPackage class and add the following code before the class definition (fig 5).

[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids.SolutionExists)]

public sealed class CustomBuildPackage : Package

{

    //...

}

Figure 5

 

In CustomBuildPackage.vsct set the command flag to DefaultDisabled in tag: Button (fig. 6):

<Buttons>

 

  <Button guid="guidCustomBuildPackageCmdSet" id="CustomBuildCommandId" priority="0x0100" type="Button">

 

    <Parent guid="guidCustomBuildPackageCmdSet" id="MyMenuGroup" />

 

    <Icon guid="guidImages" id="bmpPic1" />

 

    <CommandFlag>DefaultDisabled</CommandFlag>

 

    <Strings>

 

      <ButtonText>Invoke CustomBuildCommand</ButtonText>

 

    </Strings>

 

  </Button>

 

</Buttons>

Figure 6

 

Command button layout and shortcut key

To customize the appearance of our command you can specify some layout options in CustomBuildPackage.vsct file. Change <ButtonText> tag to set the text that will be displayed in the Tools menu.

Icon

You can also add an icon to distinguish your plugin. First add <GuidSymbol> tag to

<GuidSymbol name="cmdIcon" value="{9194BBE0-78F3-45F7-AA25-E4F5FF6D10F9}" >    <IDSymbol name="commandIcon" value="1" /></GuidSymbol>

Figure 7

 

To generate a number to the value attribute, open the Tools menu and select Generate GUID. Now click 5th format, Copy generated GUID, Exit the tool and paste it to your code (fig. 7).

Figure 8

You have to specify which picture you want to use. Put a *.png file in Resources folder under your project directory (the icon should be 16x16 px) and add this code to <Bitmaps> section.

<Bitmaps>  <Bitmap guid="cmdIcon" href="Resources\commandIcon.png" usedList="commandIcon"/></Bitmaps>

 

Now change <Icon> tag to refer to the right file:

<Icon guid="cmdIcon" id="commandIcon" />

 

Shortcut Key

To make the tool more user-friendly you can add a shortcut key for it. All you need to add is 3 lines of code just before the <Symbols> tag:

<KeyBindings>  <KeyBinding guid="guidCustomBuildPackageCmdSet" id="CustomBuildCommandId" editor="guidVSStd97" key1="1" mod1="CONTROL"/></KeyBindings>

 

Where key1 specifies an alphanumeric key or VK_constants, mod1 is any key from: CTRL, ALT, and SHIFT.

Now our command should look like Fig. 9.

Figure 9

MSBuilder logic

The extension looks like it supposed to, but it does nothing yet. To make it work, add the following class:

using System.IO;namespace CustomBuilder

{

  public class CustomMsBuilder

  {

    private string solutionPath;

 

    public string msBuildLocation

    {

      get

      {

        var currentRuntimeDirectory = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();

 

        return System.IO.Path.Combine(currentRuntimeDirectory, "msbuild.exe");

      }

      private set

      { }
    }

    public CustomMsBuilder(string solutionPath)

    {

      this.solutionPath = solutionPath;

    }

    public string BuildSolution()

    {

      return FormatOutput(StartMsBuildWithOutputString());

    }

    private string StartMsBuildWithOutputString()

    {

      var outputString = "";

 

      using (var customBuilder = GetMsBuildProcess())

      {

        var standardOutput = new System.Text.StringBuilder();

 

        while (!customBuilder.HasExited)

        {
          standardOutput.Append(customBuilder.StandardOutput.ReadToEnd());
        }
        outputString = standardOutput.ToString();
      }

      return outputString;

    }

    private System.Diagnostics.Process GetMsBuildProcess()

    {

      var startInfo = new System.Diagnostics.ProcessStartInfo(msBuildLocation, solutionPath);

 

      startInfo.RedirectStandardOutput = true;

 

      startInfo.UseShellExecute = false;

 

      return System.Diagnostics.Process.Start(startInfo);

    }

    private string FormatOutput(string processedOutput)

    {

      string solutionName = Path.GetFileName(solutionPath);

 

      var header = "CustomBuilder - Build " + solutionName + "\r\n--\r\n";

 

      return header + processedOutput;

    }
  }
}
 

This class runs the MSBuild.exe process, builds the opened solution from the custom *.proj file (if the project contains any), formats and redirects output of the MSBuild.exe process, which can displayed it to user from our extension.

The constructor of the class accepts a string with solution’s path and stores it in a field, so it can read it later in a suitable method.

The public method BuildSolution() gets the right MSBuild path (using getter of a property msBuildLocation), starts the msbuild.exe process with the solution path as a parameter, reads the output from console (using string builder), and returns formatted result – with a header "CustomBuilder – Build - <solution name>”.

After the CustomMSBuilder class is finished, it should be called from the CustomBuildCommand. In CustomBuildCommand you have to update the callback function as shown above:

 

Add a using:

using EnvDTE80;

 

 

Change the callback name:

private CustomBuildCommand(Package package)

{

     //...

 

       var menuItem = new MenuCommand(this.CustomBuildCallback, menuCommandID);

       commandService.AddCommand(menuItem);
     }
}

 

Change the callback function and add an additional one:

private void CustomBuildCallback(object sender, EventArgs e)

{

      var cMsBuilder = new CustomMsBuilder(GetSolutionPath());

 

      var outputMessage = cMsBuilder.BuildSolution();

 

      WriteToOutputWindow(outputMessage);

 

//displays the output – we’ll create this method in next step

}
 

public string GetSolutionPath()

{

      DTE2 dte = ServiceProvider.GetService(typeof(SDTE)) as DTE2;

 

      return dte.Solution.FullName ?? "";

}

 

Output

We can display the result in the output window (the same way Visual Studio informs whether the build solution succeed or not). In order to do that, add the following code to the CustomBuildCommand.cs file (just below existing methods).

private void WriteToOutputWindow(string message)

{

    var pane = GetOutputPane(PaneGuid, "CustomBuilder Output", true, true, message);    pane.OutputString(message + "\n—");

 

    pane.Activate();        // Activates the new pane to show the output we just add.

}
 

private IVsOutputWindowPane GetOutputPane(Guid paneGuid, string title, bool visible, bool clearWithSolution, string message)

{

    IVsOutputWindow output = (IVsOutputWindow)ServiceProvider.GetService(typeof(SVsOutputWindow));    IVsOutputWindowPane pane;

 

    output.CreatePane(ref paneGuid, title, Convert.ToInt32(visible), Convert.ToInt32(clearWithSolution));

 

    output.GetPane(ref paneGuid, out pane);

 

    return pane;

}

 

We need also to generate new guid and assign it to a variable at the beginning of our file:

namespace CustomBuilder

{

  internal sealed class CustomBuildCommand

  {
 

  //...

 

    public static readonly Guid CommandSet = new Guid("84a7d8e5-400d-40d4-8d92-290975ef8117");

 

  //...

 
  }
}

Distribution

After you’re done with the development of your extension and you’re sure it works fine (double check!) you can share the extension easily with other developers. First you have to open source.extension.vsixmanifest and specify some information about your extension. You should fill in all metadata information, target versions of Visual Studio, dependencies and other known information.

Figure 10

There are two supported ways to distribute your extension. First you can just share the *.vsix binary file of your Visual Studio Extension – e.g. send it by email, send a link to ftp/cloud or distribute as you wish. You can find the file in the bin/Release folder of the solution (if you built a release version of your extension). All the recipients will have to do is to download the file, close Visual Studio, double click on the file and use the installation wizard which is straight forward.

Visual Studio Gallery

If you want to reach a larger number of Visual Studio users, you can also publish the extension on the Visual Studio Gallery. Doing so requires a few steps:

Sign in to Visual Studio Gallery website with your Microsoft account. Choose “Upload” option on the screen and create an MSDN Profile if you don’t have any. Specify your Display Name, agree to the Terms of Use and click the Continue button.

On the next few screens you have to input some information about your extension such as Extension type – whether it is a tool, control, template or storyboard shape (which applies only to PowerPoint, so not here).

After you have specified the type of your plugin, you can choose where you will store the *.vsix file. You can upload it to Microsoft servers or share a link to some custom location in the Internet (e.g. if you have your own server).

After you have uploaded (or linked) the proper extension, you can add Basic information. Some of this is filled in automatically based on our source.extension.vsixmanifest from the project, like the title, version or summary. You can choose the category and add some tags to help users to find your extension easily. In the Cost category you can specify whether you want to sell your extension (Trial or Paid option) or provide it for Free. A really nice feature included here is the option to share a link to your code repository if you want to let users to browse through your code.

You have to also provide more information about your plugin. In the Description field you can use given templates or create your own document about your extension. Videos and images can be included so you can fully present your plugin.

Later you have to agree to the Contribution Agreement by checking the box and click Create contribution button.

After saving process your extension is not published yet, but you can Edit, Delete, Translate or Publish your project to finally make it live.

Now it’s easy to find your plugin in the Visual Studio Gallery website or in Extension and Updates in your Visual Studio by typing in the name or keywords which were specified while uploading your project.

Summary

The extensibility feature in Visual Studio 2015 is very easy and straight forward, as you can see. You create a project as you’d create a simple console application in Visual Studio, and you can share it too, easily extending the functionality of your IDE.

 

Want to build your desktop, mobile or web applications with high-performance controls? Download Ultimate Free trial today or contact us and see what it can do for you.