Tuesday, June 3, 2008

TeamBuild ClickOnce – Publishing To Different Locations

Our team's goal with TeamBuild is to keep things as automated as possible.  We currently have two separate publishing locations for production and beta releases.  Wouldn't it be nice if we didn't have to put the publishing location manually each time we put out a build?

publish

What I found interesting inside my project's vbproj file (which is just XML) was that the information I needed was located within the <PublishUrl> element:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
  <PropertyGroup>
    ...
    <PublishUrl>http://somelocation/</PublishUrl>
    ...
  </PropertyGroup>
</Project>

Great, we can now have two different build definitions for production and beta releases without having to manually enter the publishing location.  All we need to do is write the location in the vbproj file before we compile.  The problem is that TFS has the vbproj file (and I’m pretty sure all other files within your project) as read-only during a build.

My first thought was that I just needed to checkout the vbproj file, do my modification, then just check it in.  This worked fine, but was all that really necessary?  I thought of using attrib.exe to remove the read-only attribute from the file, but I didn’t know if it’d work because of the TFS source control files.  I got confirmation from a post that attrib.exe would do the job so that’s what I ended up doing in my TFSBuild.proj:

<Project ...>
    ...
    <Target Name ="BeforeCompile">
        <Message Text="In Before Compile"/>

        <Message Text="Making vbproj file writable"/>
        <Exec Command="attrib -R &quot;$(SolutionRoot)\project.vbproj&quot;"/>
        ...
    </Target>
    ...
</Project>

Now we just have to modify the <PublishUrl> element with our publishing location.  If you’ve read my previous posts you know that we use the SDC Tasks Library for XML manipulation.  So the first stab I took at placing our publishing locations into the vbproj file was using the <XmlFile.SetValue> task.  The problem here is that the default namespace in the vbproj file isn’t prefixed so getting the XPath I needed didn’t work.  The solution was to use the <File.RegEx> task:

<Project ...>
    ...
    <Target Name ="BeforeCompile">
       <Message Text="In Before Compile"/>

       <Message Text="Making vbproj file writable"/>
       <Exec Command="attrib -R &quot;$(SolutionRoot)\project.vbproj&quot;"/>

       <Message Text="Replacing PublishUrl"/>
       <File.RegEx
         Path="$(SolutionRoot)\project.vbproj"
         RegularExpression="&lt;PublishUrl&gt;(.*?)&lt;/PublishUrl&gt;"
         NewValue="&lt;PublishUrl&gt;http://YourSpecifiedLocation/&lt;/PublishUrl&gt;"
       />
    </Target>
    ...
</Project>

When the project compiles, it’ll use the updated vbproj’s information so the manifests will have the correct publishing information when built.  Using this method, you can still manually publish your ClickOnce without messing up your build definitions and also have various build definitions to specify different publishing locations. 

One thing to note is that the Publish Version in the IDE is the <ApplicationVersion> element in your vbproj file.  If you modify this in your IDE then this will also affect your builds.  I found that keeping your Assembly Version and Publish Version in sync the best way to do things.  This way nobody gets confused as to which version you might be talking about.  Also, ClickOnce checks your Publish Version when doing automatic updates so keep that in mind.

No comments: