Easy Testing Of Your Web.Config Transformations

One of the powerful features of ASP.Net 4.0 was the introduction of web.config transformations that meant you could now do with ASP.Net out-of-the-box what you would have previously done with some form of custom XSLT transform in your build process. One thing that is not that easy is to test the outputs from the transformations.

One option is the simple online web.config tester from the guys over at AppHarbor.  While that’s great, personally I don’t want to round-trip my web.config files over the Net just to test something I should be able to do locally. The result was that after some playing I found a way to test locally utilising msbuild with the right parameters.

The one proviso to this simple test working is that you have successfully compiled the code for you web application (either via msbuild command-line or inside Visual Studio).  This test will fail if the binaries or other inputs for your package aren’t available.

All you need to do is issue this command line:


MSBuild.exe YourProject.csproj /T:Package /P:DeployOnBuild=True;Configuration=YourConfiguration

You will now find in the ‘obj’ folder of the project you targetted a set of folders – if you dig through them you will find a “TransformsWebConfig” sub-folder that will contain the output result of your transform.

Happy Days!

Updated!

New in Visual Studio 2012 is the ability to “Preview Transform” on configuration files that utilise the above technique.  Open your solution in Visual Studio, expand the transformation node of your config file, select the transform to review and choose “Preview Transform” from the menu.  Grab a look at screenshots either at Hanselman’s blog or here.

Configure Mercurial Pull with HTTP Authentication for Jenkins on Windows

As part of the journey our team is going on in 2012 I am looking to migrate our processes and tools to a more scalable and maintainable state.  One of the first items to look at was the replacement of that early cornerstone of CI – CruiseControl.  Our existing server was aging and was running on a platform that meant Azure SDK support was unavailable.  To resolve this I moved to a new machine and new CruiseControl build only to discover that the Mercurial features don’t work.

After an initial clone the CC builds failed – having spent time investigating I decided to see what else was about that allowed us to do centralised builds.  Bearing in mind this is a transition state I didn’t want invest a huge amount of time and money in a solution and found the Jenkins CI offering online.  As a bonus it has support (via plugins) for Mercurial and MSBuild so it seemed to fit the bill.

However…

As is always the way things didn’t quite work as I wanted.  Our existing build setup utilises HTTP authentication and not SSH to connect to our hosted Mercurial repositories and when I setup new Jenkins builds I was seeing that each build would clone the entire remote repository again which in some instances is not desirable given their size.  This is what I saw at the top of my build console log:

Started by user anonymous
Building in workspace E:\Program Files (x86)\Jenkins\jobs\XXXXXXXXX\workspace
[workspace] $ hg.exe showconfig paths.default
ERROR: Workspace reports paths.default as https://AAAAAAAA@bitbucket.org/BBBBB/XXXXXXXXX
which looks different than https://AAAAAAAA:PPPPPPPP@bitbucket.org/BBBBB/XXXXXXXXX
so falling back to fresh clone rather than incremental update

So I was scratching my head about this – we’ve always used the inline auth approach but it looked like in this instance the Jenkins Mercurial plugin / Mercurial client was deciding that the inline authentication made the source different enough that it wouldn’t even attempt to do a pull.

Digging around I discovered that the hg command line supports use of “ini” files to set global parameters.  I could theoretically have just set this in the baseline ini file that comes with the Mercurial client but as that file says “any updates to your client software will remove these settings.”

So, how to resolve?

Here are the steps I followed to resolve:

1. Create a new non-admin windows login with a valid password.

2. Grant this login control of the Jenkins folder (and any others that Jenkins may read / write / update).

3. Open up Windows Services and change the Jenkins service logon to be the new logon from (1).

4. Create a .hgrc file in the new windows login’s “C:\Users\<username>\” folder with these contents:

[auth]
bitbucket.prefix = bitbucket.org/BBBBB
bitbucket.username = AAAAAAAA
bitbucket.password = PPPPPPPP

5. Restart the Jenkins Windows Service.

Now when you run your build you will see a much more expected site:

