Thursday, June 19, 2008

TeamBuild ClickOnce – Auto Incrementing Your Version Information

Using AssemblyInfoTask within your TFSBuild.proj file, you can set your builds to automatically increment your AssemblyVersion and AssemblyFileVersion.  You can also keep your application’s Publish Version in sync, which is useful if you’re publishing your application via ClickOnce. 

The Publish Version is important because that’s what ClickOnce looks at to see if there’s a new version available when the user runs the application.  Updating the AssemblyVersion or AssemblyFileVersion doesn’t tell ClickOnce that there’s a new version of your application to download.

There are a ton of blog posts like this one that tell you how to check in/out files from your TFS so I won’t repeat those steps.  The actual version updates happen inside the <AssemblyInfo> task:

<Project>
    <Import Project="$(MSBuildExtensionsPath)\Microsoft\AssemblyInfoTask\Microsoft.VersionNumber.Targets" />
    ...
    <Target Name="AfterGet">
        <Message Text="In After Get"/>

        <CreateItem Include="$(SolutionRoot)\My Project\AssemblyInfo.vb">
            <Output ItemName="AssemblyInfoFiles" TaskParameter="Include"/>
        </CreateItem>

        <Exec WorkingDirectory="$(SolutionRoot)\My Project\"
            Command="$(TF) checkout AssemblyInfo.vb" />

        <AssemblyInfo AssemblyInfoFiles="@(AssemblyInfoFiles)"
            AssemblyRevisionType="AutoIncrement"
            AssemblyRevisionFormat="0"
            AssemblyFileRevisionType="AutoIncrement"
            AssemblyFileRevisionFormat="0">

          <Output ItemName="MaxAssemblyVersion" TaskParameter="MaxAssemblyVersion" />
        </AssemblyInfo>
      </Target>
    ...
</Project>

For our project, we only want to update a specific AssemblyInfo.vb as opposed to recursively checking out all AssemblyInfo files to modify.  We used the <CreateItem> task to store the AssemblyInfo files into an array.  In our case it’s just one file (That’s why the <Exec> task looks a bit different than most examples out there on the web). 

Inside the <AssemblyInfo> task is pretty self-explanitory.  Setting the type and format of the Revision (you can do this to your Major, Minor, and Build as well) to AutoIncrement is what does the trick.  You don’t have to have an <Output> element, but I wanted to keep our Publish Version and Assembly Version in sync. 

We can use the variable @(MaxAssemblyVersion), which stems from the ItemName attribute, to hold our new Assembly Version.  I’ve previously posted about modifying the vbproj file to update the Publish Version so I won’t go into the details, but finding and replacing the needed element isn’t difficult.  Lastly you’ll want to check in your new AssemblyInfo file so that the next build increments your versions. 

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.