Building and Deploying Windows Azure Projects using MSBuild and TFS 2010

Slalom Consultant Joel Forman

Slalom Consultant Joel Forman specializes in cloud computing and the Windows Azure Platform.

Windows Azure tools for Visual Studio makes it very easy for a developer to build and deploy Windows Azure projects directly from within Visual Studio. This works fine for a single developer working independently. But for developers working on projects as part of a development team, there is the need to integrate the ability to build and deploy the project from source control via a central build process.

There are some good resources out there today that talk about how to approach this. Tom Hollander has a great blog post on Using MSBuild to deploy to multiple Windows Azure environments, which leverages an approach that Alex Lambert blogged about regarding how to handle the ServiceConfiguration file for different environments. However, the new Windows Azure Tools for Visual Studio 1.4 introduced some changes into this process that will now have to be accounted for, such as a separate Windows Azure targets file, and the concept of a Cloud configuration setting. I thought this would be a good opportunity for me to dive into updating our build process for Windows Azure projects to be in line with these changes. The following post will cover configurations in Visual Studio, packaging from Visual Studio and the command line, packaging on a build server, and finally deploying from a build server.

Before I dug in, I came up with some goals for my Windows Azure build process. Here is what was at the top of my list:

  • Being able to build, package, and deploy our projects to multiple Windows Azure environments from a build server using MSBuild
  • Being able to retain copies of our packages and configurations for our builds in Windows Azure BLOB Storage for safe keeping, history, easy rollback scenarios, etc.
  • Having a build number generated for a build and make sure that build number is present in the name of our packages, configs, and deployment labels in Azure for consistency
  • Leveraging the Release Configurations in Visual Studio as much as possible as the designation of our different environments, since Web.Config transformations rely on this pattern already
  • Being able to still package and deploy locally, in case there is an issue with the build server for some reason

Let’s start with a new Windows Azure Project in Visual Studio, with a MVC 3 web role. I quickly prepped the MVC project for deployment to Azure by installing the NuGet package for MVC 3 and turning off session state in my web.config. I will use this as my sample project.

Defining Target Configurations and Profiles

Release Configurations and Service Configurations

The first thing I want to examine is configuration files are now handled for deployments.  I see that the Web.config has two release transformations that match up to the “Debug” and “Release” configurations that come with the project. I also see that there are two configs, a ServiceConfiguration.Local.cscfg file and a ServiceConfiguration.Cloud.cscfg file, that represent “Local” and “Cloud” target environments. I have found in my experience that each target environment for a web project needs its own transformation for a web.config and its own version of a ServiceConfiguration file. For example, the ServiceConfiguration might have settings such as the Windows Azure Storage Account to use for Diagnostics, but the web.config has the configuration section for your Windows Azure Cache Settings. I am sure there could be advantages to having the flexibility to have these differ, but for my project I want them to be consistent, in-line with our target environments, and to rely on the Release Configuration setting.

I added a Release Configuration for “Test” and “Prod” and removed the one for “Release” to mimic a couple of target environments. This was easily done using the Configuration Manager.

I used the “Update Config Transforms” option by right clicking on the web.config file to generate the new transforms.

Now I just needed to get the ServiceConfiguration files to fall in line.  In Windows Explorer, I added ServiceConfiguration.Test.cscfg, and renamed the other two so that I had ServiceConfiguration.Debug.cscfg and ServiceConfiguration.Prod.cscfg.  I then unloaded the Cloud Service project in Solution Explorer and editing the project file to include these elements.

After saving and reloading, you can see that all the configurations are showing up and named consistently.

Next, I needed to make sure these would package and publish correctly from Visual Studio.  I am happy to see that my changes are recognized and supported. There is a setting for which ServiceConfiguration to use when debugging.

For packaging/publishing my cloud project, I am prompted to select the configurations I want (and in this case I do not have variability in the two).  The correct configuration file is staged alongside my package file.

The Service Definition File

In Hollander’s post, he mentioned some good reasons why you might want to have a different ServiceDefinition file for different environments, such as altering the size of the role.  I also could envision other cases such as not having an SSL endpoint in a DEV environment, etc.  I see this more as a nice to have than a must have.  Feel free to skip this section if you are ok with a standard definition file for all of your environments.

