TFS, Team Build, and Beyond Compare as a lighter XCopy

We’re using TFS 2008 and Team Build to compile and build a website mixed with ASP.Net and classic ASP. It works fine, but the only problem is the site is huge. It’s over over 2500 files and over 400 megs. That’s HUGE! We could XCopy that to our server, but pushing 400 megs every time feels like a little bit of overkill, especially when the majority of the changes are copy updates. We could also use a content management system for this, but we thought we would give try it with TFS.

First, it took forever to pull 400 megs out of source control every time we did a build. We can fix that.

<PropertyGroup>
    <IncrementalGet>true</IncrementalGet>
<PropertyGroup>

Done. Now the build will only pull down the files that changed. It’ll leave the files from previous build laying around on the server, but that’s OK.

Next, it takes forever to copy the 400 megs to the drop folder. We don’t even need to do it in the first place since we are going to xcopy the _PublishedWebsites contents elsewhere when we’re done. We can fix this problem with ease too.

<PropertyGroup>
    <SkipDropBuild>true</SkipDropBuild>
<PropertyGroup>

Now we won’t copy the build to the drop server.

So finally the XCopy. That was a little more tricky. We need to figure out only the files that change and transfer them, via FTP in out case. Luckily, Beyond Compare not only has a scripting interface that will let us work it into the build script, it also has the example script of how to do exactly what we want to do in the documentation. Win for us!

We can just tweak it a bit and we’re off to the races. Let’s make a script file called deploy.to.server.script and put it in the same folder as the TFSBuild.proj.

# %1 is source of the build to copy to the new server
# %2 is the outdir where the log should be written

#log the script actions
log verbose "%2\deployment.log.txt"

#set the comparison criteria
criteria timestamp size

#load source and target
# you could also use a UNC share
load "%1" "ftp://user:password@oursite.com"

# Move everything
filter "*.*"

#Sync the local files to the web site, creating empty folders
sync create-empty mirror:lt->rt

Next, invoke it in the build script.

<Exec
 Command="&quot;c:\Program Files\Beyond Compare 3\bcompare.exe"
 &quot;@$(MSBuildProjectDirectory)\deploy.to.server.script&quot;
&quot;$(SolutionRoot)\MyWebsite" &quot;$(OutDir)&quot;  \silent \closescript" >

We’re done. It’ll only copy the stuff we’ve changed and we’ve taken our build time from 25 minutes down to less than one.

Update: If you want to keep an eye on what you’re moving, add the following steps in the Beyond Compare script.

#load source and target
# you could also use a UNC share
load "%1" "ftp://user:password@oursite.com"

expand all
folder-report &
    layout:side-by-side &
    options:display-mismatches &
    title:"Deployment Report" &
    output-to:"%2\deployment.summary.html" &
    output-options:html-color

Sorry about the funny syntax highlighting. There is not “BeyondCompare” language available.

You’ll get a nice HTML report.
ss-20090129092919

Another Update: It looks like Beyond Compare will not tell you if an error occurs during the script. This is a huge bummer. If you get something like this in your script, it’ll still return 0 for the ExitCode and you won’t know it failed.

1/29/2009 11:40:42 AM  Fatal Scripting Error: Unable to load base folder
1/29/2009 11:40:42 AM  Script completed in 0.01 seconds

Yet another update: There is some new news on using Beyond Compare with Team Build.

