Continuous deployment with TFS 2010 Build Agent

When looking at Visual Studio Lab Management and how deployment are done I found this diagram.

LabComponentsExtended

If you look at the Virtual Machine 1 box inside the Hype-V Host box you find Build Agent. This is the same as Build Agent A.1 used on the Build Machine for doing “normal” builds as shown in this diagram.

BuildServerStandalone

This means that you can use the the TFS Build Agent to do continuous/automated deployment even if you don’t have the Lab Management parts.

How to set it up

To make it work you could set it up something like in the diagram below.

DeploymentDiagram

On the Team Foundation Application-Tier the build controller will delegate the different parts of the build and deployment to the different build agents based on the assigned tags passed to build for the different parts. This can be done by adding process parameters as described at the end of Jim Lambs post on How to Create a Custom Workflow Activity for TFS Build 2010 RTM

You have to modify your build workflow to do the deployment. To do this you add a AgentScope activity for every server you want to deploy to. In the AgentScope you add what ever workflow activity you need to perform the deployment. Below is a very simple example.

BuildWorkflow

Advertisement

How to build ClickOnce applications with TeamBuild for multiple environments

  

In ths post I will show how to build ClickOnce applications with TeamBuild for multiple environments in Team Foundation Server.

I have been trying to build ClickOnce applications in our TeamBuild for some time now and only after long ours of googling and trial and error was I successful. The most useful information I found on this blog:

Publish ClickOnce project with Team Build?

Made some modifications and refactoring and here are the code we currenlty use:

This is how you call the ClickOnce build from you TeamBuild project file (line breaks added for readability):

    <Msbuild
          Projects="$(SolutionRoot)ConfigFilesBuildClickOnce.targets"
          Targets="BuildClickOnce"
          Properties="SourceDir=$(SourceDir);PublishDir=$(PublishDir);                      ClickOnceAppName=$(ClickOnceAppName);ClickOnceExeFile=$(ClickOnceExeFile);                      ClickOnceProduct=$(ClickOnceProduct);Company=$(Company);                      ClickOnceDescription=$(ClickOnceDescription);ClickOnceUrl=$(ClickOnceUrl);                      VersionNumber=$(VersionNumber);SigningCert=$(SigningCert);                      SigningCertPassword=$(SigningCertPassword)"
            />