For those interested, lets now tackle adding different ServiceDefinition files for our Release Configurations and making sure those are built as part of our package.

Hollander’s example shows how to use transformations on both the ServiceConfiguration file and ServiceDefinition file.  In this example, I am simply going to replace the entire file, to fall in line with what is being done by Visual Studio for the ServiceConfiguration file.

In Windows Explorer, I added a ServiceDefinition.Test.csdef and ServiceDefinition.Prod.csdef, and renamed the existing one to ServiceDefinition.Debug.csdef for consistency.   I updated the ccproj file for my cloud service to include those files like I did with the configuration files before, but added a separate section so that VS doesn’t complain about multiple definition files.

They now show up in Solution Explorer.

The part that is a little trickier is making sure the correct ServiceDefinition file for our Release Configuration is chosen when we are packaging/publishing our project.  To understand this process, I started looking at the targets file that my project is using, Microsoft.WindowsAzure.targets located at C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Windows Azure Tools\1.4, which is different from the targets file it used in Tom and Alex’s examples before the 1.4 tools update.  I also starting running msbuild from the command line so that I could see the log file it was generating for the Publish process.  Here is an example of the command I was using:

msbuild CloudService.ccproj /t:Publish /p:Configuration=Prod > %USERPROFILE%\Desktop\msbuild.log

On a side note, initially the command fails.  In looking at the log file, it was because the variable $(TargetProfile) was not specified for the target “ResolveServiceConfiguration”, which looks for the correct ServiceConfiguration file to use.

I updated my command to the following and ran again:

msbuild CloudService.ccproj /t:Publish /p:Configuration=Prod;TargetProfile=Prod > %USERPROFILE%\Desktop\msbuild.log

This time my project build and packaged successfully and produced the correct configuration file.  This property will come in handy later for our Build server configuration.

Now, back to the task of getting the appropriate definition file into our package.  After more digging into the targets file and log file, its appears that a target service definition file, ServiceDefinition.build.csdef, gets generated from the original source definition file, and that one is used with cspack to create the package file.  The target file is first created in the same directory as your project file, and then it is used with cspack and for reference can be seen in the .csx folder in your bin directory.

In looking back at the Microsoft.WindowsAzure.targets file, we can see that the ResolveServiceDefinition target sets these source and target variables.  If I override this target to set the appropriate source definition file variable, then that one will be used.  I added the following section to my CloudService.ccproj file to override this target and use the Configuration property as part of the source definition file name (could have also used TargetProfile).

I then ran some builds, packages, and publishes from the command line and from Visual Studio.  Everything was ending up in the right place.  To verify, you can dig into the CloudService.csx folder for our service located in bin\(Configuration) to find the definition file that was packed.  For this “Prod” build we can verify that the correct definition file was used by looking at the contents of this folder:

Now I have my environment definitions and configurations in sync, and all relying on the same Release Configuration property.  I am ready to starting building from our build machine.

Packaging and Deploying on a Build Machine

Build Machine Setup

I am using an existing instance of TFS 2010 for source control.  I now need to configure a machine to be able to build my Azure project. I elected to set up a new virtual machine to serve as a new build server because of the Azure dependencies my build server will have.  From some research, it appears that most people are installing Visual Studio, the Windows Azure SDK and the Windows Azure Tools for Visual Studio onto their build server in order to build Azure projects.  I briefly looked at what it would take to not go down this path, but decided against it.  If anyone has a more elegant solution (that doesn’t involve faking the Azure SDK installation), I am all ears.  Due to these requirements, I felt it was best to use a separate VM as a build server.

After provisioning a new Windows Server 2008 R2 VM, I installed the Build Services only for TFS 2010.  I did end up creating a new Project Collection on our TFS 2010 server and configured the build controller to be associated to that collection.

I was now ready to install the necessary dependencies to build Windows Azure projects.  I installed Visual Studio 2010.  I then used the Web Platform Installer to install the Windows Azure SDK and Windows Azure Tools for Visual Studio – August 2011.  The Web Platform Installer did a good job of taking care of upstream dependencies for me.

