Nov 24

In the first post I gave a quick overview of what our deployment script does and why you’d want one, then the second post went over pre-deployment steps. This post will go over the actual deployment steps we take to publish our site. Like the last post, most all of this code will probably be pretty self explanatory.

function deploy_and_prime_site
{
	modify_web_config_for_production
	precompile_site
 
	archive_site
	delete_extra_live_site_backups
 
	try
	{
		stop_dns_caching
 
		foreach ($server in $servers_production)
		{
			deploy_site_to $server
			preload_site_on $server
		}
 
		foreach ($server in $servers_production)
		{
			ensure_error_emails_are_working_on $server $live_url
		}
	}
	finally
	{
		remove_hosts_file_entries
		start_dns_caching
	}
}

This is the function the build target actually calls into. The part you’ll care about here is where it loops through the known production servers and deploys the site to each one in tern. The “preloading” of the site, checking for functioning error emails, and DNS caching stuff is some of the post-deployment steps we take, which I’ll discuss in the next post.

IIS Remote Control

Here’s how we control IIS remotely (this is IIS7 on Windows 2008 R2 – not sure how much changes for different versions):

function execute_on_server($target_server, [scriptblock]$script_block)
{
	$secure_password = ConvertTo-SecureString $security_password -AsPlainText -Force
	$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $security_full_user, $secure_password
 
	Invoke-Command -ComputerName $target_server -Credential $credential -ScriptBlock $script_block
}
 
function stop_iis_on($target_server)
{
	echo "Stopping IIS service on $target_server..."
 
	execute_on_server $target_server { & iisreset /stop }
}
 
function start_iis_on($target_server)
{
	echo "Starting IIS service on $target_server..."
 
	execute_on_server $target_server { & iisreset /start }
}

The secret sauce to getting this to work is the execute_on_server function. The actual stop & start methods just execute standard iisreset commands (which is a built-in command line tool w/IIS). So the top function converts our plain text server username & passwords in the build script into a SecureStringPSCredential object. Not the most secure way to do this, I’m sure (hence the -Force parameter), but it’s working for us. After connecting to the remote machine, it executes the given script block with those credentials (like the execute_with_secure_share function from the last post). In order to make this work though, you’ll need to give some lovin’ on your build server and web servers:

  • Make sure all boxes have at least PowerShell 2.0 with WinRM 2.0 (which is what allows the remote machine command execution)
  • On each web server, you’ll need to run this one time command from a PowerShell prompt: Enable-PSRemoting

Deployment

With that out of the way, the actual deployment part is pretty easy – it’s just copying files after all:

properties {
	$siteWebFolder_name = $solution_name
 
	$ident_file = "Content\ident.txt"
}
 
function pause_for($seconds)
{
	sleep -s $seconds
}
 
function deploy_site_to($server)
{
	echo "*** Beginning site deployment to $server."
 
	$compiled_site = "$compiled_site\*"
	$web_share = "\\$server\$share_web"
	$live_site_path = "$web_share\$siteWebFolder_name"
 
	stop_iis_on $server
 
	pause_for 10 #seconds, to give IIS time to release file handles.
 
	execute_with_secure_share $web_share {
		echo "Deleting the existing site files on $server ($live_site_path )."
		delete_directory_with_errors "$live_site_path \*"
 
		echo "Copying the new site files (from $compiled_site) to $server."
		copy_directory $compiled_site $live_site_path 
 
		echo "Creating ident file at $live_site_path."
		"$server" > "$live_site_path\$ident_file"
	}
 
	start_iis_on $server
}

Stop IIS, give it a few seconds, copy files, start IIS. Like I said – simple. If your situation can’t allow this for some reason (perhaps you have a more complicated load balancing scheme or whatever), you can expand as needed. We actually deploy several sites and a few console apps at the same time so everything’s in sync. The ident file is a simple way for us to find out which server a user’s on for troubleshooting purposes. We can navigate to the url + /Content/ident.txt and it’ll have the server’s name.

Conclusion

Other than the actual remote manipulation of the servers, which we keep to a pretty minimum IIS start & stop, there’s not much to this part of the build either. This code provides a good jumping off point for customization to your setup, as well as some helper methods you can hopefully make use of. The next post will wrap up this series by showing some of the post-deployment steps we take.

Nov 18
How *not* to hash passwords
icon1 Darrell Mozingo | icon2 Misc. | icon4 November 18th, 2010| icon3No Comments »

We were stupid back in the day (OK, a year or two, but who’s counting?). When we started our latest project it was a given that we’d be hashing passwords for storage. The most obvious and easiest way to do it was the good ‘ol (password + hash).GetHashCode(). Done and done. We moved on to the next feature and never gave it a second thought.

As it turns out though, using GetHashCode() for password hashing purposes is, well, pretty stupid and irresponsible. GetHashCode() was never intended to be stable across .NET versions or even architectures (x86 vs x64), and apparently the framework spec documents call this out. In fact, its results have changed slightly between .NET 3.5 and 4.0, which is what we were just upgrading to when I noticed this. Similar changes aparently occurred between 1.1 and 2.0 too.

For example, the GetHashCode() hash of the string “password” from .NET 3.5 is -733234769, while the hash from that exact same string in .NET 4.0 is -231203086. Scary, huh?

