Integrating PowerUp into Your Deployments – Step By Step

AKA Deploying a fully working NerdDinner website in 15 minutes and 50 lines of code

The goal of this post is to show, one straightforward step at a time, how you can integrate PowerUp into your solution.

We will start with downloading the “NerdDinner” MVC sample, and finish by having two completely independent and full working versions of the site on your local machine.

Don’t worry if this looks long – it is just because I haven’t left out a single detail. Also, the thing about PowerUp is that it is very layered – the deployment we are working on will just become gradually more sophisticated. You can actually stop at any step and you will have still produced something useful.

Here is some links to other parts of the page so you know where you are up to:

You can get everything going just from the steps below. If you prefer to have a fully working copy to look at, download the final solution as a zip file here (which is from the GitHub repo here).

Step Zero - Prerequisites

Remember to check you have the required prerequisites. Even better, make sure the QuickStart build runs fine. If it doesn’t, its unlikely that this step by step guide will work.

Step One – Download NerdDinner

Download the NerdDinner Codeplex Zip File and extract this anywhere on your machine. It should have the following contents.

Step Two – Download and copy PowerUp

Download PowerUp and place the contents of the contained _powerup folder into the root folder of NerdDinner. The end result will look like this:

Step Three – Create your nant build file

At the root of NerdDinner, create a new file called main.build. Within this file, add the following code:

<project default="build-package-common">
	<include buildfile="_powerup\common.build" />
	<property name="solution.name" value="NerdDinner" />
	<target name="package-project">
	</target>
</project>

In short, this includes a reference to the common build file, tells PowerUp the name of the solution file, and creates a (stub package-project) target.

Step Four – Run your first build

Open a commandline prompt at the root of NerdDinner. Within this prompt, run the following:

_powerup\nant\bin\nant

This will run nant on your main.build file. The output will look like:

To make this easy to run again, create a batch file (build_package_nant.bat) with the following code:

_powerup\nant\bin\nant
pause

Step Five – Alter the build to include the compiled output

As the build currently stands, PowerUp is happily compiling the NerdDinner solution, and creating a basic package folder.

You might notice that the output of the NerdDinner solution isn’t in there – we need to actually tell PowerUp what to include for this to happen.

To make this happen, alter main.build to include these lines:

<project default="build-package-common">
	<include buildfile="_powerup\common.build" />

	<property name="solution.name" value="NerdDinner" />

	<target name="package-project">
		<copy todir="${package.dir}\NerdDinnerWebsite" overwrite="true" flatten="false" includeemptydirs="true">
			<fileset basedir="${solution.dir}\NerdDinner">
				<include name="**"/>
				<exclude name="**\*.cs"/>
				<exclude name="**\*.csproj"/>
				<exclude name="**\*.user"/>
				<exclude name="obj\**"/>
				<exclude name="lib\**"/>
			</fileset>
		</copy>
	</target>
</project>

What we have added is a basic copy target to copy all the necessary files from NerdDinner into the package.

Run the build again, and you should see NerdDinnerWebsite appear inside the package folder.

With that folder having these contents:

Step Six – Start by deploying the files

You might recall that PowerUp is roughly split into two steps – building a package, and deploying that package. Up until now, we have been working on the packaging. Its time to start deploying.

To get this under way, create a file called deploy.ps1 in the root of the folder. Start with the contents:

include .\_powerup\commontasks.ps1

task deploy {
	import-module powerupfilesystem

	$packageFolder = get-location
	copy-mirroreddirectory $packageFolder\nerddinnerwebsite c:\sites\nerddinner
}

This (psake) file first dot includes the common tasks (to hook up with some useful defaults), then copies the contents of the NerdDinnerWebsite folder to c:\sites\nerddinner.

To execute this, open a command prompt in the _package directory and run:

deploy local

This will produce the following output:

And if you look in c:\sites\nerddinner, you will now see the contents of the NerdDinnerWebsite folder from the package.

To make later deployments easier, create a new file in the root called build_package_nant_deploy_local.bat with the contents:

_powerup\nant\bin\nant
cd _package
call deploy local
cd ..
pause

Step Seven – Create a website