After the installation completed,  I double checked that the Microsoft.WindowsAzure.targets file was present on the VM.

Finally, I created a drop location on my build machine and updated the permissions to allow builds to be initially stored on this machine itself.  My end goal is to have these builds being pushed to Windows Azure, but I figure it will be nice to verify locally on the machine first.

Packaging on the Build Machine

Back in Visual Studio on my laptop, I created a new Build Definition called “AzureSample-Prod”.  I specified the Drop location to the share I created on the build VM.  On the Process tab of the wizard I made sure to select my CloudService.ccproj file for my solution under “Items To Build” and also entered the “Prod” Configuration for which Configuration to build.  I selected the cloud service project so I can package my solution for Windows Azure.  The “Prod” Configuration represents my target environment I will be publishing to.

Next, remembering my experience running msbuild.exe from the command line before, I knew I had to set some MSBuild arguments.  The field is located in the Advanced Properties window section on the Process tab.  I added an MSBuild argument for $TargetProfile and set it to match my Release Configuration. I also specified a MSBuild argument for the Publish target to indicate that we want to create the package.

This Build Definition should successfully build and package my Azure project.  I ran the build and it succeeded.  I then went to the drop location that I specified to verify everything was packaged appropriately.  To my surprise, the “app.publish” folder that should contain the .cspkg and .cscfg file that was packaged did not exist at the drop location.  Strange.  I looked over the MSBuild log file for any clues.  I also found that the app.publish folder was created at the source build location on the build server.  It just looked like it was getting left behind and not making it to the drop location.  I am thinking it could be a timing issue for when contents are dropped at the drop location.  This was not an issue prior to the 1.4 Tools for Visual Studio.

To get around this problem, I decided to add a simple custom target to my project file to copy the app.publish folder contents to the drop location after they are created.  The condition checks the value of a new parameter, which I set as an MSBuild argument, so that this target doesn’t run when building/packaging locally in Visual Studio.

My MSBuild Arguments property now looked like this:

A kicked off another build, and was happy to see the app.publish folder with package file and configuration file in my drop location.  Note that my drop location happens to be on my build server, but it really could be another shared location.

Deploying from the Build Machine

The next task was to start deploying from my build machine directly to Windows Azure.  Already having a Windows Azure Subscription, I created a new hosted service and storage account for my target environments.  To be able to deploy, there are some extra configuration steps to complete on the build machine.  In Hollander’s post, he outlines these steps:

  • Create a Windows Azure Management Certificate and install it on the build machine and also upload it to the Windows Azure Subscription
  • Download and install the Windows Azure Management Cmdlets on the build machine
  • Create an AzureDeploy.ps1 powershell script and save it to the build machine

After completing these steps, I also made sure to update the execution policy for powershell on my build machine, knowing that the script would fail if the execution policy was restricted.

I continued following Tom’s instructions, and updated my project file to add a target that calls the AzureDeploy.ps1 script. I used the AzureDeploy.ps1 “as is” from his post.  I did have to modify his “AzureDeploy” target.  I had to update some of the parameter values to match my naming conventions and also to match the new app.publish folder where our package is created.  I also reused the same “BuildServer” parameter that I used in my other target above.  I eliminated the “AzureDeployEnviornment” parameter since we are leveraging the Release Configuration for this.

Finally, I came back to my Build Definition, and updated the MSBuild Arguments on the Process Tab to include the parameters for the Azure Subscription ID, the Hosted Service Name, the Storage Account Name, and the Certificate Thumbprint:

/t:Publish /p:TargetProfile=Test /p:BuildServer=True /p:AzureSubscriptionID="{subscriptionid}"
/p:AzureCertificateThumbprint="{thumbprint}"
/p:AzureHostedServiceName="{hostedservicename}" /p:AzureStorageAccountName="{storageaccountname}"

I then kicked off a new build and logged onto the Windows Azure Management Portal to watch my service deploy!

Final Tweaks: Build Numbers, Deployment Labels, Deployment Slots, and Saving Builds in BLOB Storage

