Automating the build with MSBuild (part three)

Automating the build with MSBuild

Welcome to part three of the MSBuild tutorial; please take a look at the previous parts if you haven’t already.

At this point there are a couple of minor issues with our build script that need to be resolved before pressing on with any new targets.

Usability

The first of these is usability. Right now there is no easy way to run all of the targets in one go. This is not very helpful if someone wants to run the full build process from start to finish. To remedy this, I’ve introduced a new target called Run, below

<Target Name="Run">
  <CallTarget Targets="Compile" />
  <CallTarget Targets="Publish" />
  <CallTarget Targets="SetConfig" />
</Target>

This simply uses the CallTarget task to hand the work off to the targets we created in the last two posts. Notice that it isn’t necessary to call Clean and GetConfig as they are dependencies of Compile and SetConfig respectively. MSBuild will automatically run them, in the correct order.

The finishing touch is applied by adding the DefaultTargets attribute to the root Project node, with a value of Run. This tells MSBuild to call the Run target if no targets are explicitly passed in from the command line. Having added the attribute, the whole script can be executed with this simple command

msbuild Build.xml

As you can see the /t switch has gone. Much nicer!

DRYing out the XML

Our second issue involves refactoring. It is important to treat our build script just like any other code and apply good practices to it. Some repetition has crept in, which violates the DRY principle, specifically

  • Each XPath to a config setting occurs twice
  • The Output folder is referred to numerous times

The best solution is to extract these strings into properties and then use the $(PropertyName) syntax to refer to them. It doesn’t take long and makes the script much easier to maintain in future. You can see the end result here.

Deploying the web site

Now the housekeeping is out of the way, we can start work on the last target, which is to deploy the web site to the correct location and configure IIS accordingly. This means copying the contents of the Output folder to another computer and either adding or updating a virtual directory to run the site.

The first part is pretty simple, and just a case of using the RemoveDir and Copy tasks in a similar way to the Publish target in part two. The following XML illustrates this

<Target Name="Deploy">
  <RemoveDir Directories="$(DeploymentFolder)"
             ContinueOnError="true" />
  <ItemGroup>
    <DeploymentFiles Include="$(OutputFolder)\**\*.*" />
  </ItemGroup>
  <Copy SourceFiles="@(DeploymentFiles)"
        DestinationFolder="$(DeploymentFolder)\%(RecursiveDir)" />
</Target>

Nothing new to see there, so we’ll move on quickly to configuring the IIS virtual directory. There is no built-in task to do this, however the MSBuild Community Tasks Project comes to the rescue again with the WebDirectoryDelete and WebDirectoryCreate tasks. It is important to recreate the virtual directory each time to ensure we have a clean deployment. This is accomplished by adding the following XML to our Deploy target

<WebDirectoryDelete VirtualDirectoryName="$(VirtualDirectory)"
                    ContinueOnError="true" />
<WebDirectoryCreate VirtualDirectoryName="$(VirtualDirectory)"
                    VirtualDirectoryPhysicalPath="$(DeploymentFolder)" />

Something worth noting at this point is that this only works for the local machine. If you are deploying to another box, which is highly likely, then you would need to supply the ServerName, Username and Password attributes as well.

The final piece of the puzzle is to amend the Run target to ensure it calls our new Deploy target at the end. At this point it is possible to run the entire script and get a newly built and deployed web site in a matter of seconds. Very useful indeed.

Environment-specific deployment

At this point we need to revisit the Environment property which was defined back in part two. This is still hard-wired to Test, as are the new DeploymentFolder and VirtualDirectory properties. As a consequence, users of the build script will have to edit it if they want to build for, say, a live environment.

To prevent this we can introduce a PropertyGroup for each of the environments, and move the DeploymentFolder and VirtualDirectory properties into each of them. Then, by adding the Condition attribute to each group, it is possible to test which environment is being used and bring its PropertyGroup into play. The following snippet shows how to do so

<PropertyGroup Condition="$(Environment) == 'Test'">
  <DeploymentFolder>C:\Temp\BuildDemoSite\Test</DeploymentFolder>
  <VirtualDirectory>BuildDemoTest</VirtualDirectory>
</PropertyGroup>

<PropertyGroup Condition="$(Environment) == 'Live'">
  <DeploymentFolder>C:\Temp\BuildDemoSite\Live</DeploymentFolder>
  <VirtualDirectory>BuildDemoLive</VirtualDirectory>
