TFS Build not transforming web.config as expected

2019-01-13 02:03发布

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!

5条回答
仙女界的扛把子
2楼-- · 2019-01-13 02:09

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>
查看更多
再贱就再见
3楼-- · 2019-01-13 02:19

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.

查看更多
该账号已被封号
4楼-- · 2019-01-13 02:20

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
    }
查看更多
放我归山
5楼-- · 2019-01-13 02:26

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

查看更多
时光不老,我们不散
6楼-- · 2019-01-13 02:28

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.

查看更多
登录 后发表回答