Started by user anonymous
Building in workspace E:\Program Files (x86)\Jenkins\jobs\XXXXXXXX\workspace
[workspace] $ hg.exe showconfig paths.default
[workspace] $ hg.exe pull --rev default
[workspace] $ hg.exe update --clean --rev default
0 files updated, 0 files merged, 0 files removed, 0 files unresolved

Hopefully this post will save you some time if you’re trying to achieve the same sort of thing for your team.

Amazon AWS Elastic Load Balancers and MSBuild – BFF

Our jobs take us to some interesting places sometimes – me, well, recently I’ve spent a fair amount of time stuck in the land of Amazon’s cloud offering AWS.

Right now I’m working on a release process based around MSBuild that can deploy to a farm of web servers at AWS.  As with any large online offering ours makes use of load balancing to provide a reliable and responsive service across a set of web servers.

Anyone who has managed deployment of a solution in this scenario is most likely familiar with this approach:

  1. Remove target web host from load balancing pool.
  2. Update content on the web host and test.
  3. Return web host to load balancing pool.
  4. Repeat for all web hosts.
  5. (Profit! No?!)

Fantastic Elastic and the SDK

In AWS-land load balancing is provided by the Elastic Load Balancing (ELB) service which, like many of the components that make up AWS, provides a nice programmatic API in a range of languages.

Being focused primarily on .Net we are we are happy to see good support for it in the form of the AWS SDK for .NET.  The AWS SDK provides a series of client proxies and strongly-typed objects that can be used to programmatically interface with pretty much anything your AWS environment is running.

Rather than dive into the detail on the SDK I’d recommend downloading a copy and taking a look through the samples they have – note that you will need an AWS environment in order to actually test out code but this doesn’t stop you from reviewing the code samples.

Build and Depoy

As mentioned above we are looking to do minimal manual intervention deployments and are leveraging MSBuild to build, package and deploy our solution.  One item that is missing in this process is a way to take a target machine out of the load balancer pool so we can deploy to it.

I spent some time reviewing existing custom MSBuild task libraries that provide AWS support but it looks like many of them are out-of-date and haven’t been touched since early 2011.  AWS is constantly changing so being able to keep up with all it has to offer would probably require some effort!

The result is that I decided to create a few custom tasks so that I could use for registration / deregistration of EC2 Instances from one or more ELBs.

I’ve included a sample of a basic RegisterInstances custom task below to show you how you go about utilising the AWS SDK to register an Instance with an ELB.   Note that the code below works but that it’s not overly robust.

The things you need to know for this to work are:

  1. Your AWS Security Credentials (Access and Secret Keys).
  2. The names of the ELBs you want to register / deregister instances with.
  3. The names of the EC2 Instances to register / deregister.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using Amazon.ElasticLoadBalancing;
using Amazon.ElasticLoadBalancing.Model;

namespace TheFarm.MsBuild.CustomTasks.Aws.Elb
{
    /// <summary>
    /// Register one or more EC2 Instance with one or more AWS Elastic Load Balancer.
    /// </summary>
    /// <remarks>Requires the AWS .Net SDK.</remarks>
    public class RegisterInstances : Task
    {
         /// <summary>
         /// Gets or sets the load balancer names.
         /// </summary>
         /// <value>
         /// The load balancer names.
         /// </value>
         /// <remarks>The account associated with the AWS Access Key must have created the load balancer(s).</remarks>
         [Required]
         public ITaskItem[] LoadBalancerNames { get; set; }

         /// <summary>
         /// Gets or sets the instance identifiers.
         /// </summary>
         /// <value>
         /// The instance identifiers.
         /// </value>
         [Required]
         public ITaskItem[] InstanceIdentifiers { get; set; }

         /// <summary>
         /// Gets or sets the AWS access key.
         /// </summary>
         /// <value>
         /// The aws access key.
         /// </value>
         [Required]
         public ITaskItem AwsAccessKey { get; set; }

         /// <summary>
         /// Gets or sets the AWS secret key.
         /// </summary>
         /// <value>
         /// The aws secret key.
         /// </value>
         [Required]
         public ITaskItem AwsSecretKey { get; set; }