One more update: Here is a copy of the edit to the TFSBuild.proj file. This gets added at the end of the file before the closing <Project> tag.

 	<PropertyGroup>
		<IncrementalGet>true</IncrementalGet>
	</PropertyGroup>

	<!-- Since we are doing the source side by side we deploy before
		copying to the the builds folder. We might consider changing this
		to AfterDropBuild and copy from there if we are doing the precompiled
		websites -->
	<Target Name="BeforeDropBuild">
		<CallTarget Targets="Deploy"/>
	</Target>

	<Target Name="Deploy">
		<!-- Create a Custom Build Step -->
		<BuildStep
			TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
			BuildUri="$(BuildUri)"
			Name="BeyondCompareDeployStep"
			Message="Deploying to webserver">
			<Output
				TaskParameter="Id"
				PropertyName="BeyondCompareDeployStepID" />
		</BuildStep>

		<!-- We are deploying the source side by side with the pages instead of
			doing the precompiled website-->
		<Exec Command="&quot;c:\Program Files\Beyond Compare 3\bcompare.exe&quot; &quot;@$(MSBuildProjectDirectory)\deploy.to.server.script" &quot;$(SolutionRoot)\source&quot;  &quot;\\myWebserver\path\to\mysite&quot;  &quot;$(DropLocation)\$(BuildNumber)&quot;  \silent \closescript" >
			<Output
				TaskParameter="ExitCode"
				PropertyName="BeyondCompareExitCode" />
		</Exec>

		<!-- These tests don't work as BeyondCompare will ALWAYS exit with code 0 from script -->
		<BuildStep
			Condition="'$(BeyondCompareExitCode)' == '0'"
			TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
			BuildUri="$(BuildUri)"
			Id="$(BeyondCompareDeployStepID)"
			Status="Succeeded"/>
		<BuildStep
			Condition="'$(BeyondCompareExitCode)' != '0'"
			TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
			BuildUri="$(BuildUri)"
			Id="$(BeyondCompareDeployStepID)"
			Status="Failed"/>
		<Error
			Condition="'$(BeyondCompareExitCode)'!='0'"
			Text="Deployment Failed" />
	</Target>

The olny other file is the deploy.to.server.script file. This file sits aside the TFSBuild.proj file and looks like this.

# %1 is the solution root of the build
# %2 is the target location (ftp or share) to deploy
# %3 is the outdir where the log should be written

#log the script
log verbose "%3\deployment.log.txt"

#set the comparison criteria
criteria timestamp size

#load source and target
load "%1"  "%2"

expand all
folder-report &
    layout:side-by-side &
    options:display-mismatches &
    title:"Deployment Report" &
    output-to:"%3\deployment.summary.html" &
    output-options:html-color

filter "*.*"

# we want to override all the read-only files that exist
option confirm:yes-to-all

#Sync the local files to the web site, creating empty folders
sync create-empty mirror:lt->rt

5 Comments so far

  1. Shay on February 3rd, 2009

    Could you please add to download a sample project that integrates this solution?
    Thx.

  2. mark on February 3rd, 2009

    @Shay Sure. I’ll add the entire edit to TFSBuild.proj and Beyond Compare script today.

  3. mark on February 3rd, 2009

    @Shay I hope that helps.

  4. Stretch on May 13th, 2009

    I am up against the exact same situation. We have classic ASP, .NET 1.0, 1.1, 2.0 and 3.5 mixed into our www site, intranet and extranet. We are replacing sourcesafe with TFS 2008 and SLOWLY moving to SharePoint. Any sample code, scripts, tips would be very much appreciated!!! We are loaded down with a lot of content files like pdf’s, pictures, etc. I was going to create a deployment server that has workspaces mapped to the different environments (test, stage, production) and use branching and mergin to deploy and promote changes.

  5. mark on May 13th, 2009

    @Stretch We’ve found FTP doesn’t work that well for us since we were having issues with timestamps being offset by GMT during the compare. Also, we had a hard time with read-only flags on files we were FTPing. We started using a UNC share and it works much better.

    Our BC script us the same as we’re using here with one change:
    filter “*.*”
    filter -”designer2007.zip”

    We are excluding some large files that we never change and don’t want to push, even if the time stamp changes.

    Our connection to our data center is really slow from the development offices where our TFS box lives. Our initial push takes 2+ hours, but after that it only takes a few minutes to pull down, build and push to the data center.

    We’re using all the script examples shown here.

Leave a Reply