For every environment or configuration change you want you use this. Below you find the BuildClickOnce.targets file (line breaks added for readability):

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <Import Project="$(MSBuildExtensionsPath)MicrosoftVisualStudioTeamBuildMicrosoft.TeamFoundation.Build.targets" />
    <Import Project="$(MSBuildExtensionsPath)MSBuildCommunityTasksMSBuild.Community.Tasks.Targets"/>
    <Import Project="$(MSBuildExtensionsPath)MicrosoftSDCMicrosoft.Sdc.Common.tasks"/>

    <Target Name="BuildClickOnce" DependsOnTargets="">

        <PropertyGroup>
            <ClickOnceApplicationUrl>$(ClickOnceUrl)$(ClickOnceAppName).application</ClickOnceApplicationUrl>
            <AppPublishDir>$(PublishDir)</AppPublishDir>
            <SdkPath>C:Program FilesMicrosoft SDKsWindowsv6.0A</SdkPath>
        </PropertyGroup>

        <BuildStep
          TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
          BuildUri="$(BuildUri)"
          Message="Buildning $(ClickOnceAppName) ClickOnce version: $(VersionNumber)">
            <Output TaskParameter="Id" PropertyName="StepId" />
        </BuildStep>

    <CallTarget Targets="UpdateWebPage" />

        <!--
  ************************************************
  Generate application manifest
  ************************************************
  -->
        <Exec
        Command="mage.exe -New Application -TrustLevel FullTrust                  -ToFile &quot;$(AppPublishDir)$(ClickOnceExeFile).manifest&quot;                  -Name &quot;$(ClickOnceAppName)&quot; -Version &quot;$(VersionNumber)&quot;                 -FromDirectory &quot;$(AppPublishDir)&quot;"
        WorkingDirectory="$(SdkPath)Bin"/>

        <!--
  ************************************************
  Signing application manifest
  ************************************************
  -->
        <Exec Condition="'$(SigningCertPassword)'==''"
          Command="mage.exe -Sign &quot;$(AppPublishDir)$(ClickOnceExeFile).manifest&quot;                    -CertFile &quot;$(SigningCert)&quot;"
          WorkingDirectory="$(SdkPath)Bin"  />

        <Exec Condition="'$(SigningCertPassword)'!=''"
            Command="mage.exe -Sign &quot;$(AppPublishDir)$(ClickOnceExeFile).manifest&quot;                      -CertFile &quot;$(SigningCert)&quot; -Password &quot;$(SigningCertPassword)&quot;"
            WorkingDirectory="$(SdkPath)Bin"/>

        <!--
  ************************************************
  Renaming source files to .deploy
  ************************************************
  -->
        <ItemGroup>
            <SourceFilesToRename Include="$(AppPublishDir)***.*"                  Exclude="$(AppPublishDir)*.manifest;$(AppPublishDir)*.htm"/>
            <SourceFilesToDelete Include="$(AppPublishDir)***.*"                  Exclude="$(AppPublishDir)*.application;$(AppPublishDir)*.manifest;$(AppPublishDir)*.htm"/>
        </ItemGroup>

        <Copy
            SourceFiles="@(SourceFilesToRename)"
            DestinationFiles="@(SourceFilesToRename->'$(AppPublishDir)%(RecursiveDir)%(Filename)%(Extension).deploy')"
        />

        <Delete Files="@(SourceFilesToDelete)"/>


        <!--
  ************************************************
  Generating deployment manifest
  ************************************************
  -->

        <GenerateDeploymentManifest
          MapFileExtensions="true"
          AssemblyName="$(ClickOnceAppName).application"
          AssemblyVersion="$(VersionNumber)"
          MinimumRequiredVersion="$(VersionNumber)"
          DeploymentUrl="$(ClickOnceApplicationUrl)"
          Description="$(ClickOnceDescription)"
          Product="$(ClickOnceProduct)"
          Publisher="$(Company)"
          SupportUrl="$(SupportUrl)"
          EntryPoint="$(AppPublishDir)$(ClickOnceExeFile).manifest"
          Install="true"
          UpdateEnabled="true"
          UpdateMode="Foreground"
          OutputManifest="$(PublishDir)$(ClickOnceAppName).application"/>

        <!--
  ************************************************
  Signing application manifest
  ************************************************
  -->
        <Exec Condition="'$(SigningCertPassword)'==''"
            Command="mage.exe -Sign &quot;$(PublishDir)$(ClickOnceAppName).application&quot;                     -CertFile &quot;$(SigningCert)&quot;"
            WorkingDirectory="$(SdkPath)bin"/>
        <Exec Condition="'$(SigningCertPassword)'!=''"
            Command="mage.exe -Sign &quot;$(PublishDir)$(ClickOnceAppName).application&quot;                     -CertFile &quot;$(SigningCert)&quot; -Password &quot;$(SigningCertPassword)&quot;"
            WorkingDirectory="$(SdkPath)bin"/>


        <!--
  ************************************************
  Generating Bootstrapper
  ************************************************
  -->
        <ItemGroup>
            <BootstrapperFile Include="Microsoft.Net.Framework.2.0">
                <ProductName>Microsoft .NET Framework 2.0</ProductName>
            </BootstrapperFile>
        </ItemGroup>

        <GenerateBootstrapper
          ApplicationFile="$(ClickOnceAppName).application"
          ApplicationName="$(ClickOnceAppName)"
          ApplicationUrl="$(ClickOnceUrl)"
          BootstrapperItems="@(BootstrapperFile)"
          Culture="en"
          FallbackCulture="en-US"
          CopyComponents="true"
          Validate="false"
          Path="$(SdkPath)Bootstrapper"
          OutputPath="$(PublishDir)"/>


        <BuildStep
          TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
          BuildUri="$(BuildUri)"
          Id="$(StepId)"
          Status="Succeeded"/>

        <OnError ExecuteTargets="MarkBuildStepAsFailed" />
    </Target>

    <!--
  ************************************************
  Marks a buildstep as failed
  ************************************************
  -->
    <Target Name="MarkBuildStepAsFailed">
        <BuildStep
          TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
          BuildUri="$(BuildUri)"
          Id="$(StepId)"
          Status="Failed"/>
    </Target>

    <!--
  ************************************************
  Updating web page
  ************************************************
  -->
    <Target Name="UpdateWebPage">
        <ItemGroup>
            <WebPage Include="$(PublishDir)publish.htm" />
        </ItemGroup>

        <RegEx
          Condition="Exists(@(WebPage))"
          Path="@(WebPage)"
          RegularExpression="#VERSION#"
          NewValue="$(VersionNumber)"
          Force="true"/>
    </Target>
</Project>