</PropertyGroup>

As you can see, the value of the Condition attribute is an expression that evaluates to true or false. In our case, we test the value of the Environment setting. In future, if further deployment environments are required, it is simple to add a new PropertyGroup.

Despite these changes, we still have that pesky Environment property – let’s delete it. Instead, it can specified via the command line when the script is run, as follows

msbuild Build.xml /p:Environment=Live

By using the /p switch it is possible to specify a value for the property, in a similar way to calling a function and passing in arguments. However, there is always a chance that someone may forget to do this, in which case you may wish to restore the Environment property and have it act as a default.

Summary

That’s the end of the tutorial. Although the final script (available on CodePlex) achieves all of the aims set out in part one there are still a couple of areas for improvement, specifically

  • There is no MSBuild task which provides the same behaviour as Visual Studio’s Publish feature
  • A certain amount of repetition remains when merging config settings. Ideally all settings would be merged automatically, perhaps via a custom MSBuild task (something for another post perhaps)

Overall however the end result is still very useful, and only scratches the surface of what MSBuild can do. If you are interested in learning more, the following links may be of use

All of the source code for the tutorial is available from CodePlex. I hope you’ve found it useful and welcome any feedback.

Technorati tags:

Tags: ,

21 Responses to “Automating the build with MSBuild (part three)”

  1. Martijn says:

    Hi,

    Thanks for the great tutorials!

    I get an “access denied” error however when the WebDirectoryDelete and WebDirectoryCreate are being called. Any idea how I could solve this? I am using IIS 7.

    Thanks again and regards.

  2. Martijn,

    Sounds like the account under which the build script is running may not have privileges to create and delete virtual directories.

    Are you using Vista? If so try opening the command window with the “Run as Administator” option before executing the build script. It might be that your own account is not permitted to modify virtual directories.

    Let me know how you get on, and thanks for the feedback.

  3. Martijn says:

    Hi,

    I did like you said (yes, I use Vista) but get an HRESULT error.
    I guess it’s not working for IIS7 yet?

    best regards,
    Martijn

  4. Martijn,

    I have not tested the build script on Vista as I still use XP at home. However I have now got Vista at work so I will try to run it there and see if I can reproduce your error.

    I hope to get back to you in the next couple of days.

  5. Dan F says:

    Dude! Awesome series. I’ve been going around in circles this afternoon getting my head around Cruise Control, Nant, MSBuild, the community tasks, etc. Your series has helped cement a few things in my head. Ta!

  6. @Dan F,

    Thanks for the feedback, it sounds like you’ve got quite a build server coming on there! And BTW, nice hat ;-)

  7. walash says:

    Interesting article, but the aim of automated build is to make everything work correctlyl. But if sign not default document for example, latter we must do it using our hands. . .

  8. Sebas says:

    Great articles, I was wondering about something tho.
    I’m confronted with a /bin directory which directly contains some configuration files .config.xml, I’d like to execute a clean on the directory _without_ losing those files, as it’s pain to reconfigure everything.
    Is it possible to create an ItemGroup and tell the delete action _not_ to include that particular ItemGroup?

  9. Sebas,

    The bin directory should only be used for the output of the build, and as such any files it contains are not a permanent part of the project, nor should they be under source control.

    If this .config.xml file is important, it probably needs to be added to the project itself and copied to the bin folder as part of the build. That way you can fully clean the bin folder and not lose anything.

    I hope this makes sense :-)

  10. punit says:

    Thanks again… best and most easy tutorial for msbuild i’ve found on the net so far…

  11. Thanks punit glad it has helped you.

  12. mark says:

    Fantastic set of tutorials and just what I needed. I am however hitting an issue with IIS7. Originally a permissions issue, which was quickly fixed by adding the build user to the local administrator group on my remote IIS server but now I get “The RPC Server is unavaliable’ and HRESULT 0×900706BA, accompanied with Class not registered.

    Has anyone managed to get an automated website build working to IIS7?

    In the mean time I will disable the Virtual Directory delete and create for now as it’s not a requirement as once it’s been created once then we will be simply republishing the content in the folder. But it would be nice to have a complete solution especially if we switch servers down the road.

  13. @mark, glad you got some use from the articles. I’ve not tried them on Vista/IIS7 yet so can’t suggest any potential solutions.

    If you do find anything of use, please leave another comment so we can all enjoy the benefits of your research :-)

  14. KJ says:

    Thank in advance, everyone.

    I’m a bit new to working with MSBuild, and I’m having a hard time getting the WebDirectoryDelete and WebDirectoryCreate to work.

    SYSTEM: TFS 2008 SP1 on Windows 2003 Server. The build machine and TFS server are on the same machine.

    What I want my build script to do is on the target server (not the build/tfs machine), delete the web site and it’s physical directory and then re-create both, then deploy my code.

    I cannot get the WebDirectoryDelete to work. So what I did was edited the build script to try to delete a web site on the TFS Build machine (so everything is local) but that didn’t work either.

    Here’s the code I’m using to try to delete a web site in IIS on my the local TFS build machine:

    ConfigurationService-Test
    65504

    When I run this code I get:
    Task “WebDirectoryDelete”
    Deleting virtual directory ‘ConfigurationService-Test’ on ‘localhost:65504′.
    D:\TFSBuilds\Healthcare\testing\BuildType\TFSBuild.proj(30,9): error : The system cannot find the path specified. (Exception from HRESULT: 0×80070003)

    I have tried numerous iterations on the above code, specifying the user ID and password, tried specifying “localhost”, \\MachineName – etc. I just can’t seem to delete the web site in IIS via my build script.

    Could someone please point out what I’m doing wrong? I’m very eager to get this to work, and it seems that others have had success with it.

    Thank you.

  15. Hi KJ,

    Try posting your question on StackOverflow.com, you’re likely to get a quick response there.

    Apologies but I haven’t got the time to look at this in detail right now.

    Ross

  16. KJ says:

    Excellent – thanks Ross. I’ll post my question there.

  17. OK good luck. If you get a decent answer please post a link in the comments. Cheers.

  18. KJ says:

    I figured it out.

    I stopped trying to use the code in my original post because I just couldn’t get it to work, and imported this library instead:

    Here’s the code to create and delete web sites in IIS. Please note that this works remotely, meaning I can create and delete web sites on a machine that’s not the TFSBuild machine (providing file system permissions is not an issue of course):


    MATX.Healthcare.AdminService
    AUSTX010PROD
    65501
    D:\Healthcare\AdminService

    <Message Text********** CreateWebSiteInIIS **********

    I found and used the info from this web site and it works perfectly:

    http://grounding.co.za/blogs/romiko/archive/2008/09/20/msbuild-creating-professional-deployment-scripts-part-1.aspx

  19. KJ says:

    Sorry folks… here’s a re-post:

    I figured it out.

    I stopped trying to use the code in my original post because I just couldn’t get it to work, and imported this library instead:

    Import Project=(MSBuildExtensionsPath)\SDCTasks\Microsoft.Sdc.Common.tasks

    Here’s the code to create and delete web sites in IIS. Please note that this works remotely, meaning I can create and delete web sites on a machine that’s not the TFSBuild machine (providing file system permissions is not an issue of course):

    PropertyGroup
    — Set properties for web site and app pool operations –
    WebSiteName MATX.Healthcare.AdminService /WebSiteName
    ServerName AUSTX010PROD /ServerName
    PortNumber 65501 /PortNumber
    IISNetworkDir D:\Healthcare\AdminService /IISNetworkDir
    /PropertyGroup

    Target Name=”DeleteWebSiteInIIS”
    Message Text=”****** DeleteWebSiteInIIS”

    Web.WebSite.DeleteWebsite
    MachineName=”(ServerName)
    Description=”(WebSiteName)
    /Target

    Target Name=”CreateWebSiteInIIS”
    Message Text=”******CreateWebSiteInIIS”

    Web.WebSite.Create
    Description=”(WebSiteName)”
    Path=”(IISNetworkDir)”
    MachineName=”(ServerName)”
    Port=”(PortNumber)”
    AppPoolID=”Services”
    DefaultDocs=”AdminService.svc;Default.aspx;Default.html;Default.asp;index.htm”
    /Target

    I found and used the info from this web site and it works perfectly:

    http://grounding.co.za/blogs/romiko/archive/2008/09/20/msbuild-creating-professional-deployment-scripts-part-1.aspx

  20. Nam Gi VU says:

    I’ve used “Run as administrator” the cmd to open the command line and run msbuild but the task WebDirectoryDelete and WebDirectoryCreate failed with error 0×80005000.

    Please update solution.

  21. Hi Nam,

    It might be worth trying some of the other solutions suggested in the comments, particularly those from KJ.

Leave a Reply