         /// <summary>
         /// Gets or sets the Elastic Load Balancing service URL.
         /// </summary>
         /// <value>
         /// The ELB service URL.
         /// </value>
         /// <remarks>Will typically take the form: https://elasticloadbalancing.region.amazonaws.com</remarks>
         [Required]
         public ITaskItem ElbServiceUrl { get; set; }

         /// <summary>
         /// When overridden in a derived class, executes the task.
         /// </summary>
         /// <returns>
         /// true if the task successfully executed; otherwise, false.
         /// </returns>
         public override bool Execute()
         {
             try
             {
                  // throw away - to test for valid URI.
                  new Uri(ElbServiceUrl.ItemSpec);

                  var config = new AmazonElasticLoadBalancingConfig { ServiceURL = ElbServiceUrl.ItemSpec };

                  using (var elbClient = new AmazonElasticLoadBalancingClient(AwsAccessKey.ItemSpec, AwsSecretKey.ItemSpec, config))
                  {
                        foreach (var loadBalancer in LoadBalancerNames)
                        {
                              Log.LogMessage(MessageImportance.Normal, "Preparing to add Instances to Load Balancer with name '{0}'.", loadBalancer.ItemSpec);

                              var initialInstanceCount = DetermineInstanceCount(elbClient, loadBalancer);

                              var instances = PrepareInstances();

                              var registerResponse = RegisterInstancesWithLoadBalancer(elbClient, loadBalancer, instances);

                              ValidateInstanceRegistration(initialInstanceCount, instances, registerResponse);

                              DetermineInstanceCount(elbClient, loadBalancer);
                        }
                  }
             }
             catch (InvalidInstanceException iie)
             {
                   Log.LogError("One or more supplied instances was invalid.", iie);
             }
             catch (LoadBalancerNotFoundException lbe)
             {
                   Log.LogError("The supplied Load Balancer could not be found.", lbe);
             }
             catch (UriFormatException)
             {
                   Log.LogError("The supplied ELB service URL is not a valid URI. Please confirm that it is in the format 'scheme://aws.host.name'");
             }

             return !Log.HasLoggedErrors;
        }

        /// <summary>
        /// Prepares the instances.
        /// </summary>
        /// <returns>List of Instance objects.</returns>
        private List<Instance> PrepareInstances()
        {
            var instances = new List<Instance>();

            foreach (var instance in InstanceIdentifiers)
            {
                Log.LogMessage(MessageImportance.Normal, "Adding Instance '{0}' to list.", instance.ItemSpec);

                instances.Add(new Instance { InstanceId = instance.ItemSpec });
            }
            return instances;
        }

        /// <summary>
        /// Registers the instances with load balancer.
        /// </summary>
        /// <param name="elbClient">The elb client.</param>
        /// <param name="loadBalancer">The load balancer.</param>
        /// <param name="instances">The instances.</param>
        /// <returns>RegisterInstancesWithLoadBalancerResponse containing response from AWS ELB.</returns>
        private RegisterInstancesWithLoadBalancerResponse RegisterInstancesWithLoadBalancer(AmazonElasticLoadBalancingClient elbClient, ITaskItem loadBalancer, List<Instance> instances)
        {
            var registerRequest = new RegisterInstancesWithLoadBalancerRequest { Instances = instances, LoadBalancerName = loadBalancer.ItemSpec };

            Log.LogMessage(MessageImportance.Normal, "Executing call to add {0} Instances to Load Balancer '{1}'.", instances.Count, loadBalancer.ItemSpec);

            return elbClient.RegisterInstancesWithLoadBalancer(registerRequest);
        }

        /// <summary>
        /// Validates the instance registration.
        /// </summary>
        /// <param name="initialInstanceCount">The initial instance count.</param>
        /// <param name="instances">The instances.</param>
        /// <param name="registerResponse">The register response.</param>
        private void ValidateInstanceRegistration(int initialInstanceCount, List<Instance> instances, RegisterInstancesWithLoadBalancerResponse registerResponse)
        {
            var postInstanceCount = registerResponse.RegisterInstancesWithLoadBalancerResult.Instances.Count();

            if (postInstanceCount != initialInstanceCount + instances.Count)
            {
                 Log.LogWarning("At least one Instance failed to register with the Load Balancer.");
            }
        }

