http://blogs.clariusconsulting.net/kzu

Daniel Cazzulino's Blog

Go Back to
kzu′s Latest post

How to create a Visual Studio extensibility project that is compatible with VS 2010, 2012 and 2013

For most common project types, you can just create a project in VS2010 and open it in 2012 and 2013 just fine. For some others (like VS package or VSIX projects), you may find Visual Studio wants to perform a one-way conversion:

image

 

The first check that was introduced with VS2012 was to require a new $(MinimumVisualStudioVersion) property that has to match the also newly introduced $(VisualStudioVersion). The one-way upgrade would end up introducing the following two properties at different places in the project file:

<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion><MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>

The next was to improve the way the location of the VSSDK is determined (to allow overriding it on build servers, I assume). Where previously you’d have a hardcoded reference to the VSSDK targets:

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\VSSDK\Microsoft.VsSDK.targets" Condition="false" />

You’d now have:

<PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />

This makes it possible to build the same extension for multiple Visual Studio targets.

While these changes seem like they would be backwards-compatible with VS2010, they are not. Specifically, the MinimumVisualStudioVersion = 11.0 is the problem there. Moreover, when we open the project in VS2013, it will be updated to 12.0 making it incompatible with VS2012 AND VS2010!

image

We can solve it by changing it as follows:

<!-- This is the property that causes VS 2012+ to insist on one-way update of the project -->
<PropertyGroup Condition="'$(VisualStudioVersion)' &gt;= '11.0'">
    <MinimumVisualStudioVersion>$(VisualStudioVersion)</MinimumVisualStudioVersion>
</PropertyGroup>

We assume that later versions of VS will keep checking the same way, so we use the >= 11.0 condition there, and essentially just make the two match. With this in place, we can now open the project in all three versions.

Another property that VS2013 adds too is:

<OldToolsVersion>4.0</OldToolsVersion>

So to recap, we can just add these properties at the top of the project file:

<PropertyGroup>
    <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>

<!-- This is the property that causes VS 2012+ to insist on one-way update of the project -->
<PropertyGroup Condition="'$(VisualStudioVersion)' &gt;= '11.0'">
    <MinimumVisualStudioVersion>$(VisualStudioVersion)</MinimumVisualStudioVersion>
</PropertyGroup>
<!-- This is an additional property added by VS 2013 -->
<PropertyGroup Condition="'$(VisualStudioVersion)' &gt;= '12.0'">
    <OldToolsVersion>4.0</OldToolsVersion>
</PropertyGroup>

And at the bottom, replace the SDK targets with:

<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />

 

Referencing SDK Assemblies

In addition to the project opening fine, you also need to be able to reference the SDK assemblies from your projects properly. This is tricky since the location of the SDK assemblies changed significantly between 2010 and 2012+. It used to be under C:\Program Files (x86)\Microsoft Visual Studio 2010 SDK SP1\ and now is under C:\Program Files (x86)\Microsoft Visual Studio 11.0\VSSDK\. Clearly, the new location is much more amenable to automation from MSBuild. The most reliable way I’ve found that looks concise is to just grab the value from the VSSDK[version]Install environment variable and use that to build the SDK path:

<PropertyGroup Condition="'$(VSSDK)' == ''">
    <VSSDKVar>VSSDK$(VisualStudioVersion.Replace('.', ''))Install</VSSDKVar>
    <VSSDK>$([System.Environment]::ExpandEnvironmentVariables('%$(VSSDKVar)%'))VisualStudioIntegration\Common\Assemblies\</VSSDK>
</PropertyGroup>
<PropertyGroup>
    <VSSDK20>$(VSSDK)v2.0\</VSSDK20>
    <VSSDK40>$(VSSDK)v4.0\</VSSDK40>
    <VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>

Note that the [version] in the environment variable does not contain the VS version dot (i.e. 11.0 is 110), so we remove that to build the variable name. We then expand it to get the $(VSSDK). And then I build the lib locations for both 2.0 and 4.0 folders, which contain different sets of assemblies that might be useful when developing an extension.

