Apr 27

I finally got around to implementing help screens on our site recently. We needed a system that would enable our domain peeps to update the help text directly with no intervention from us, along with being easy to implement and maintain on our end. I ended up using flat HTML files and a jQuery modal dialog (Colorbox), which has support for asynchronously loading those HTML files from disk when needed. The one thing we didn’t want to do with this solution was give our domain peeps production server access or the responsibility of keeping those HTML files up to date on the servers – I could only imagine the chaos that’d ensue from that.

Solution: use our build script & build server to handle it for us.

We gave our domain peeps commit access to the repository – thankfully we’re still on SVN, as I’m sure their heads will explode when we switch to a DVCS. This provides nice versioning and accountability features if someone messes up (imagine that), and gives us a hook for the build server. All help files are contained in a hierarchy under a folder that’s appropriately named HelpFiles. I checked out just that folder from the source tree on their machines and gave them a quick commit/update spiel. We created empty HTML files for them, and they went about their way filling them all in.

Now on to the more interesting part, our build script. As I’ve mentioned previously, we’re using psake. Here’s the relevant properties and task:

properties {
	$scm_hidden_dir = ".svn";
 
	$executing_directory = new-object System.IO.DirectoryInfo $pwd
	$base_dir = $executing_directory.Parent.FullName
 
	$source_dir = "$base_dir\src"
	$build_dir = "$base_dir\build"
	$build_tools_dir = "$build_dir\tools"
 
	$share_web = "wwwroot"
	$servers_production = @("server1", "server2")
 
	$security_user = "user_with_write_access"
	$security_password = "pa55w0rd"
 
	$tools_robocopy = "$build_tools_dir\robocopy\robocopy.exe"
 
	$help_folder = "HelpFiles"
	$help_local_dir = "$source_dir\$project_name.Web\$help_folder"
	$deployTarget_help = "$project_name\$help_folder"
}
 
task publish_help {
	foreach ($server in $servers_production)
	{
		$full_server_share = "\\$server\$share_web"
 
		exec { & net use $full_server_share /user:$security_user $security_password }
 
		& $tools_robocopy $help_local_dir $full_server_share\$deployTarget_help /xd $scm_hidden_dir /fp /r:2 /mir
 
		# See page 33 of the help file in the tool's folder for exit code explaination.
		if ($lastexitcode -gt 3)
		{
			Exit $lastexitcode
		}
 
		exec { & net use $full_server_share /delete }
	}
}

There’s an array of production server names, which we iterate over and use the net command built into Windows to map its wwwroot share using a different username & password than the current user (this allows the build server to run as an unprivileged user but still access needed resources).

Then we use the surprisingly awesome Robocopy tool from Microsoft, which is basically xcopy on steroids, to copy out the help files themselves. The xd flag is excluding the hidden .svn folders, the fp flag is displaying full path names in the output (for display in the build output from TeamCity later), the r flag is telling it to only retry failed file twice (as opposed to the default million times!), and the mir flag is telling it to mirror the source directory tree to the destination, including empty folders and removing dead files.

We can’t use psake’s built in exec function to run Robocopy, as exec only checks for non-zero return codes before considerng it a failure. Of course, just to be different, Robocopy only fails if its return code is above 3 (1 = one or more files copied successfully, 2 = extra files or folders detected, and there is no 3). So we check the return code ourselves and exit if Robocopy failed. We then delete the share, effectively making the machine forget the username/password associated with it.