        /// <summary>
        /// Determines the instance count.
        /// </summary>
        /// <param name="elbClient">The elb client.</param>
        /// <param name="loadBalancer">The load balancer.</param>
        /// <returns>integer containing the instance count.</returns>
        private int DetermineInstanceCount(AmazonElasticLoadBalancingClient elbClient, ITaskItem loadBalancer)
        {
             var response = elbClient.DescribeLoadBalancers(new DescribeLoadBalancersRequest { LoadBalancerNames = new List<string> { loadBalancer.ItemSpec } });

             var initialInstanceCount = response.DescribeLoadBalancersResult.LoadBalancerDescriptions[0].Instances.Count();

             Log.LogMessage(MessageImportance.Normal, "Load Balancer with name '{0}' reports {1} registered Instances.", loadBalancer.ItemSpec, initialInstanceCount);

             return initialInstanceCount;
        }
    }
}

So now we have a task compiled into an assembly we can reference that assembly in our build script and invoke the task using the following syntax:


    <RegisterInstances LoadBalancerNames="LoadBalancerName1" InstanceIdentifiers="i-SomeInstance1" AwsAccessKey="YourAccessKey" AwsSecretKey="YourSecretKey" ElbServiceUrl="https://elasticloadbalancing.your-region.amazonaws.com" />

That’s pretty much it… there are some vagaries to be aware of – the service call for deregistration of an Instance returns prior to the instance being fully de-registered with the load balancer so don’t key any deployment action directly off of that return – you should perform other checks first to make sure that the instance *is* no longer registered prior to deploying.

I hope you’ve found this post useful in showing you what is possible when combining the AWS SDK with the extensibility of MSBuild.

Fixing Packaging Of Web Projects On Your .Net Build Server

On my current project I’m running up the build and deployment environment and hit a roadblock that took me a little while to fathom.  Hopefully reading this might save you some time if you’re having this issue.

The Scenario

1. A build server that does not have Visual Studio installed but has an appropriate .Net SDK that allows you to compile projects successfully.

2. The MSDeploy package on the server – I get mine via the Web Platform Installer (or Web PI for short).

3. A project that you know has valid deployment settings – typically one you can build and package using Visual Studio locally.

4. When building on the server you compile (build) everything OK but the packaging fails silently.

If this sounds like your situation (or similar to it) read on to find a solution.

The Clue

It took me a while to work this out and to be honest the Google Gods were not much use to me (including Scott Guthrie’s blog on all this – see if you can find the follow on blog on automating packaging he hints at).

I tried a range of things before I came across a post on Stack Overflow that pointed me in the right direction.  It refers to the much maligned Microsoft.WebApplication.targets that is installed along with Visual Studio but which is gloriously missing when you build a clean server without Visual Studio.  You’ve probably come across that file before because trying to build without it with Web Application projects ends up with nasty errors being emitted from MSBuild:

error MSB4019: The imported project “C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets” was not found. Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.

So you know how to fix that – you copy across the necessary files from a machine with Visual Studio and recreate that folder structure on your build server.  Done.

The Fix

The Stack Overflow post specifically mentions that understanding how the MSDeploy stuff works basically boils down to reading the contents of the  Microsoft.WebApplication.targets file.

Hang on, what’s that got to do with packaging?

So I cracked open the targets file and sure enough at one point in it it reads clearly (including good grammar):

<!–This will be overwrite by ..\web\Microsoft.Web.Publishing.targets when $(UseWPP_CopyWebApplication) set to true–>

OK, so now I’m a bit surprised… didn’t MSDeploy lay down some MSBuild support for me? Nope.

At this stage I switched to my working development box and sure enough found a ‘Web’ folder sitting at the same level as the ‘WebApplications’ one.

Web Folder Highlighted in Yellow.

Web Folder Highlighted in Yellow.

I zipped up the contents of this folder and copied to my build server and placed them in the right location (C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\WebApplications\Web\).

After this change I re-ran my packaged build and found that the expected build steps and output (a zip and some manifest files) were produced.

So, Hollywood ending to this story then!

Hope it saves you some time.