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:
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:
- 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.
- Copy the contents of %ProgramFiles%\MSBuild\Microsoft\VisualStudio\v10.0\VSSDK into this directory.
- 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.
- 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:
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:
- Remove the following COMReferences from your project:
- EnvDTE
- EnvDTE80
- EnvDTE90
- EnvDTE100
- Microsoft.VisualStudio.CommandBars
- stdole
- Create a “binaries” folder in our VSPackage project
- “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
- Select all the binary files and set the “Build Action” property to “None”.
- Re-add assembly references to the binaries you just added.
- 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
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:
- Copy the contents of %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Inc to vssdk_tools\inc
- Copy the contents of %ProgramFiles%\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Tools\Bin to vssdk_tools\bin
- Add all these new files to source control
Let’s checkin again and see where we are now:
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:
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:
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:
- 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
- “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
- Select all the binary files and set the “Build Action” property to “None”.
- Re-add assembly references to the binaries you just added.
One more time…
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:
- Copy Microsoft.VisualStudio.Shell.Immutable.10.0.dll from our project binaries folder to vssdk_tools\bin.
- Add this new file to source control and checkin
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:
- Copy the VSIXManifestSchema.xsd file from “%ProgramFiles%\Microsoft Visual Studio 10.0\Xml\Schemas” to vssdk_tools\schemas.
- Add VSIXManifestSchema.xsd to source control
Let’s try again and see where we are:
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:
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:
Success!!
Hooray! If I check the build output directory, we now see that we have a VSIX file that was built on the server:
Comments
- Anonymous
May 11, 2011
I've been down this road of trying to make a build server without VS.I've found that when I am selling build automation to a larger IT team with less technical knowledge depth this kind of process ends up taking more time and frustrating people with errors than just installing VS on the build server and moving on. - Anonymous
May 12, 2011
Great post! - Anonymous
June 15, 2011
Awesome post!But the VS team needs to realize this is a HUGE pain point for those of us that admin CI systems. Getting buy-in from the Devs really isn't an issue, its all the manual work we now need to do. Which ironically is one thing CI is supposed to help with :) - Anonymous
February 24, 2015
Incredibly useful, saved me a ton of time. The step-by-step explanation and resolution of problems made it very credible. Step 3 references a <ProjectGroup> element, which I think is a typo for <PropertyGroup> since I placed it there and I was able to keep following the instructions.