Now having my project deploying to Windows Azure, I noticed a few other tweaks I wanted to make to tighten this process up a bit.  One of the things that I thought would be valuable was to have the Build Number generated by TFS during the build process be the Label on the deployment in Windows Azure.  Then it would be very easy to identify what build is deployed.  This turned out to be a little more complicated than I expected.

I knew that if I passed in the build number as a parameter to the AzureDeploy.ps1 script, then I could easily set that to the label for the deployment.  But unfortunately, by default the build number value is not available to downstream targets.  I found this post by Jason Prickett, which describes an easy update to make to the build template that exposes this value.  I made a copy of the default build process template, edited the XML as indicated in that post, and saved the file as a new build process template.  You then need to check this template into source control.  I edited my Build Definition to use this new template rather than the default template.

Now that I had a variable containing the value of the build number, I updated the target to pass the $BuildNumber into the AzureDeploy.ps1 script.  I then updated the script to accept this parameter and use it for the build label.  The script already had a variable for “buildlabel” that was being used.  I simply set its value to this incoming argument.

$buildLabel = $args[7]

I kicked off another build and went back to the Azure Portal.  This time the deployment label matched the build number in TFS!

Next, I took a look at what was being stored in BLOB storage when deploying to Windows Azure with this process.  My goal was to have both the package and service configuration file for a build be stored in BLOB Storage for historical purposes, and also to easily support rollback scenarios.  I noticed that the Cmdlet being used was placing the package file in a private blob container called “mydeployments” in the storage account I specified.  The name of the blob was a current date format plus the name of my package.  Also, the configuration file was not present.

I wanted to see the package and configuration file here, and using a naming convention that included the build number so I could distinguish them appropriately.  Let’s make this happen.

I opened the solution file for the Windows Azure Management Cmdlets.  I looked at the source code for the cmdlet that was being called from the AzureDeploy.ps1 script.  Inside NewDeployment.cs, I navigated to the NewDeploymentProcess method.  It was easy to see where the package was being uploaded to blob storage.  It was eventually calling to a static method called UploadFile in AzureBlob.cs.  There it was applying a timestamp to all BLOBs as part of a naming convention, likely to ensure uniqueness.  I decided to comment this out, because I plan to make my BLOB names unique myself by using the Build Number.

Next, back in the NewDeploymentProcess method, I added a snippet to also upload the configuration file to BLOB storage, right after the package file.  Finally, I was prepared to move these changes onto the build server.  I uninstalled the existing Cmdlets using an uninstall script that is provided with the cmdlets.  I then removed them entirely from the build server.  I zipped up and copied the updated version to the build server and then installed them once again.

The last remaining step was to have the package and configuration file names contain the build number from TFS.  I once again modified the target that copied these files to the drop location.  I simply included the $(BuildNumber) parameter that we exposed earlier as part of the destination file name.

After running a new build, I verified the contents of the “mydeployments” container.  I was pleased again to see a package and configuration file labeled by build number.

This now matches the label on my hosted service that was just deployed, and also aligns with the build history in TFS and on the build server.  All are now in sync, and centered around the Build Number generated from TFS.  If I decided to change my naming convention for builds in my build definition, that change would be represented in these other areas as well.

For extra credit, I made one final improvement.  Knowing that I may want to target either the Production or Staging deployment slot for different build definitions, I updated the AzureDeploy.ps1 script to accept deployment slot as an incoming parameter.

Here is a look at the final version of these targets in my CloudService.csproj file.  You will be able to download the entire solution from a link at the end of this post.

Resources

I hope that this post is helpful in creating and customizing your build process for Windows Azure.  If there are better ways to accomplish some of these things, please let me know.  Credit to Tom Hollander and others for laying the groundwork for me to follow.

The solution, which includes the ccproj file with my custom targets, as well as the other files such as the AzureDeploy.ps1 script are available for download here.

Happy building and deploying!

UPDATE:  Tom Hollander from Microsoft had a great new post outlining how to update your build process for the Windows Azure 1.6 SDK.  Check it out here.

 