With that done, we created a new build configuration in TeamCity and had it check the repository for changes only to the help file directory by adding +:src/Project.Web/HelpFiles/** to the Trigger Patterns field on the Build Triggers configuration step.

That’s pretty much it. Our domain peeps have been pretty receptive to it so far, and they love being able to edit the help files, commit, and see them live only a minute or two later. We loved not having to pull all that text from the database on each page load and not having to create editing/viewing/versioning/etc tools around such a process. It’s a win-win.

Apr 3
2010 Goals – April Update
icon1 Darrell Mozingo | icon2 Goals | icon4 April 3rd, 2010| icon3No Comments »

First quarter’s down. In full disclosure fashion, here’s how I’m doing so far:

Books

  1. Code Complete – Steve McConnellNot started.
  2. Patterns of Enterprise Application Architecture – Martin FowlerNot started.
  3. Applying Domain-Driven Design and Patterns – Jimmy Nilsson
  4. Working Effectively With Legacy Code – Michael FeathersNot started.

Tools/Techniques/Processes @ Work

  • Move from SVN to Git (and learn more about Git in the process). – Learning about Git and bugging our sys admin about setting up Git, but haven’t actually started yet.
  • Move from CC.NET to Team City for our build server.
  • Build a more robust build script and management process – including production deployment and database migration scenarios. – Well underway.

Involvement

  • Develop an idea I was given for an open source project and get it live to see what happens. – In progress.
  • At least 24 blog posts (I’m not going to say 2 per month as I’m getting married this summer and I’m certain I won’t be able to maintain a schedule around it). – 4 down (ouch!).
  • At least 3 feature/patch submissions to open source projects. – Not started.

Coding

  • Get a version 1 out there on at least 1 of the 3 product ideas I have floating around. – Not started.
  • Keep working on a good working knowledge of Ruby & Ruby on Rails (and use it to build the product mentioned above). – Playing and reading.

So-so. Lets see about next quarter.

Apr 2

A while back I wrote a small series on creating a basic build script and setting up a build server (part 1, part 2, and part 3). I used NAnt and CruiseControl.NET in that series, but alluded to a few other options for each. I recently got around to switching our build script from NAnt to psake, which is written in PowerShell, and switching our build server from CruiseControl.NET to JetBrain’s TeamCity. I’ll give a quick overview of our new build script here, which I’ll use to build on in future posts showing a few of the more interesting things that suddenly became much easier with this setup, and in a few cases, possible at all.

To start with, you’ll want to make sure you have the latest versions of PowerShell (2.0) and psake. Here’s the basics of our build script:

$ErrorActionPreference = 'Stop'
 
Include ".\functions_general.ps1"
 
properties {
	$project_name = "MainApplication"
	$build_config = "Debug"
}
 
properties { # Directories
	$scm_hidden_dir = ".svn";
 
	$executing_directory = new-object System.IO.DirectoryInfo $pwd
	$base_dir = $executing_directory.Parent.FullName
 
	$source_dir = "$base_dir\src"
 
	$build_dir = "$base_dir\build"
	$tools_dir = "$base_dir\tools"
	$build_tools_dir = "$build_dir\tools"
 
	$build_artifacts_dir = "$build_dir\artifacts"
	$build_output_dir = "$build_artifacts_dir\output"
	$build_reports_dir = "$build_artifacts_dir\reports"
 
	$transient_folders = @($build_artifacts_dir, $build_output_dir, $build_reports_dir)
}
 
properties { # Executables
	$tools_nunit = "$tools_dir\nunit\nunit-console-x86.exe"
	$tools_gallio = "$tools_dir\Gallio\Gallio.Echo.exe"
	$tools_coverage = "$build_tools_dir\ncover\ncover.console.exe"
	$tools_coverageExplorer = "$build_tools_dir\ncover_explorer\NCoverExplorer.Console.exe"
}
 
properties { # Files
	$solution_file = "$source_dir\$project_name.sln"
 
	$output_unitTests_dll = "$build_output_dir\$project_name.UnitTests.dll"
	$output_unitTests_xml = "$build_reports_dir\UnitTestResults.xml"
	$output_coverage_xml = "$build_reports_dir\NCover.xml"
	$output_coverage_log = "$build_reports_dir\NCover.log"
	$output_coverageExplorer_xml = "$build_reports_dir\NCoverExplorer.xml"
	$output_coverageExplorer_html = "$build_reports_dir\NCover.html"
}
 
properties { # Skip coverage attributes
	$skipCoverage_general = "Testing.SkipTestCoverageAttribute"
}
 
task default -depends unit_test_coverage
 
task clean {
	$transient_folders | ForEach-Object { delete_directory $_ }
	$transient_folders | ForEach-Object { create_directory $_ }
}
 
task compile -depends clean {
	exec { msbuild $solution_file /p:Configuration=$build_config /p:OutDir=""$build_output_dir\\"" /consoleloggerparameters:ErrorsOnly }
}
 
task unit_test_coverage -depends compile {
	exec { & $tools_coverage $tools_nunit $output_unitTests_dll /nologo /xml=$output_unitTests_xml //reg //ea $skipCoverage_general //l $output_coverage_log //x "$output_coverage_xml" //a $project_name }
	exec { & $tools_coverageExplorer $output_coverage_xml /xml:$output_coverageExplorer_xml /html:$output_coverageExplorer_html /project:$project_name /report:ModuleClassFunctionSummary /failMinimum }
}

As the second line alludes to, you can break functions out into separate files and include them back into the main one. Here’s functions_general.ps1:

function delete_directory($directory_name)
{
	Remove-Item -Force -Recurse $directory_name -ErrorAction SilentlyContinue
}
 
function create_directory($directory_name)
{
	New-Item $directory_name -ItemType Directory | Out-Null
}

This script will build our project and run the unit tests, producing a coverage report we can display later inside Team City. Much of this maps loosely one to one against the NAnt version discussed in my past series, and there’s plenty of articles/posts online explaining this stuff in much more detail than I can here. Note that all the pieces that can “fail” the script are wrapped in exec, which will execute the code block (i.e. lambda/anonymous delegate) and basically alert the build server if it fails. Not too difficult, at least for now 🙂

As for getting this to work with Team City, if you specify the runner as a command line and point it at a batch file with these contents:

@echo off
cls
powershell -Command "& { Set-ExecutionPolicy Unrestricted; Import-Module .\build\tools\psake\psake.psm1; $psake.use_exit_on_error = $true; Invoke-psake '.\build\build.ps1' %*; Remove-Module psake}"

You’ll be golden. This batch allows the build server to run the script (perhaps setting unrestricted execution isn’t the smartest from a security standpoint, but oh well), sets up the psake environment, tells psake to raise its warnings in a way that TeamCity can pick up on, executes your build script, and tears down the psake environment. Looks a little complicated, but it’s just a bunch of smaller commands strung together on one line, and you shouldn’t have to look at it again.