Visual Studio Extensions and Build Servers

From time to time, we see questions around building a project created with the Visual Studio 2010 SDK on a build server (e.g. Team Foundation Build, TeamCity, CC.NET, etc…). The primary misconception that folks have is that you must install Visual Studio 2010 + SDK on the build server.

In this post, I’ll walk through the process of getting a C#/VB VSPackage project up and running on Team Foundation Build, without requiring an install of Visual Studio on the build agent machine. The same steps apply for editor extensions or other extensibility project types.

 

Once you’ve configured the build server and are ready to try out a build, you’ll probably see something like the following error in your build log:

The imported project "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\VSSDK\Microsoft.VsSDK.targets" was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.

Step #1: Put Visual Studio SDK targets/tasks in source control

Since neither Visual Studio nor the Visual Studio SDK are installed on my build machine, the build complains about the missing Microsoft.VsSDK.targets file. This is simple enough to fix by doing the following:

  1. Create a folder at the root of your solution directory called “vssdk_tools”. We’ll be adding all the necessary targets, tasks, etc… to this folder and adding it to source control.
  2. Copy the contents of %ProgramFiles%\MSBuild\Microsoft\VisualStudio\v10.0\VSSDK into this directory.
  3. Add the contents of your vssdk_tools directory to source control.
    • If you’re using TFS Source Control, you can do this via the “tf add” command or through the Source Control Explorer tool window in Visual Studio 2010.
  4. Edit your project file to point to this new targets file. Change the line:
    <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\VSSDK\Microsoft.VsSDK.targets" />
       to
    <Import Project="..\vssdk_tools\Microsoft.VsSDK.targets" />

Let’s try checking in again and seeing where we are now:

image

Step #2: Put COMReference binaries in source control

The reason that MSBuild is trying to run AxImp.exe is because we have a collection of COMReference elements in our VSPackage project. Instead of registering these assemblies as COM components on the build server, let’s copy these binaries to our local project and add them as normal assembly references:

  1. Remove the following COMReferences from your project:
    • EnvDTE
    • EnvDTE80
    • EnvDTE90
    • EnvDTE100
    • Microsoft.VisualStudio.CommandBars
    • stdole
  2. Create a “binaries” folder in our VSPackage project
  3. “Add existing item…” on this binaries folder for the following assemblies:
    • %ProgramFiles%\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\EnvDTE.dll
    • %ProgramFiles%\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\EnvDTE80.dll
    • %ProgramFiles%\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\EnvDTE90.dll
    • %ProgramFiles%\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\EnvDTE100.dll
    • %ProgramFiles%\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\Microsoft.VisualStudio.CommandBars.dll
    • %ProgramFiles%\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\stdole.dll
  4. Select all the binary files and set the “Build Action” property to “None”.
  5. Re-add assembly references to the binaries you just added.
  6. Important: Select all the references and set the “Embed Interop Types” property to false. (You can select and change them all in one operation.)

Let’s check in and try another build on the server

image

Step #3: Manually set the VsSDKInstall locations

Let’s take a look at the actual line where we’re hitting the error in Microsoft.VsSDK.Common.targets:

<Target Name="FindSDKInstallation" Condition="'$(VsSDKInstall)'==''">
<FindVsSDKInstallation SDKVersion="$(VsSDKVersion)">

The reason this task needs to run is because the VsSDKInstall property (and friends) hasn’t been set yet. Let’s use the “vssdk_tools” folder we had set up earlier. Edit your project file again, and add the following properties to the first <PropertyGroup> element:
 

<VsSDKInstall>..\vssdk_tools</VsSDKInstall>
<VsSDKIncludes>$(VsSDKInstall)\inc</VsSDKIncludes>
<VsSDKToolsPath>$(VsSDKInstall)\bin</VsSDKToolsPath>

Clearly, this won’t work until we actually have the corresponding files from the Visual Studio SDK also checked in to those directories. Let’s do that now:

  1. Copy the contents of %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Inc to vssdk_tools\inc
  2. Copy the contents of %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Tools\Bin to vssdk_tools\bin
  3. Add all these new files to source control

Let’s checkin again and see where we are now:

image

Step #4: Set VsSDKToolsPath as an Environment Variable

Hmmm…this one is a bit tricky. It turns out that some of the VSSDK build tasks rely on not only the $(VsSDKToolsPath) MSBuild property, but they also rely on this being set as an environment variable. We can do that fairly easily with an inline build task which we can add to our project file:

<UsingTask TaskName="SetVsSDKEnvironmentVariables" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<ProjectDirectory Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
System.Environment.SetEnvironmentVariable("VsSDKToolsPath", System.IO.Path.GetFullPath(ProjectDirectory + @"\..\vssdk_tools\bin"));
</Code>
</Task>
</UsingTask>
<Target Name="SetVsSDKEnvironmentVariables" BeforeTargets="VSCTCompile">
<SetVsSDKEnvironmentVariables ProjectDirectory="$(MSBuildProjectDirectory)" />
</Target>

