George Durzi

in

November 2008 - Posts

MOSSCamp 2008 Recap

We had about 90 people show up for the second annual MOSSCamp yesterday at Clarity. We had capped the registration at 100 and had a waiting list of 30 people, so I'm really happy with the turnout.

Thank you to Microsoft for sponsoring the event - and paying for the food and beverages. Thanks to Wrox, O'Reilly, and MSPress for hooking us up with plenty of books for giveaways.

Matt Morse put together a great post recapping the sessions that we had throughout the day, be sure to check it out.

Larry Clarkin (Midwest Architect Evangelist from Microsoft) hosted the Airing of Grievances for the second year in a row. After Larry posted the grievances on his blog last year, members of the SharePoint community - as well as the product team - chimed in and answered them.

The code is posted on CodePlex at http://www.codeplex.com/MOSSCamp. I'll be updating it with the PowerPoint decks from the presenters early next week.

Many thanks to Matt Morse for helping organize MOSSCamp, as well as Anthony Handley, Paul Schaeflein, Dan Herzog, Darrin Bishop, Burt Floraday, Jay Ritchie, Peter Walke, and Todd Bleeker for all their effort.

We joked at first that it was either really brave (or really stupid) to build a small application throughout the course of the day, it turned out great!

See you next year!

TFS Builds for SharePoint Projects - Part 2

In the last post in this series - TFS Builds for SharePoint Projects - Part 1 - I demonstrated how to use a build target in your Visual Studio project in such a way that either a desktop or TFS build can generate a WSP for your SharePoint Solution.

<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <MAKECAB>"C:\Windows\System32\makecab.exe"</MAKECAB>
    <DiskDirectory1>"$(OutDir)"</DiskDirectory1>
    <DiskDirectory1 Condition="HasTrailingSlash($(OutDir))">"$(OutDir)."</DiskDirectory1>
  </PropertyGroup>
  <Target Name="OfficeSpaceFeaturePackage">
    <Copy ContinueOnError="true" SourceFiles="$(TargetPath)" 
          DestinationFolder="DeploymentFiles"/>
    <Copy Condition="'$(IsDesktopBuild)'=='false'" SourceFiles="$(TargetPath)" 
          DestinationFiles="$(SourceDir)\DeploymentFiles\$(TargetFileName)"/>
    <Exec Command="$(MAKECAB) /F DeploymentFiles\Cab.ddf /D 
          CabinetNameTemplate=$(MSBuildProjectName).wsp /D 
          DiskDirectory1=$(DiskDirectory1)" />
    <Delete ContinueOnError="true" Files="DeploymentFiles\$(TargetFileName)"/>
  </Target>
  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\
                  MSBuild.Community.Tasks.Targets"/>
  <UsingTask AssemblyFile="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\
             MSBuild.Community.Tasks.dll" 
             TaskName="MSBuild.Community.Tasks.MAKECAB" />
</Project>

Since I wrote that post, I've been doing a lot of reading about using Features to package up SharePoint Solutions. I came across a great post by Chris O'Brien in which he argues that Features aren't the answer to everything - that they can add unnecessary overhead to a project. Definitely worth reading, and makes it more obvious that there is more than one correct way of doing things in the world of SharePoint development.

In this post, I'll add more functionality to the TFS build definition to:

  • Demonstrate using an external configuration file specific to a particular environment
  • Copy the generated WSP to a drop location on a SharePoint WFE
  • Automatically create the necessary scripts to install, activate, delete, or upgrade the SharePoint Solution

Configuration File

If you maintain different source control repositories for your project, e.g. Dev, Main, and Production, builds originating out of each repository can use their own set of configuration values during a build.

I like creating a file called Environment.proj which contains my configuration values and then referencing it from TFSBuild.proj.

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <CopySharePointWSPTo>\\WFEServer\Drop</CopySharePointWSPTo>
    <SolutionDeploymentUrl>http://portal</SolutionDeploymentUrl>
    <STSADM>"%commonprogramfiles%\microsoft shared\web server extensions\12\bin\stsadm.exe"</STSADM>
  </PropertyGroup>
</Project>

The value of the configuration items are specific to the individual environment. I can then reference this from TFSBuild.proj like this:

<Import Project="Environment.proj"/>

You can then reference these variables using the MSBuild variable syntax, e.g. $(CopySharePointWSPTo). Be careful not to name your variables with any of the MSBuild reserved names. If that occurs, the last definition is the one that takes precedence over the others.