One final variable which is quite useful to determine the location of PublicAssemblies and PrivateAssemblies (not that you should be using any from there ;) ) is DevEnvDir. As I explained elsewhere, that variable is only populated when building from inside VS. We can create it for command line builds using the same technique as above to make it VS version aware:

<!-- On build servers and command line, this property is not available, so we redefine it in
         terms of the environment variables created by VS when installed -->
<PropertyGroup Condition="'$(DevEnvDir)' == ''">
    <VSCommToolsVar>VS$(VisualStudioVersion.Replace('.', ''))COMNTOOLS</VSCommToolsVar>
    <DevEnvDir>$([System.Environment]::ExpandEnvironmentVariables('%$(VSCommToolsVar)%'))..\IDE\</DevEnvDir>
</PropertyGroup>

And on top of that, we can just create the two additional variables for convenience:

<PropertyGroup>
    <PublicAssemblies>$(DevEnvDir)PublicAssemblies\</PublicAssemblies>
    <PrivateAssemblies>$(DevEnvDir)PublicAssemblies\</PrivateAssemblies>
</PropertyGroup>

 

So now you need to updated your references to take advantage of those variables, like:

<Reference Include="Microsoft.VisualStudio.Shell, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
    <SpecificVersion>False</SpecificVersion>
    <HintPath>..\..\..\Program Files (x86)\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v2.0\Microsoft.VisualStudio.Shell.dll</HintPath>
</Reference>

To:

<Reference Include="Microsoft.VisualStudio.Shell, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
    <SpecificVersion>False</SpecificVersion>
    <HintPath>$(VSSDK)v2.0\Microsoft.VisualStudio.Shell.dll</HintPath>
</Reference>

 

The final bit is that if your code takes advantage of a particular new feature in a newer version of VS, you’ll need to #ifdef the code (or conditionally include source files). So it’s convenient to have a constant defined too:

<DefineConstants>$(DefineConstants);VS$(VisualStudioVersion.Replace('.', ''))</DefineConstants>

 

You can look at the final MSBuild properties and targets files directly on Github. Or even better, just install the nuget package and get both added to your VSX project automatically :)

Install-Package Clarius.VisualStudio

 

Enjoy!

Comments

5 Comments

  1. Hey Daniel, I’m writing my first VS extension and want it to work in VS 2012 and VS 2013, but am having some problems. I’ve posted my problem on the MSDN Forums at http://social.msdn.microsoft.com/Forums/vstudio/en-US/8bf181f9-71d3-4c2d-8df9-bb78910df557/do-you-need-multiple-solutions-to-create-sidebyside-vspackages-for-multiple-versions-of-visual?forum=vsx which is what led me here. I’ve tried adding your msbuild properties to my .csproj, but still have the same problem :( If you could take a look at my MSDN Forum post, I would appreciate any advice you can give. Thanks.

  2. I didn’t mention it before, but this really is a great article and I’m learning quite a bit from your code. Thanks! My question is, if you have one project for all versions of Visual Studio (VS), how do you test it in each different VS version? Do you just manually edit the .csproj file and change the to the version that you want it to launch when debugging it? Does that work? Or is there another way to do it? Thanks.

  3. Bah, the xml attribute got stripped out of my last comment. Is was supposed to read “Do you just manually edit the .csproj file and change the VisualStudioVersion attribute to the version that you want it to launch when debugging it?”

    • The beauty of this approach is that you don’t have to modify anything! Just open the project in whatever version of VS you want to test in, and it will build and deploy to that version’s experimental instance :)

  4. What about VSPackage Builder that currently only supports VS 2010? Is there a way to successfully open a VS 2010 project that uses VSPackage Builder in VS 2012/2013?

    Note that if VSPackage Builder doesn’t work in VS 2012/2013, that is fine, one can always re-open it in VS 2010 if you want to use the VSPackage Builder features. But I don’t want VS 2012/2013 blowing up because it doesn’t understand the VSPackage Builder files, or modifying the VSPackage Builder files so it no longer works when opening it again in VS 2010.