Slalom Consulting’s Seattle office Slalom Consulting's Project & Cloud focus
Learn more about our Seattle office Learn more about Slalom Consulting Cloud

subscribe by emailSubscribe to follow new Cloud posts

About Joel Forman
Joel Forman is a Solution Architect at Slalom Consulting and specializes in cloud computing and the Windows Azure Platform.

12 Responses to Building and Deploying Windows Azure Projects using MSBuild and TFS 2010

  1. Pingback: Episode 57 – Windows Azure Page Blobs with Brent Stineman - Windows Azure Blog

  2. Pingback: Windows Azure and Cloud Computing Posts for 8/26/2011+ - Windows Azure Blog

  3. Wow. Thanks a ton Joel. I had cobbled a few of these pieces together for my build process, but you solved all the lingering problems that I had. I am feeling GREAT tonight after adding all this awesomeness to the continuous deployment setup for my Azure app. Sweet!

  4. As I have been debugging today I noticed something interesting. When I build from the command line all of the trasforms are correct and all of my targets (debug, test, prod) are created properly.

    When I build from within Visual Studio though it looks like the web.config transforms don’t get made. Any idea why this would be? Do I need to add something else to my VS configuration?

    I can work around this issue by leaving my debug settings as the defaults in web.config, but ideally I would like the transforms made there as well.

    • Joel Forman says:

      From my experience, the web config transforms are not applied on build, only on publish. What targets are you calling from the command line? From within VS, you have to select “Package” on the cloud project to get the publish executed on the web project. Simply building the solution or project will not cause the transforms to be applied.

  5. Todd Shelton says:

    Fantastic, Joel–thanks for your detailed summary of steps. You have saved me an unknown (but big) amount of time!

    Todd

  6. Rainer says:

    Hi Joel, thank you very much for your post. It solved nearly all problems I had with my scenario. However I still have some questions, maybe you can help me?

    I have a solution, that contains an AzureProject and two WebProjects (and some class library projects). One Webproject is linked to the AzureProject as WebRole (=root of the web site). I would like to have the second WebProject as virtual application within the same WebRole. So I changed the csdef file according to Tom Hollanders blog so that the physicalDirectory attribute points to ..\..\..\Binaries\_PublishedWebsites\MySecondWebProject. Then I start a TeamBuild with the ccproj file as “Projects to build” and /t:Publish (and some other) as “MSBuild Arguments”. With this parameters the second WebProjects is not built on the build agent. _PublishedWebsites contains only the first WebProject. Tom proposed to add project dependencies between the AzureProject and the WebProjects. But these dependencies are not stored in the ccproj file but in the .sln file. So I created this dependencies an passed the sln file to the TeamBuild but no luck. After changing the MSBuild Arguments to “/t:Build /t:Publish” I get a build error saying “Need to specify the physical directory for the virtual path ‘Web/'”. So now I am confused:
    a) What do I have to pass to the TeamBuild: ccproj file, sln file, ccproj file + csproj file?
    b) What do I have to pass as MSBuildArguments: Build, Publish, CorePublish, some combinations?

    Regards,
    Rainer

  7. Pingback: Windows Azure Planning – A Post-decision Guide to Integrate Windows Azure in Your Environment « Fleeting Thoughts

  8. Pingback: Recent Windows Azure Project Highlights: HTML5, Autoscaling, iOS, and more! « The Slalom Blog

  9. Shailesh says:

    This was great and is pretty much how I have setup my environment.So thanks a lot. But now that 1.6 is out, and they claim to have included lots of changes to make team builds easier(without providing any details), I am sure there will be ways to simplify a lot of this.

    Any chances, you going to post an update soon for 1.6?

    • Joel Forman says:

      Hi Shailesh,

      I have seen the changes with the 1.6 SDK, but have yet to incorporate them into the build process. If you beat me to it, pass along some tips. Otherwise I will post an update here in the near future.

  10. Pingback: Using Build/Service configurations and web.config transformations to make deployments easier | Learning never exhausts the mind !!

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 )

Google+ photo

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

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 129 other followers

%d bloggers like this: