Automate SharePoint Solution Builds with Visual Studio Extensions For Windows SharePoint Services (VSEWSS) 1.3
In this walkthrough, I’ll demonstrate how to achieve automated builds and continuous integration by creating a build script for VSEWSS 1.3 solutions. The patterns and practices SharePoint Guidance (11 drop) contains an example build script that this article is based on, but I found some issues with it in practice that I’d like to share. Before we get started, I’d like to talk about the design of the extensions and some of the problems you may encounter with them.
VSEWSS 1.3 stores information used to build the wsp file in a directory named “pkg” that is not included in the project. This works fine for individual development on a single machine, but not so well for team development and build automation. The pkg directory must be manually included in the project so files needed to build the wsp are included in source control. If you forget to do this, your teammates won’t be able to build the wsp, and you risk losing deployment configuration. Furthermore, if you later add a new feature, you also have manually add the new feature subdirectory to the project. In addition to storing configuration information, the pkg directory is also where solution files (feature.xml for example) are automatically generated. When you package the wsp (or refresh in the wsp view), the files in the pkg directory must be checked out to be re-generated. This also causes problems on the build server because the files in the pkg directory are read-only because they are checked into source control. An improved design would be to store wsp configuration information in a folder/file that is part of the project and use the pkg directory only for generating packages based on the wsp configuration files. So now that you are aware of some of the team development “gotchas” with VSEWSS 1.3, I’ll show you, step-by-step, how to configure automated builds.
If you just want to see the build script, get it here. Update: get a simplified build script here .
If you have not installed TFS Build, you will need to install it using the TFS installation media. You will also need to configure a service account such as TFSBuild, and add the account to the TFS server group Team Foundation Licensed Users and the Team Project(s) group Build Services.
To get started, configure the build agent. In the team project select Builds > Manage Build Agents…
Enter the build agent properties for your build server
Create a new build definition by selecting Builds > New Build Definition…
Enter a name for the build definition
Configure the workspace. Note: all files in the source control folder will be downloaded to the local folder you specify, so be sure the location you specify has enough free space
Create the project file (TFSBuild.proj) by selecting the version control folder where you want it stored and selecting Create…
Select the Visual Studio solution to build and click Next
Select the configuration(s) to build and click Next
If you want to run unit tests or code analysis during the build, configure it and click Finish
Now that the project file is created, click OK
Configure how many builds you would like to retain and click OK. Note: you should at least keep the latest of failed or partially succeeded builds or the build log file you need to troubleshoot will be deleted
Enter a share where the build files will be dropped and click OK
Configure what triggers the build, either manually, on a schedule or when files are checked in (continuous integration)
Test the build by highlighting the build definition and selecting Queue New Build…
Click Queue
At this point you should have a working build, but it doesn’t package the wsp file yet
Next, we’ll modify the build script to package the wsp file. The build steps are as follows:
- Clean up any workspaces / files left by the previous build packaging
- Build the Visual Studio solution
- Delete the workspace that was created by the build
- Create a workspace for the wsp packaging build
- Open a second instance of the IDE and package the solution using the /package switch
- Copy the wsp to the drop folder
In addition to this process, a nice to have is to display the packaging steps in the GUI, so we’ll use the BuildStep task to accomplish that
To get started, get the latest version of the Team Build Types folder
Check out the TFSBuild.proj file. Note: when developing the build script, you will have to check it out to work on it, and check it back in to test it
Add the custom tasks used by the build. Although we could accomplish what we need to using TF commands, these tasks are installed with TFS and work well
<Project DefaultTargets="DesktopBuild" xmlns="https://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<!-- These tasks are used by the Team Build process defined in this file -->
<UsingTask TaskName="Microsoft.TeamFoundation.Build.Tasks.DeleteWorkspaceTask"
AssemblyFile="$(TeamBuildRefPath)\Microsoft.TeamFoundation.Build.Tasks.VersionControl.dll" />
<UsingTask TaskName="Microsoft.TeamFoundation.Build.Tasks.CreateWorkspaceTask"
AssemblyFile="$(TeamBuildRefPath)\Microsoft.TeamFoundation.Build.Tasks.VersionControl.dll" />
Add the variables needed for wsp packaging. Note: there is probably a better way to determine the location of the IDE
<PropertyGroup>
<!--
Variables added for VSeWSS 1.3 builds
-->
<!-- IDEPath
The path in which DevEnv and TF reside
-->
<IDEPath>C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE</IDEPath>
<!-- TempWorkspaceName
Workspace name, must not match an existing namespace name
-->
<TempWorkspaceName>NightlyBuildTempWorkspace</TempWorkspaceName>
</PropertyGroup>
Add the solution to build. Note: this will build the binaries, but will not generate the wsp file
<ItemGroup>
<SolutionToBuild Include="$(BuildProjectFolderPath)/../../Intranet/Intranet.sln">
<Targets></Targets>
<Properties></Properties>
</SolutionToBuild>
</ItemGroup>
Add the target to clean up any files left by the previous build packaging (if the previous build failed or was cancelled). This target is called during the build just before the workspace is created to build the solution
<!-- Before the build workpace is initialized -->
<Target Name="BeforeInitializeWorkspace" >
<!-- Delete temporary workspace (if left by previous build) -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Deleting temporary workspace "$(TempWorkspaceName)" (if left by previous build).">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="$(TempWorkspaceName)"
DeleteLocalItems="true" />
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="$(TempWorkspaceName)"
DeleteLocalItems="false" />
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- Error Occurred -->
<OnError ExecuteTargets="MarkBuildStepAsFailed" />
</Target>
Add the error handling target. If any of the other steps fail, they will call this target using ExecuteTargets
<!-- Handles custom errors -->
<Target Name="MarkBuildStepAsFailed">
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Failed" />
</Target>
In the AfterCompile target, add the step to delete the workspace that was automatically created by the build
<!-- After the solutions are compiled -->
<Target Name="AfterCompile" >
<!-- Delete build workspace -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Deleting build workspace "$(WorkspaceName)".">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="$(WorkspaceName)"
DeleteLocalItems="false" />
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- END Delete build workspace -->
Just below that, add the build step to create the temporary workspace
<!-- Create temporary workspace -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Creating temporary workspace "$(TempWorkspaceName)".">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<CreateWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
BuildDirectory="$(BuildDirectory)"
SourcesDirectory="$(SolutionRoot)"
Name="$(TempWorkspaceName)"
Comment="Temporary workspace">
</CreateWorkspaceTask>
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- END Create temporary workspace -->
Add the step to package the solution. This first marks all files in the pkg directory as not read only--the packaging will not be able to overwrite these files otherwise. Then it runs the IDE and uses the /package switch to generate the wsp. Finally, it copies the wsp to the drop folder. If you had multiple wsp files to build, you could repeat the steps below or refactor into a target
<!-- Place projects to package here -->
<!-- Build and package solution -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Packaging " Contoso Intranet solution".">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<!-- The extensions modify files in the pkg directory, so those files cannot read only-->
<Exec Command="attrib -R "$(BuildDirectory)\Contoso\Intranet\Deployment\pkg\*.*" /S /D" />
<!-- Open a second instance of the dev environment and build using /package switch -->
<Exec Command=""$(IDEPath)\devenv" "$(BuildDirectory)\Contoso\Intranet\Intranet.sln" /deploy debug /package" />
<!-- Copy to drop location -->
<Exec Command="xcopy "$(BuildDirectory)\Contoso\Intranet\Deployment\bin\debug\Contoso.Intranet.wsp" "$(DropLocation)\$(BuildNumber)\" /E /Y /R" />
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- END Build and package solution -->
Add the build step to delete the temporary workspace we created
<!-- Delete temporary workspace -->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Message="Deleting temporary workspace "$(TempWorkspaceName)".">
<Output TaskParameter="Id" PropertyName="StepId" />
</BuildStep>
<DeleteWorkspaceTask
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="$(TempWorkspaceName)"
DeleteLocalItems="false" />
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(StepId)"
Status="Succeeded" />
<!-- END Delete temporary workspace -->
Check the project back in to source control, and queue the build to test it
Comments
Anonymous
June 23, 2009
It seems every week more and more great content appears! These could almost become daily Coffee break posts! WSP Carsten Keutmann: Beginning SharePoint Development (Bonus part #3 WSPBuilder overview)Anonymous
June 27, 2009
Important thing to mention here is that this will only work if your team has TFS Build Server...Anonymous
July 31, 2009
This is great information, but I have Visual Source Safe 6.0. Can somebody please give an example of how a build script could be done using Visual Studio 2008 and Visual Source Safe?Anonymous
August 17, 2009
Do I have to install and configure VSeWSS 1.3 on the build machine? I am a bit concerned about the IIS modifications needed to get VSeWSS 1.3 running on a dev machine. Since the build doesn't need to deploy I wonder what is needed. XCopy steps for configuring the build server to build VSeWSS 1.3 projects would be greatly appreciated! PhilippAnonymous
August 19, 2009
You will need to install the extensions on the build machine. The extensions install a service which is used for packaging and deploying.Anonymous
August 19, 2009
Thx for your reply. My concern is that IIS needs to be installed and configured on the build machine. Furthermore additional accounts need to be added to the Administrator group to run the service. These are things that are normally not done to a build machine. Is the service required for the packaging build step? Obviously we would not need to deploy on the build machine since its only job is to produce the WSP file.Anonymous
January 29, 2010
Instead of using VSeWSS 1.3, can I do this with WSPBuilder?Anonymous
February 22, 2010
The comment has been removed