Using TeamCity for ASP.NET development: Backup

This is the fourth part of a blog post series on using TeamCity continuous integration server from JetBrains for asp.net development.

Here are the links to all the parts of the series:

  1. Installing/Configuring TeamCity for use with IIS
  2. MSBuild requirements for web package publishing
  3. Deploying via Web Deploy
  4. Backup (pre-deploy) [this post]

4. Backup (pre-deploy)

I’ve already written about how to install your TeamCity server on IIS.
I’ve also written about what you need to install in order to make TeamCity, or MSBuild to be precise, package your asp.net based project.
Lastly, I’ve also written about how to deploy your web deploy offline package to a server (or servers) of your choice.

This post, which is more on the “extra stuff account”, will focus on how to backup before deploying to a set of servers which may or may not be included in ones deployment process.
The post will cover:

  1. how to backup a web site through web deploy, using the PowerShell cmdlets
  2. how I’ve incorporated this step in TeamCity’s build process.

Backup – using web deploy

The PowerShell cmdlets for web deploy has a few options for backup:

  • Backup-WDServer: used to backup an IIS server
  • Backup-WDSite: used to backup content and configuration (optionally) of an IIS site
  • Backup-WDApp: more or less the same as above but only content and works for applications (good article explaining difference found here). A site can be an application though.
  • Backup-WDSqlDatabase and Backup-WDMySQLDatabase: are used to backup databases.

I’m focusing on performing a content backup, not configuration of either IIS server nor IIS site, if something goes wrong during deployment of the new web application package.
So my choice will fall on the Backup-WDApp function and I’ve written a small script as a “wrapper” around that function.


Update 2013-06-15

I’ve actually published a nuget package that contains the script I will discuss somewhat in this post. (Install-Package WDP.Backup)
The source code for this is as always up on github for you to watch, download or improve (with a fork + pull-request). The repo also contains a quite extensive documentation wiki.


Here’s a quick (not complete) look at my script for performing a backup:

function Invoke-Backup {
	param([string]$site)
...
}

You’d use this PowerShell module by calling the Invoke-Backup function. The function takes an optional parameter:

  1. $site: the name/path of the application as seen in IIS manager to backup, e.g. “test.xample.com”.
    This parameter can be used either to override the name otherwise specified through a .publishsettings file (which we’ll see shortly) or to simply backup a local site (on the same box).

There are more things you can change though which we’ll see in a bit but first let’s see what the backup function really does.

The backup

foreach($file in $cfg.SourcePublishSettingsFiles) {
	$parameters = BuildParameters $site $file
	$backup = Backup-WDApp @parameters -ErrorAction:Stop

You can see that the function loops over a set of files (or paths to files). We’ll get back to these shortly but let’s just say that they represent the servers that contain the site to backup.
In each loop a set of parameters are constructed from the site name and the file path. We’ll look at how this is performed next.
The function finally calls the Backup-WDApp function with the constructed parameters and we’re done.

Parameter Builder

function BuildParameters {
	param(
		[string]$application,
		[string]$sourcePublishSettings
	)

	$parameters = @{
		Application = $application
	}

	if($sourcePublishSettings) {
		$parameters.SourcePublishSettings = $sourcePublishSettings
	}

	return $parameters

}

This function uses splatting to build a parameter-array (a key/value hashtable really) that we can send into the Backup-WDApp function.
It does so conditionally by looking at both the parameters that we send into the “builder” and also by looking at the configuration parameters which we’ll look at now.

Setting Configuration Properties

$cfg = @{
	SourcePublishSettingsFiles = @($null)
	BackupLocation = (Get-Location).Path + .\Backups
	PublishArtifacts = if($Env:TEAMCITY_DATA_PATH){$true} else {$false}
}

That’s some of the conventional configuration properties that the Invoke-Backup function will use if you don’t tell it otherwise.

  • SourcePublishSettingsFiles : Default is to backup sites on the local IIS ($null). Each file, path to a file, should hold information about a remote server and how to connect to it (read more here).
  • BackupLocation: Default is to place all the backups in the current directory and a sub folder called ‘Backups’.
  • PublishArtifacts: Default is that the script determines if the code executes within TeamCity and sets to either true or false depending on that.

So how do you change the conventions? We’ll show an example of that when talking about how to wire this up within TeamCity. That’s next!

Backup – using TeamCity

I’m not going to tell you how to get the script into TeamCity, I’ll just show you how I’ve done it…OK?!

Get the code into version control

Install-Package WDP.Backup

Is that it?
Well, it adds a solution-level nuget package which in short means that nothing is added as content to your project.
The .psm1 file (the PowerShell module) is just downloaded to the root of the solution and into the packages folder.

I prefer to keep it this way and handle updates and such from within Visual Studio and/or the nuget package manager.

Get the code into TeamCity

The “problem” with a solution-level package if don’t check-in/commit the actual package contents is that MSBuild package restore doesn’t support solution-level packages OOB. I’ve discussed this in this post if you’re interested.

However, if you instead use TeamCity’s nuget:installer which I’ve explained here you’re in better luck. Just remember to put this step first in the build chain.

image

Execute the code within TeamCity

Well now that we’ve got a script, let’s wire up TeamCity to use it via adding a new build step with a PowerShell runner.
Remember that this step has to come before the deployment step though.

image

The PS runner is pretty much configured by default and it uses script mode: source code which is highlighted above and discussed below.

Import-Module .\packages\WDP.Backup.%WDP.Backup.Version%\wdp.backup.psm1

Set-Properties @{
    SourcePublishSettingsFiles = @('%cfg.ServersToBackup')
}

Invoke-Backup

1) Import the PS module. I’ll discuss that configuration parameter (%WDP.Backup.Version%) in just a bit.
2) Tell the PS module which servers/sites to backup. I’ll talk about this some more shortly.
3) Invoke the backup.

That’s it!!

About versioning

image

That’s an image of the checkout directory. You can see that the packages folder has the WDP.Backup package.
It’s now version 2.0.3 and I don’t want to hard-code that into the path that imports it.
So I have extracted that into a configuration parameter that I can set at the project level and then override it if I need to at the build configuration level.

image

I can even set it specifically for just one custom build execution, if I’d like to test out a new version or something.

image

About the .publishsettings file/s

image

I’ve extracted which servers (and sites) to backup into a configuration parameter just so I have the ability to change it easily.
The files are in my checkout directory, and in source control, so the path/s are simple:

image

Summary

image

Now when the build runs, the backup is created and added as an artifact to build. As you remember from before the PS module knows whether or not it runs within TeamCity.
But how did I do that?

Adding Artifacts

TeamCity has the ability for build interaction and specifically the opportunity to add artifacts while the build is still running. (##teamcity[publishArtifacts ‘<path>’])
With that in mind all we need to do is alter the script to call a function after the backup has taken place with the location of the backup. Something like this:

function PublishArtifacts([string] $path) {
	if($cfg.PublishArtifacts) {
		Write-Host "##teamcity[publishArtifacts '$path']"
	}
}

And the result:

image

, , , , ,