During this step we will be altering the deployment script to automatically create a website pointing to the website folder. Alter deploy.ps1 to have the following contents:

include .\_powerup\commontasks.ps1

task deploy {
	import-module powerupfilesystem
	import-module powerupweb

	$packageFolder = get-location
	copy-mirroreddirectory $packageFolder\nerddinnerwebsite c:\sites\nerddinner

	set-webapppool "NerdDinner" "Integrated" "v4.0"
	set-website "NerdDinner" "NerdDinner" "c:\sites\nerddinner" "" "http" "*" 20001
}

Notice the extra lines at the bottom where we create an appool, and also create a website.

Rerun the build and deployment (build_package_nant_deploy_local.bat)

Thats the output of a successful website deployment!

Now browse to http://localhost:20001 to see the NerdDinner website in action.

Step Eight – Introducing deployment settings

At first look, you might think we have finished the job. We have a fully working website, after all. The problem is this package is only going to work on one environment. Take a look again at this snippet from deploy.ps1:

copy-mirroreddirectory $packageFolder\nerddinnerwebsite c:\sites\nerddinner
set-webapppool "NerdDinner" "Integrated" "v4.0"
set-website "NerdDinner" "NerdDinner" "c:\sites\nerddinner" "" "http" "*" 20001

This screams “hard coded to hell”. Its all very well deploying to c:\sites onto your local machine. But what about staging or live? They might have the web folder in e:\webroot. Also if you want to have two branches of this code base on the test server, how will you ensure they each have their own website? Thats where deployment profiles and settings come in.

Our first step in this direction is to introduce a settings file. Create the file settings.txt in the root, with the contents:

local
	website.name			NerdDinner
	web.root			c:\sites
	http.port			20001

Step Nine – Using the settings within deploy.ps1

Now you have settings, you can use these within your deploy.ps1. How? Just refer to them (by key) like you would any Powershell variable. So in this case, change your deploy.ps1 file to:

include .\_powerup\commontasks.ps1

task deploy {
	import-module powerupfilesystem
	import-module powerupweb

	$packageFolder = get-location
	copy-mirroreddirectory $packageFolder\nerddinnerwebsite ${web.root}\${website.name}

	set-webapppool ${website.name} "Integrated" "v4.0"
	set-website ${website.name} ${website.name} ${web.root}\${website.name} "" "http" "*" ${http.port}
}

Notice how this is almost the same script as from step seven. All that has changed is that we have replaced the hard coded file paths and website parameters with the setting names.

Run build_package_nant_deploy_local.bat and notice the website deploys again and still runs exactly as before.

Step Ten – Deploying to another environment

Now we have settings and a parameterised build file, we can easily deploy to other environments by simply adding additional deployment profiles. Normally this would target another server (test, staging, live etc), but in this case I’m going to assume you don’t have many other machines lying around so we are going to have to “fake it” by deploying to another location on your machine.

Start by changing the settings.txt file to:

default
	web.root	c:\sites

local
	website.name	NerdDinner
	http.port	20001

fakelive
	website.name	NerdDinnerFakeLive
	http.port	20002

This has created the new deployment profile, “fakelive”. Also notice the “default profile” – this is a special, reserved, profile that all others inherit from (so common settings don’t have to be repeated).

Now to deploy this new profile, all you need to do is rebuild the package and deploy to “fakelive”. The following batch file will do this:

_powerup\nant\bin\nant
cd _package
call deploy fakelive
cd ..
pause

This is absolutely identical to previous deployments except we have change the argument to “deploy” from “local” to “fakelive”.

Save this to build_package_nant_deploy_fakelive.bat in the root and run it. You will now have a second copy of the site, serving from http://localhost:20002.

This demonstrates the core tenet of PowerUp – the ability to deploy to any number of environments from a single package.

As a double check, your solution folder should now look like this:

Step Eleven – Introducing file templating (usually for configs)

Hopefully the above shows the value of being able to use settings with your build script. There is another very common case where deployments need to vary by environment. That is the difference in the contents of the files you are deploying. This is almost always config files, such as web.config or app.config.

