30

The goal is to have TFS build and deploy 2+ different configurations, and have the web.config transform files include the intended content in their output. This in an ASP.NET MVC project.

alt text

Web.Debug.Config - see on PasteBin.
Web.Release.Config - see on PasteBin

The 2 transformed config files have their Build Action set to None. This was modified because all 3 web.*.config files were being included in the deployment.

TFS is configured correctly to build and deploy both configurations. It deploys to the 2 drop locations as expected. There are no MSBuild Arguments specified in the build definition.

alt text

Problem: The 2 built and deployed web sites have the same web.config file. Basically it's as if the transformed files did not exist.

Expected: the changes specified (xdt:Transform="Replace" and xdt:Transform="Remove") would be present in the web.config files.

How can you configure your project or TFS to ensure the web.config transformations are processed and their outputs deployed to the correct deployment locations? What else can I check/modify?

  • Have confirmed that the transformations are good -- Vishal's Joshit's tutorial with the MSBuild on the command line output the correct transformations!
  • No modifications have been made to the .csproj for any post-build or deployment.
  • Are any xdt attributes being misused or missing?
  • There are no MSBuild Arguments specified in the build definition.
  • Are the web.config Build Actions set correctly?
  • We're not using web deployment packages or anything. Simply expecting to xcopy these outputs over to their various webserver locations at a later date.

If I'm missing any important information, please leave a comment, and I'll include any more relevant information!

skaffman
  • 398,947
  • 96
  • 818
  • 769
p.campbell
  • 98,673
  • 67
  • 256
  • 322

5 Answers5

23

Previously I had been doing something similar to the other answers. However, I just found what seems to be a better solution to this problem. Just add "/p:UseWPP_CopyWebApplication=true /p:PipelineDependsOnBuild=false" to your MSBuild arguments. I just tried this out on one of my TFS builds and it works just fine.

I found this great tip here: http://www.andygeldman.com/index.php/2011/10/web-and-app-config-transformations-with-tfs-build.

Bart Sipes
  • 921
  • 8
  • 21
8

TFS Team Build 2010 does not automatically transform your Web.configs. You need to add a custom workflow activity to your build process template to accomplish this.

Edwald Hofman has a good blog that explains how to modify TFS 2010 Build Process Templates, so I won't go in depth on that here.

http://www.ewaldhofman.nl/post/2010/04/29/Customize-Team-Build-2010-e28093-Part-4-Create-your-own-activity.aspx

After you figure out how to add custom activities to your build process template, add the following activity into your workflow, I added the activity after "Drop FIles to Drop Location". It utilizes the Microsoft.Web.Publishing.Tasks assembly (located: C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web) to perform the transformations:

/// <summary>
/// Transforms configuration files using TransformXml
/// </summary>
[BuildActivity(HostEnvironmentOption.All)]
public sealed class WebConfigTransform : CodeActivity
{
    #region Public Properties

    /// <summary>
    /// The binaries folder
    /// </summary>
    [RequiredArgument]
    public InArgument<string> BinariesLocation { get; set; }

    #endregion

    #region Overrides of CodeActivity

    /// <summary>
    /// When implemented in a derived class, performs the execution of the activity.
    /// </summary>
    /// <param name="context">The execution context under which the activity executes.</param>
    protected override void Execute(CodeActivityContext context)
    {
        var binariesFolder = context.GetValue(BinariesLocation);

        foreach (var sourceFolder in Directory.GetDirectories(Path.Combine(binariesFolder, "_PublishedWebsites")))
        {
            var sourceFile = Path.Combine(sourceFolder, "Web.config");
            if (File.Exists(sourceFile))
            {
                var filesToTransform = Directory.GetFiles(sourceFolder, "Web.*.config");


                foreach (var fileToTransform in filesToTransform)
                {

                    var tempSourceFile = Path.GetTempFileName();
                    var tempTransformFile = Path.GetTempFileName();

                    File.Copy(sourceFile, tempSourceFile, true);
                    File.Copy(fileToTransform, tempTransformFile, true);

                    var transformation = new TransformXml
                    {
                        BuildEngine = new BuildEngineStub(),
                        Source = tempSourceFile,
                        Transform = tempTransformFile,
                        Destination = fileToTransform
                    };

                    transformation.Execute();
                }
            }
        }
    }

    #endregion
}

You will need to pass it the droplocation in the work flow. When you add it the the work flow, right click on the activity, then go to properties, and paste "DropLocation" (VB Expression) into the property "BinaryLocation"

NOTE: You'll need to create a BuildEngineStub class that implements the IBuildEngine interface in order to use the MSBuild task. Here is what I used

public class BuildEngineStub : IBuildEngine
{
        #region IBuildEngine Members

        public bool BuildProjectFile(string projectFileName, string[] targetNames,
                                     IDictionary globalProperties,
                                     IDictionary targetOutputs)
        {
            throw new NotImplementedException();
        }