Build Target

In TFSBuild.proj, override the AfterBuild target:

  <Target Name="AfterDropBuild">
  </Target>

We will populate this build target with the steps to copy the WSP to a drop location and also generate the deployment scripts.

Copy WSP to Drop Location

The BuildStep notation allows you to write out friendly messages to the build output, this is helpful for keeping track of what is going on when you are executing custom build steps.

 
  <Target Name="AfterDropBuild">
    <BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
      BuildUri="$(BuildUri)" Name="Copy SharePoint Solution WSP to drop location"
      Message="Copy SharePoint Solution WSP to drop 
               location $(CopySharePointWSPTo)\$(BuildNumber)">
      <Output TaskParameter="Id" PropertyName="CopyWSPStepId" />
    </BuildStep>
 
    <CreateItem Include="$(DropLocation)\$(BuildNumber)\**\*.wsp">
      <Output ItemName="SolutionWSPs" TaskParameter="Include"/>
    </CreateItem>
    <Copy SourceFiles="@(SolutionWSPs)" 
          DestinationFolder="$(CopySharePointWSPTo)\$(BuildNumber)"/>
  </Target>

We can then copy all the WSP files present in the build drop location on the TFS server to the location specified by $(CopySharePointWSPTo)\$(BuildNumber). Your TFS server is not usually going to be a SharePoint WFE, so you need to get those files off there.

CallTarget

From within the AfterBuild target, we want to call a custom build target to create the batch scripts necessary to perform different actions with the WSPs, e.g. install, delete, or upgrade.

<CallTarget Targets="CreateDeploymentScripts"/>

CreateDeploymentScripts Build Target

I'll preface this by saying that the scripts you're going to generate are going to depend on the Solution that you're deploying, e.g. the parameters to stsadm will differ based on how you're deploying the Solution.

For this example, I'm going to demonstrate how to create the "install" script for your solution, you can extend this based on your scenario.

  <Target Name="CreateDeploymentScripts">
    <BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
      BuildUri="$(BuildUri)" Name="Create Solution Deployment Scripts"
      Message="Create solution deployment batch scripts">
      <Output TaskParameter="Id" PropertyName="CreateScriptsStepId" />
    </BuildStep>
 
    <CreateItem Include="$(DropLocation)\$(BuildNumber)\**\*.wsp;">
      <Output ItemName="SolutionPackages" TaskParameter="Include" />
    </CreateItem>
 
    <PropertyGroup>
      <InstallSolutionPackage>$(DropLocation)\$(BuildNumber)
                              \InstallSolutionPackage.bat</InstallSolutionPackage>
    </PropertyGroup>
 
    <WriteLinesToFile File="$(InstallSolutionPackage)" Overwrite="true" 
                      Lines="@SET STSADM=$(STSADM)"/>
    <WriteLinesToFile File="$(InstallSolutionPackage)"
      Lines="%STSADM% -o addsolution -filename 
      %22%(SolutionPackages.Filename)%(SolutionPackages.Extension)%22" />
    <WriteLinesToFile File="$(InstallSolutionPackage)"
      Lines="%STSADM% -o deploysolution -name 
      %22%(SolutionPackages.Filename)%(SolutionPackages.Extension)%22 
      -immediate -allowGacDeployment" />
    <WriteLinesToFile File="$(InstallSolutionPackage)" Lines="%STSADM% -o execadmsvcjobs" />
    <WriteLinesToFile File="$(InstallSolutionPackage)" Lines="pause"/>
 
    <CreateItem Include="$(DropLocation)\$(BuildNumber)\**\*.bat">
      <Output ItemName="DeploymentScripts" TaskParameter="Include"/>
    </CreateItem>
    <Copy SourceFiles="@(DeploymentScripts)" 
          DestinationFolder="$(CopySharePointWSPTo)\$(BuildNumber)"/>
  </Target>

You can see how we're writing out lines to the "InstallSolutionPackage.bat" file specified by the $(InstallSolutionPackage) variable. In this case we write out stsadm commands to add and deploy the solution and finally force all SharePoint timer jobs to execute.

We finally copy the generated files to the drop location at $(CopySharePointWSPTo)\$(BuildNumber).

Although there are ways to have MSBuild remotely execute these scripts, I'm going to stop here. You could provide a command line argument to the build that would instruct the build to execute a certain script, e.g. "upgrade" to run an upgrade script.

Resources

There were several articles and posts that I came across when researching this, I'm including them for reference: