VisualStudioVersion, MSBuildExtensionsPath32


MSBuildExtensionsPath32

The path of the MSBuild subfolder under the \Program Files or \Program Files (x86) folder. This path always points to the 32-bit \Program Files folder on a 32-bit machine and \Program Files (x86) on a 64-bit machine. See also MSBuildExtensionsPath and MSBuildExtensionsPath64.


MSBuildExtensionsPath

Introduced in the .NET Framework 4: there is no difference between the default values of MSBuildExtensionsPath and MSBuildExtensionsPath32. You can set the environment variable MSBUILDLEGACYEXTENSIONSPATH to a non-null value to enable the behavior of the default value of MSBuildExtensionsPath in earlier versions.

In the .NET Framework 3.5 and earlier, the default value of MSBuildExtensionsPath points to the path of the MSBuild subfolder under the \Program Files\ or \Program Files (x86) folder, depending on the bitness of the current process. For example, for a 32-bit process on a 64-bit machine, this property points to the \Program Files (x86) folder. For a 64-bit process on a 64-bit machine, this property points to the \Program Files folder.

This location is a useful place to put custom target files. For example, your target files could be installed at \Program Files\MSBuild\MyFiles\Northwind.targets and then imported in project files by using this XML code:

<Import Project="$(MSBuildExtensionsPath)\MyFiles\Northwind.targets"/>


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:


1
< 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 :)



Visual Studio project compatability and VisualStudioVersion

One of the most requested features of Visual Studio 2012 was the ability to open projects in both VS 2012 as well as VS 2010 (requires VS 2010 SP1). In case you haven’t heard we did implement that feature. You may be wondering how we were able to do this and how this may impact you.

If you open the .csproj/.vbproj for a Web Project created in VS2010 you will see the following import statement.

1<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\<br>                  v10.0\WebApplications\Microsoft.WebApplication.targets" />

When you open this project in VS 2012 there are a few changes made to your project file to ensure that it can be opened in both VS 2010 SP1 and VS 2012. One of the changes made to the project when it is first loaded in VS 2012 is to add the following to replace that import statement.

1<PropertyGroup>
2  <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
3  <VSToolsPath Condition="'$(VSToolsPath)' == ''">
4    $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
5</PropertyGroup>
6<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />

We removed the hard-coded 10.0 and instead used the property VisualStudioVersion. When building in Visual Studio 2012 this value will always be 11.0, but for VS 2010 it doesn’t exist. That is why we defaulted it to 10.0 above.

There are some scenarios where building from the command line will require to set this property explicitly. Before we get there let me explain how this property gets set (in this order)

  1. If VisualStudioVersion is defined as an environment variable/global MSBuild property, that is used.
    • This is how VS and the VS developer command prompt set this value
  2. Based on the file format version of the .sln file (toolset used is sln file format –1)
    • To simplify this statement, the .sln file will build with specifying VisualStudioVersion to the value of the version of VS which created the .sln file.
  3. Choose default
    • 10.0 if VS 2010 is installed
    • Highest-versioned sub-toolset version installed

For #2 when you are building a .sln file the value of VisulStudioVersion will be –1 of the Format Version found in the .sln file. The important thing to note here is that if you build a .sln file it will build with the value of VisulStudioVersion corresponding to the version of VS which created the .sln file. So if you create a .sln file in VS2012 and you always build that .sln file the value for VisualStudioVersion will be 11.0. In many cases if you build the .sln file you are good.

If you are building .csproj/.vbproj files w/o going through a .sln file? If you build a web project from the command line (not the developer prompt) then the value for VisualStudioVersion used will be 10.0. That is an artifact of the properties which I showed above. In this case you should pass this in as an MSBuild property. For example

1msbuild.exe MyAwesomeWeb.csproj /p:VisualStudioVersion=11.0

In this case I’m passing in the property explicitly. This will always override any other mechanism to determine the value for VisualStudioVersion. If you are using the MSBuild task in a build script, then you can specify the property either in the Properties attribute or the AdditionalProperties attribute. See my previous blog post on the difference between Properties and AdditionalProperties.

If you encounter any funny behavior when building/publishing and you notice that the wrong .targets files are being imported then you may need to specify this property.



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值