The official source of product insight from the Visual Studio Engineering Team
This post is about a new way to install extensions to Visual Studio, introduced in VS 2010, called the VSIX file. The information it contains will be of most interest to readers who develop Visual Studio extensions, but I encourage users who download and install those extensions to read it as well.
A VSIX file conforms to the ECMA Open Packaging Conventions (OPC) standard. It’s created as part of a VSIX project build in Visual Studio, and you can view its contents with any zip file utility. If you upload your VSIX to the Visual Studio Gallery, your customer can install it right in Visual Studio, in the new Extension Manager:
It can also be installed by downloading and double clicking on the file, and uninstalled either in the Extension Manager, or by simply deleting the associated files. You can find introductory information about VSIX here and here.
The VSIX feature comes with a lot of options. In most cases you don’t have to understand them all. This post is a list of tips that will give you some guidance about how to use the new VSIX capabilities in the best way. Here’s what I’m going to talk about:
Packaging your extensions using VSIX
Installing via MSI
Some extensions still need to be installed by MSI: for example some of your files might have to be in a specific, well-known location, you might have a component like an MSBuild task that VSIX install doesn’t support, you might need to use binding redirection – see more information here. There’s no problem with doing that. In fact, we provide a way for an MSI installed extension to make itself visible in the new Extension Manager, so that the customer can see all his extensions in one place.
To make your extension visible in the Extension Manager, your MSI install should create a subdirectory in the Extensions directory for the hosting product:
For a non-administrative, per-user install (recommended) in Visual Studio, the path will look like this: Users\user id\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\your company\extension name\version\ and for a per-machine install: Program Files\VS 10.0 install directory\Common7\Ide\Extensions\your company\your extension name\version\ An Isolated Shell application will define its own Extension directory.
In that folder, put the extension.vsixmanifest file built by your VSIX project, with an added element that marks it as installed by MSI:
<Identifier Id="VSIXProject2.Microsoft IT.8532242f-afdc-44fa-82b2-0b6b5afc1c38"> <Name>VSIXProject2</Name> <InstalledByMsi>true</InstalledByMsi> … </Identifier>
Note that although the user will then see the extension in the Extension Manager, since it’s installed by MSI, he still needs to manage it through Windows Add/Remove Programs.
Versioning
If your extension is self-contained (i.e. you distribute it in a single VSIX that doesn’t have any dependencies on other ones), and no other VSIXs will depend on yours (i.e. your VSIX doesn’t expose any APIs), you don’t need to read this section. If your VSIX does offer or consume APIs, or you distribute multiple VSIXs with common shared assemblies, read on for more information.
First, let’s do a quick review of how versioning works in the CLR. For an assembly with a strong name, its CLR identity comes from a combination of the file name on disk, Assembly Version string, an optional cultural attribute, and a digital signature. When one assembly references (i.e. consumes APIs from) another one, the consumer is targeted to a particular version of the referenced assembly at build time. (Binding redirection can change this at run time, but the VSIX installer doesn’t support that yet.) The version string contains four segments: <major version>.<minor version>.<build number>.<revision> (for example “1.2.123.0”). The recommended convention for using the version string is that when an assembly’s API breaks binary compatibility, the major version is incremented. (Note that I’m talking about Assembly Version, which is part of the strong name, not Assembly File Version, which is purely informational – you can use Assembly File Version any way you like. See more information here.)
That’s all background information. Now let’s talk about using versioning with VSIX files. The first thing I’m going to recommend, although it probably sounds a little unexpected, is that you not change the Assembly Version strings when you ship an update of your VSIX. This is because, as I mentioned above, the VSIX installer doesn’t support binding redirection yet, so if you do change any segment of an assembly version number, you may break downstream VSIXs that depend on the old version number of your assembly. The VSIX file has its own mechanism for version management, and I recommend that you use that one instead, because its added flexibility gets you around the binding redirection issue.
For a VSIX that uses an API from another VSIX:
The syntax of a VSIX version string is the same as the assembly one, and we will use the recommended convention to indicate that a new release breaks binary compatibility with the old one: incrementing the major version number. The big advantage of the VSIX versioning mechanism is that if you’re consuming an API from another VSIX, you can specify a range of version numbers of the target VSIX that you’re compatible with. Let’s see how this works. When you raise the Add VSIX Reference dialog in the VSIX Manifest Editor:
in the Version fields just above, you can specify a range between minimum and maximum version numbers that you’re compatible with. If the developer of the VSIX whose API you consume obeys the versioning conventions, you can specify a range like from Min 1.0 to Max 1.9999 to indicate that you will use any version of your dependency between those two. When the VSIX you depend on installs, for example, version 1.2, you will be compatible with it. When the user attempts to install 2.0, the installer will recognize the incompatibility:
and display a warning dialog:
If the user updates the extension you depend on anyway, your extension will be disabled because of the incompatibility, and he should look for an update from you that’s compatible with the new version of the API.
If for any reason you believe you are dependent on a specific version of the target extension, you can code that number as the Min and Max values to target only that version:
If you only code the Min value, you will bind to anything equal or higher. I don’t recommend that, because binary compatibility breakage (in the example below, from 1.x to 3.0) can lead to run time errors.
For a VSIX that offers an API to other VSIXs:
If you release an extension that offers an API, you should handle the versioning at the VSIX level. This means leaving the Assembly Version string unchanged across releases, and incrementing your VSIX version number (shown in the VSIX manifest editor below).
It’s great for your consumers if your API can maintain binary compatibility across releases. If you need to break compatibility, increment the major version number. But at that point, when the VSIX installer upgrades your extension, all the consumers of your API will have to ship releases that are compatible with the new API.
Things to avoid:
Summary
In this article we looked at a set of recommendations for using the new VSIX feature. What I want to leave you with is: minimize complexity. Take advantage of the new VSIX features as you need them, but keep your life as simple as possible by using only the features you need. That way the VSIX install experience will be simple for your customers, which is the reason VSIX was invented.
Please feel free to post comments and questions!
Gary Horen Program Manager, Visual Studio Extensibility
Please, VSIX authors, please do NOT bundle many different features as a single extension. Instead, let each feature install as a separate extension and use the dependency features highlighted above so that users can turn one features on and off as separate extensions. The AllMargins extension is a great example of this. The various "Power Commands" and "Pro Power Tools" (unfortunately from Microsoft) extensions are horrible examples.
@efalskn --
Better support for dependencies, and finer-grained control over what is and is not installed/enabled are definitely things we have in mind for future enhancements to the VSIX feature. My advice to bundle everything into one VSIX is for now, when the user experience installing dependent VSIXs is not what we'd like it to be. The reason I'm suggesting that you try to distribute a single VSIX in this release is to avoid making the user have to understand how dependencies work. I know this is not ideal. It would be great to hear more about what scenarios you think are most important, as we plan for the next version.
Thanks for your feedback.
-Gary Horen
I agree with efalsken. If the user can't understand how dependencies work then what are they doing installing extensions in a product like visual studio?
I couldn't find a more appropriate place for this question. I actually obtained the source code for the AllMargins extension (posted somewhere on one of MS's old code sharing sites)--which I LOVE! I'm in the process of converting it so that it will work with VS2012. The conversion has gone well except for one last thing and I'm hoping that someone can point me in the right direction.
This extension (written by a MS employee) violates the "best practices" spelled out here, as mentioned by @efalsken by nesting multiple VSIX packages. This extension has a somewhat deep hierarchy of packages, each package having a hierarchy of assembly reference dependencies. For example: CaretMargin.dll -> OverviewMarginImpl.dll -> OverviewMargin.dll -> SettingsStoreImp.dll -> SettingStore.dll (I've shortened the DLL names).
The crucial part is that the SettingsStore.dll project contains a SettingsStore.pkgdef file that contains the following:
[$RootKey$\BindingPaths\{GUID}]
"$ProjectPath$"=""
When the extension is loaded (say by VS 2010), a registry entry is made under HKCU\Software\Microsoft\Visual Studio\10.0_Config\BindingPaths: a key of {GUID} is created and a string value of the expansion of $ProjectPath$ is also created. However, after modifying the extension manifests appropriately, Visual Studio 2012 successfully installs the extension, but the BindingPaths are not created as they were in Visual Studio 2010. In fact, no binding paths at all are created.
Is there any reason why this behavior is occurring? How can I track down what's preventing these registry entries from being created when the extension is installed (or after VS starts and loads the extension)? All I get right now is that, for example, FileNotFoundException, probing failed for SettingsStore.dll (shortened DLL name, but I think you get the picture). Probing failed because no binding paths are specified and VS2012 can't find the assembly.
Quick update: I tried installing the single VSIX extension in question (in this case, SettingsStore.dll -- again, shortened DLL name) that contains the *.pgkdef file with the specification of a binding path.
VSIXInstaller reported that the extension installed successfully into Visual Studio 2012 Pro, but again, no registry entry was made under HKCU\Software\Microsoft\Visual Studio\11.0_Config\BindingPaths despite the presence of the pkgdef file (after having restarted Visual Studio 2012).
Any ideas?
@fourpastmidnight:
Please run devenv /setup after your extension install. That should instantiate the entries in HKCU
Matt Kaufman
Visual Studio IDE Services Program Management
Actually, I just figured this out today. It is not necessary to run devenv /setup anymore. In fact, this wasn't even necessary on Visual Studio 2010. I did not have a reference to Visual Studio MPF in the VSIX manifest (I'm not sure if this is strictly required, but it's part of the default VSIX template that ships with the VSSDK). Of more importance, however, is the fact that the sub-extensions that have *.pkgdef files did not have a VsPackage element in the Content element whose inner text is the name of the *.pkgdef file. In Visual Studio 2010, apparently this was not required in order for the *.pkgdef file to be processed. It appears that this is now required for Visual Studio 2012 to process the *.pkgdef file.
Thanks for the suggestion, however.