In light of that, we switched to using the SHA512Managed class to generate our hashes. Switching our code over wasn’t an issue (DRY for the win!), but having to email our customers to enter new passwords and security questions, which we also hashed the same way, wasn’t exactly fun. Not knowing their passwords apparently does have a downside! Here’s how we’re generating our hash codes now:

private const string _passwordSalt = "some_long_random_string";
 
public static string CalculateSaltedHash(string text)
{
	var inputBytes = Encoding.UTF8.GetBytes(text + _passwordSalt);
	var hash = new SHA512Managed().ComputeHash(inputBytes);
 
	return Convert.ToBase64String(hash);
}

Yay? Nay?

Nov 12

In the first post I gave a quick overview of what our deployment script does and why you’d want one. This post will go over some of the pre-deployment steps we take. Most all of this code will probably be pretty self explanatory, but I know just having something to work off of is a huge boost to starting your own, so here ya go.

function modify_web_config_for_production($webConfig)
{
	echo "Modifying $webConfig for production deployment."
 
	$xml = [xml](Get-Content $webConfig)
	$root = $xml.get_DocumentElement();
 
	$root."system.web".compilation.debug = "false"
 
	$xml.Save($webConfig)
}

Given the path to a web.config file, this function switches off the debug flag (and any other changes you’d need). Being a dynamic language, you can access XML keys quite easily. You’ll need the quotes around system.web since there’s the dot in the name though. Also, if you need access to any of the app.settings keys, you can use something like: $xml.selectSingleNode('//appSettings/add[@key="WhateverYourKeyIs"]').value = "false".

function precompile_site($siteToPreCompile, $compiledSite)
{
	echo "Precompiling $siteToPreCompile."
 
	$virtual_directory = "/"
 
	exec { & $tools_aspnetCompiler -nologo -errorstack -fixednames -d -u -v $virtual_directory -p "$siteToPreCompile" $compiledSite }
}

This little beauty precompiles the site (located in the $siteToPreCompile directory, with the results output to the $compiledSite directory) using the ASP.NET compiler. I prefer to copy the actual compiler executable into the project folder even though it’s installed with the Framework. Not sure why. Anyway, $tools_aspnetCompiler can either point locally, or to C:\Windows\Microsoft.NET\Framework\vwhatever\aspnet_compiler.exe. You can also configure the options being passed into the compiler to suit your needs.

function execute_with_secure_share($share, [scriptblock]$command)
{
	try
	{
		echo "Mapping share $share"
		exec { & net use $share /user:$security_full_user $security_password }
 
		& $command
	}
	finally
	{
		echo "Unmapping share $share"
		exec { & net use $share /delete }
	}
}

This is more of a helper method that executes a given script block (think of it as an Action or anonymous code block in C#) while the given share is mapped with some known username and password. This is used to copy out the site, create backups, etc. I’ll leave the $security_full_user & $security_password variable declarations out, if you don’t mind! We just put them in plain text in the build script (I know, *gasp!*).

properties {
	$share_web = "wwwroot"
	$servers_production = @("server1", "server2")
	$live_backup_share = "\\server\LiveSiteBackups"
 
	$number_of_live_backups_to_keep = 10
}
 
function archive_current_live_site
{
	$current_datetime = Get-Date -Format MM_dd_yyyy-hh_mm_tt
	$one_of_the_production_servers = $servers_production[0]
 
	$web_share_path = "\\$one_of_the_production_servers\$share_web"
 
	echo "Archiving $web_share_path"
 
	$full_backup_path = "$web_share_path\*"
	$full_archive_file = "$live_backup_share\$current_datetime.zip"
 
	execute_with_secure_share $web_share_path {
		execute_with_secure_share $live_backup_share {
			exec { & $tools_7zip a $full_archive_file $full_backup_path } 
		}
	}
}
 
function delete_extra_live_site_backups
{
	execute_with_secure_share $live_backup_share {
		$current_backups = Get-ChildItem $live_backup_share -Filter "*.zip" | sort -Property LastWriteTime
		$current_backups_count = $current_backups.Count
 
		echo "Found $current_backups_count live backups out there, and we're aiming to keep only $number_of_live_backups_to_keep."
 
		$number_of_backups_to_kill = ($current_backups_count - $number_of_live_backups_to_keep);
 
		for ($i = 0; $i -lt $number_of_backups_to_kill; $i++)
		{
			$file_to_delete = $current_backups[$i]
			$extra_backup = "$live_backup_share\$file_to_delete"
 
			echo "Removing old backup file: $extra_backup"
			delete_file $extra_backup
		}
	}
}

These pair of methods create a backup of the current live site and make sure we’re only keeping a set number of those backups from previous runs, to keep storage and maintenance in check. Nothing too complicated. To create the backup, we just farm out to 7-Zip to compress the directory, which is ran withing nested execute_with_secure_share calls from above, which map the web server file share and backup file share. Likewise, the second method just gets a count of zip files in the storage directory and deletes the oldest ones in there until the total count gets to a specified count.

Conclusion

That’s the basics for what we do pre-deployment. Again, not really that complicated, but it can give you a starting point for your script. I’ll go over our actual deployment steps in the next post, then follow that up with some post-deployment goodness. I know, you can’t wait.