There are various common existing techniques to deal with this issue, ranging from having multiple config files (web.config.staging, web.config.live) and swapping them around at deploy time, or using Visual Studio Config Transformations. PowerUp (by default) chooses a different technique – that of file templating, a ndusing the same settings file used by deploy.ps1 to substitute in the correct values at deploy time. We believe this centralization of settings holds massive advantages.

I’m going to show this in action by templating NerdDinner’s connectionstrings.config.

If you look at this file (I won’t paste it here, as its quite long), you will see 3 connection strings. At the moment they all bind to to |DataDirectory|, which looks in App_Data for the database files (NerdDinner.mdf etc).

Lets say, for the sake of argument, we want these files to exist somewhere else on disk, and we want this location to be different on each environment.

To achieve this, we are going to template the database file folder in connectionstrings.config and create a deployment profile setting to make this different for local and fakelive.

Step Twelve – Create a template for connectionstrings.config

Starting at the root folder:

  • Create a new folder called _templates.
  • Within that folder, create another folder called NerdDinnerWesbite
  • Copy into that folder the connectionstrings.config file from NerdDinner website
The end result should look something like this:

Open up connectionstrings.config and replace |DataDirectory| with “${database.folder}\”.  Doing this introduces the placeholders to be replaced during deployment. For example, the first setting should read:

<add name=”ApplicationServices” connectionString=”data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=${database.folder}\aspnetdb.mdf;User Instance=true” providerName=”System.Data.SqlClient”/>

Step Thirteen – Create the new setting for database.folder

Change settings.txt to now include the setting database.folder, ie:

default
	web.root		c:\sites
	database.folder		c:\databases\${website.name}

local
	website.name		NerdDinner
	http.port		20001

fakelive
	website.name		NerdDinnerFakeLive
	http.port		20002

Also notice how settings can reference each-other (c:\databases\${website.name} - a useful trick to keep the settings file compact.

Step Fourteen – Witness connectionstring.config being substituted during deployment

Run build_package_nant_deploy_local.bat to deploy another package.

Open C:\sites\NerdDinner\connectionstring.config – you should see that the placeholder for ${database.folder} has been replaced with “c:\databases\NerdDinner”.

Now run build_package_nant_deploy_fakelive.bat, and open C:\sites\NerdDinnerFakeLive\connectionstring.config. In this deployment, the value is “c:\databases\NerdDinnerFakeLive” instead.

This is config substitution in action, and hopefully shows how any part of the config file can be templated, handling all the common cases of connection strings, webservice urls etc.

Step Fifteen – Change main.build and deploy.ps1 to deploy the database files

To get the NerdDinner database files into these directories, we are going to need to firstly change the packaging to copy them from their current location in App_Data, and then change the deployment to copy them to their folders under c:\databases.

So first, change main.build to package the databases from app_data, ie:

<project default="build-package-common">
	<include buildfile="_powerup\common.build" />

	<property name="solution.name" value="NerdDinner" />

	<target name="package-project">
		<copy todir="${package.dir}\NerdDinnerDatabases" overwrite="true" flatten="false" includeemptydirs="true">
			<fileset basedir="${solution.dir}\NerdDinner\app_data">
				<include name="**"/>
			</fileset>
		</copy>
		<copy todir="${package.dir}\NerdDinnerWebsite" overwrite="true" flatten="false" includeemptydirs="true">
			<fileset basedir="${solution.dir}\NerdDinner">
				<include name="**"/>
				<exclude name="**\*.cs"/>
				<exclude name="**\*.csproj"/>
				<exclude name="**\*.user"/>
				<exclude name="obj\**"/>
				<exclude name="lib\**"/>
			</fileset>
		</copy>
	</target>
</project>

Then change deploy.ps1 to read:

include .\_powerup\commontasks.ps1

task deploy {
	import-module powerupfilesystem
	import-module powerupweb

	$packageFolder = get-location
	copy-mirroreddirectory $packageFolder\nerddinnerwebsite ${web.root}\${website.name}

	if (!(Test-Path ${database.folder}))
	{
		copy-mirroreddirectory $packageFolder\NerdDinnerDatabases ${database.folder}
	}

	set-webapppool ${website.name} "Integrated" "v4.0"
	set-website ${website.name} ${website.name} ${web.root}\${website.name} "" "http" "*" ${http.port}
}

The relevant addition is to the copying of the NerdDinnerDatabases folder from the package to the database directory (if it doesn’t already exist, to prevent overwriting).

Running build_package_nant_deploy_local.bat and build_package_nant_deploy_fakelive.bat should now distribute those database files to c:\databases\nerdinner and c:\databases\nerdinnerlive respectively.

Browser http://localhost:20001 and http://localhost:20002 to see the sites still ticking along.

If you perform a few functions (register an account etc), you will see they are running off independent databases.

Step Sixteen – Why you would want to execute parts of the deployment on other machines

Again, you might be excused for thinking we are done. The deployment and config files are now good for any number of environments.

But have a quick think. What would happen if wanted to a) run this script from a Continuous Integration server deploying to a test server or b) deploy files or create websites on more than one server (for example a load balanced environment).