Let’s cross our fingers and try again:

image

Step #5: Use 32-bit MSBuild.exe

By default, TFS will use the x64 version of MSBuild.exe (assuming you’re on a 64-bit server). Since the VSCT assembly is 32-bit only, it will fail to load in a 64-bit process. To use 32-bit MSBuild.exe on the server (if you’re using Team Foundation Build), simply edit the build definition and change Process => Advanced => MSBuild Platform to “X86” instead of “Auto”.

One more try:

 

image

 

 

Step #6: Add other VSSDK Assemblies to source control

In step 3, we only added the COMReferences to source control. Now, let’s do a similar procedure with the other assemblies:

  1. Remove the following assembly references from your project:
    • Microsoft.VisualStudio.OLE.Interop
    • Microsoft.VisualStudio.Shell.10.0
    • Microsoft.VisualStudio.Shell.Immutable.10.0
    • Microsoft.VisualStudio.Shell.Interop
    • Microsoft.VisualStudio.Shell.Interop.10.0
    • Microsoft.VisualStudio.Shell.Interop.8.0
    • Microsoft.VisualStudio.Shell.Interop.9.0
    • Microsoft.VisualStudio.TextManager.Interop
  2. “Add existing item…” on the binaries folder for the following assemblies:
    • %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v2.0\Microsoft.VisualStudio.OLE.Interop.dll
    • %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v2.0\Microsoft.VisualStudio.Shell.Interop.dll
    • %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v2.0\Microsoft.VisualStudio.Shell.Interop.8.0.dll
    • %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v2.0\Microsoft.VisualStudio.Shell.Interop.9.0.dll
    • %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v2.0\Microsoft.VisualStudio.Shell.Interop.10.0.dll
    • %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v2.0\Microsoft.VisualStudio.TextManager.Interop.dll
    • %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v4.0\Microsoft.VisualStudio.Shell.10.0.dll
    • %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Assemblies\v4.0\Microsoft.VisualStudio.Shell.Immutable.10.0.dll
  3. Select all the binary files and set the “Build Action” property to “None”.
  4. Re-add assembly references to the binaries you just added.

One more time…

image

 

Step #7: Add Microsoft.VisualStudio.Shell.Immutable.10.0.dll to the tools directory

CreatePkgDef.exe is the tool used to create a pkgdef file for your VSPackage. The tool itself relies on types defined in the Microsoft.VisualStudio.Shell.Immutable.10.0 assembly. On a machine with Visual Studio 2010 installed, there isn’t a problem loading it since the assembly is installed to the GAC. However, on our build server, the assembly is not in the GAC since Visual Studio 2010 isn’t installed.

In order to allow CreatePkgDef.exe to find the assembly, we can simply add a copy of this binary in our vssdk_tools\bin directory. Do the following:

  1. Copy Microsoft.VisualStudio.Shell.Immutable.10.0.dll from our project binaries folder to vssdk_tools\bin.
  2. Add this new file to source control and checkin

image

Step #8: Add the VSIXManifestSchema.xsd to allow VsixManifest validation on the build server

This task fails because the build task can’t locate the XML schema file for VSIXManifest to do schema validation. We could just switch this task off, but since it’s a good idea to run this validation when we build, let’s do what’s necessary to enable validation. There is an MSBuild property we can set to override this location on our build server. Simply add the following property to the first <PropertyGroup>:

<VsixSchemaPath>$(VsSDKInstall)\schemas\VSIXManifestSchema.xsd</VsixSchemaPath>

Of course, we also need to add the schema file to this directory:

  1. Copy the VSIXManifestSchema.xsd file from “%ProgramFiles%\Microsoft Visual Studio 10.0\Xml\Schemas” to vssdk_tools\schemas.
  2. Add VSIXManifestSchema.xsd to source control

Let’s try again and see where we are:
image

Step #9: Disable deployment to the Experimental Instance

To make ‘F5’ debugging work without any work by the user, by default, there are some additional targets that run in Microsoft.VsSDK.Common.targets. These targets ‘deploy’ your extension’s files to the Experimental instance for debugging. Since this scenario doesn’t make sense for our build server, we should disable it.

The Visual Studio SDK includes a project property page for configuring this property:

image

Note that you will probably want a separate build configuration for your build server (to set this property to false) so that developers can still easily debug their package on a client machine.

If you prefer to configure this directly in your project file instead of using the UI, use the following property:

<DeployExtension>False</DeployExtension>

Let’s see how this affects our build:

image

Success!!

Hooray! If I check the build output directory, we now see that we have a VSIX file that was built on the server:

image