        public int ColumnNumberOfTaskNode
        {
            get { return 0; }
        }

        public bool ContinueOnError
        {
            get { return false; }
        }

        public int LineNumberOfTaskNode
        {
            get { return 0; }
        }

        public string ProjectFileOfTaskNode
        {
            get { return ""; }
        }

        public void LogCustomEvent(CustomBuildEventArgs e)
        {
            Console.WriteLine("Custom: {0}", e.Message);
        }

        public void LogErrorEvent(BuildErrorEventArgs e)
        {
            Console.WriteLine("Error: {0}", e.Message);
        }

        public void LogMessageEvent(BuildMessageEventArgs e)
        {
            Console.WriteLine("Message: {0}", e.Message);
        }

        public void LogWarningEvent(BuildWarningEventArgs e)
        {
            Console.WriteLine("Warning: {0}", e.Message);
        }

        #endregion
    }
Ben Anderson
  • 7,003
  • 4
  • 40
  • 40
Danny
  • 812
  • 1
  • 8
  • 15
  • 1
    The other options are a far far better way to go and significantly easier to setup – Allen Rice Nov 03 '11 at 19:44
  • 5
    Disagree. I'd much rather make this change to my build process template once (for ALL solutions to use) than have to edit every single .proj file or build definition I create. – Danny Nov 04 '11 at 13:58
  • 1
    Hrm, good point, never thought about that... Admittedly, I'm new to building through TFS and the build templates but this all seems very frustrating. It seems that we're having to go out of our way to dev/maintain scripts to implement functionality that is already present in msbuild. – Allen Rice Nov 04 '11 at 16:02
  • I was too frustrated when we first switched from TFS 2008 to 2010. But once you get a grasp for modifying the build process template, I think you'll appreciate the enhancement. – Danny Nov 15 '11 at 21:18
  • I'm having a go at implementing this, but I'm not getting a WriteBuildMessage method on context, is this an extension method from somewhere I'm missing? – Chris Surfleet Feb 03 '12 at 09:54
  • Chris - sorry I didn't catch that when posting. WriteBuildMessage is an extension method of mine. You can simply use TrackBuildMessage off of your context object. – Danny Feb 06 '12 at 18:39
  • Does anyone know whether this still needs to be done for TFS Build 2012? – Jaans Sep 23 '13 at 01:31
  • It's frustrating working with TFS because those things should be built in and easy to setup. Yes indeed being able to customize the build workflow is great and gives you lots of flexibility but the fact is the out of the box functionality of TFS will hardly be enough even to the simplest projects. – Alex Jul 02 '14 at 13:50
4

Here's what I've been using. The current TransformXml task has a bug where it leaves files open. Read more here.

You can call this task and deploy for each configuration you're working with.

<Target Name="TransformWebConfig">

    <PropertyGroup>
        <_tempSourceFile>$([System.IO.Path]::GetTempFileName())</_tempSourceFile>
        <_tempTransformFile>$([System.IO.Path]::GetTempFileName())</_tempTransformFile>
    </PropertyGroup>

    <Copy SourceFiles="$(_websiteDirectory)\Web.config" DestinationFiles="$(_tempSourceFile)"/>
    <Copy SourceFiles="$(_websiteDirectory)\Web.$(_transformConfiguration).config" DestinationFiles="$(_tempTransformFile)"/>

    <MSBuild.Community.Tasks.Attrib Files="$(_websiteDirectory)\Web.config" ReadOnly="false" />

    <TransformXml Source="$(_tempSourceFile)"
                  Transform="$(_tempTransformFile)"
                  Destination="$(_websiteDirectory)\Web.config"
                  StackTrace="false" />
</Target>
Andrew Whitaker
  • 124,656
  • 32
  • 289
  • 307
Danny
  • 812
  • 1
  • 8
  • 15
  • thanks for this. I'm a bit confused at this stage; where goes this snippet of XML get placed? Any other modifications of the TFS Build definition to accommodate? – p.campbell Jan 24 '11 at 15:52
  • How familiar are you with MSBuild? You would place this target in your build definition and perhaps call it from the "BeforeDropBuild" target. – Danny Jan 28 '11 at 18:48
  • 1
    True. Though I think when I posted my answer, that wasn't the case. Who knows, I'll re-post the correct answer for TFS 2010. – Danny Sep 06 '11 at 19:41
0

The Drop does not actually do any transforming. What you need is to add /p:DeployOnBuild=Trueto the MSBuild Arguments.

This will create a Package that can then be used to install the website either via command line or using the IIS Import Application wizard.

If what you are after is to directly publish more than one configuration that is a whole other story and that is how I stumbled onto this post.

jonsca
  • 10,218
  • 26
  • 54
  • 62
Kn0wit
  • 1
0

Try not to set the Build Platform - basicly delete "Any CPU" in ItemToBuild and select MSBuild platform as "Auto"

ZERO
  • 133
  • 3