At the moment, our deploy.ps1 has the severe restriction that it all runs on the same machine. In the CI case, this means that if this script was run, it would create the websites on the CI server (or agent). Hardly ideal!

But don’t worry, the solution is quite simple.

Step Seventeen – Run web-deploy remotely

Start by altering deploy.ps1 as follows:

include .\_powerup\commontasks.ps1

task deploy {
	run web-deploy ${web.servers}
}

task web-deploy {
	import-module powerupfilesystem
	import-module powerupweb

	$packageFolder = get-location
	copy-mirroreddirectory $packageFolder\nerddinnerwebsite ${web.root}\${website.name}

	if (!(Test-Path ${database.folder}))
	{
		copy-mirroreddirectory $packageFolder\NerdDinnerDatabases ${database.folder}
	}

	set-webapppool ${website.name} "Integrated" "v4.0"
	set-website ${website.name} ${website.name} ${web.root}\${website.name} "" "http" "*" ${http.port}
}

The change is fairly subtle: We have moved most of the script from the default “deploy” task to “web-deploy”, and changed “deploy” to call web-deploy.

The key here is the “run” command. This instructs PowerUp to run the web-deploy remotely on another machine.

To get this going, we need to add a few more things. First of all, we change the setting.txt to read:

default
	web.root		c:\sites
	database.folder		c:\databases\${website.name}

local
	web.servers		localmachine
	website.name		NerdDinner
	http.port		20001

fakelive
	web.servers		localmachine
	website.name		NerdDinnerFakeLive
	http.port		20002

The important change here is the declaring that (for both deployment profiles) the web server is the server “local”.

Next we need a new file, called servers.txt to describe some necessary details about “local”.

So, create servers.txt in the root, with the contents:

localmachine
	server.name			localhost
	remote.temp.working.folder	\\${server.name}\c$\packages
	local.temp.working.folder	c:\packages

Note that (unlike setting.txt, which has no rules about which keys you create), servers.txt must have all three settings (with those exact names) for each server. These settings simply let PowerUp know what the UNC path is for the server, and the folder where it can copy your package and run deployment scripts from.

Now that is all in place, run build_package_nant_deploy_local.bat again.

Notice how the output now changes:

At the key moment (the remote call to web-deploy), PowerUp automatically copies your package to the remote working folder, then starts the deployment again for the task web-deploy.

This can be done at any point in the script, to any server. You can even call out remotely when you are already remote (there are a lot more details to this that I will cover in later posts.)

Summary

Well there we have it. Hopefully that took way longer for me to write than it took for you to run through!

If you reflect on what has been done to make this all work, it might be quite startling how little it took. All we did to get this website buildable and deployable to multiple environments was to:

  • Copy the _powerup folder
  • Create main.build (23 lines)
  • Create settings.txt (13 lines)
  • Create deploy.ps1 (21 lines)
  • Create servers.txt (4 lines)
  • Template connectionstrings.config (won’t count this)
  • Create a few batch files (won’t count this either)
So thats 51 lines of code (ish). Close.
Remember that the zip file of this solution is available here.

Other Posts

Next (How To Use PowerUp in Your Deployments)

Previous (PowerUp Quickstart – A Rundown)

The PowerUp Series

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.