trigger config transformation in TFS 2010 or msbui

2019-01-22 13:44发布

I'm attempting to make use of configuration transformations in a continuous integration environment.

I need a way to tell the TFS build agent to perform the transformations. I was kind of hoping it would just work after discovering the config transform files (web.qa-release.config, web.production-release.config, etc...). But it doesn't.

I have a TFS build definition that builds the right configurations (qa-release, production-release, etc...) and I have some specific .proj files that get built within these definitions and those contain some environment specific parameters eg:

<PropertyGroup Condition=" '$(Configuration)'=='production-release' ">
    <TargetHost Condition=" '$(TargetHost)'=='' ">qa.web</TargetHost>
    ...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)'=='qa-release' ">
    <TargetHost Condition=" '$(TargetHost)'=='' ">production.web</TargetHost>
    ...
</PropertyGroup>

I know from the output that the correct configurations are being built. Now I just need to learn how to trigger the config transformations. Is there some hocus pocus that I can add to the final .proj in the build to kick off the transform and blow away the individual transform files?

6条回答
劫难
2楼-- · 2019-01-22 14:16

The web.config transformation functionality that was added to Website Projects in Visual Studio 2010 is disabled by default for command-line and TFS builds.

There are two relatively straightforward solutions:

Option 1: Edit the build definition and add the following to the "MSBuild Arguments" field:

/p:UseWPP_CopyWebApplication=true /p:PipelineDependsOnBuild=false

UseWPP_CopyWebApplication will cause the new Web Publishing Pipeline (WPP) to be activated for the build. WPP performs the web.config transformations and can also be used to block things such as .PDB files from being copied to the bin folder.

Option 2: Both MSBuild and WPP are fully extensible. Create a new XML file in the same directory as your project and use the ".targets" extension - for example, ProjectName.custom.targets. Place the following MSBuild code into the targets file:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <UseWPP_CopyWebApplication>True</UseWPP_CopyWebApplication>
    <PipelineDependsOnBuild>False</PipelineDependsOnBuild>
  </PropertyGroup>
</Project>

Right click on your website and select "Unload project". Right click on the unloaded project and select Edit. Scroll to the bottom of the project file and look for these lines:

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

These lines are where the C# and Web Project build process gets hooked up. Insert an import to your custom build extensions (target file) immediately before the CSharp import:

<Import Project="ProjectName.custom.targets"/>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

That's it - you're good to go. The MSBuild customization method is a bit more work to set up, but the benefit is that you can use the new targets file to "hook" into the build process and have much more control over how your build happens on the server. For example, you could hook in tasks to perform CSS and JS compression.

I'd also recommend looking at "wpp targets" - if you name another MSBuild file with the specific name "ProjectName.wpp.targets" you can control the entire website publishing process. We use it to remove the -vsdoc javascript documentation files as the published website output is copied:

<ItemGroup>
  <ExcludeFromPackageFiles Include="Scripts\**\*-vsdoc.js;Resources\Scripts\**\-vsdoc.js">
    <FromTarget>Project</FromTarget>
  </ExcludeFromPackageFiles>
</ItemGroup>

Everything said, you're probably better off leaving out the production web.configs from your build entirely. We place the transforms directly onto our production deployment machine and use powershell to transform as we deploy the application.

查看更多
时光不老,我们不散
3楼-- · 2019-01-22 14:17

I finally managed to get this working. I'm using TFS 2008, but also using MSBuild 4.0 so it should work for you.

First, add this import to TFSBuild.proj:

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

Next, add a BeforeDropBuild target:

<Target Name="BeforeDropBuild">
  <TransformXml Source="$(SolutionRoot)\MySite\Web.config"
    Transform="$(SolutionRoot)\MySite\Web.QA.config"
    Destination="$(OutDir)\_PublishedWebsites\MySite\Web.QA.config.transformed" />
</Target>

You can then copy the Web.QA.config.transformed to wherever you need it to go.

查看更多
狗以群分
4楼-- · 2019-01-22 14:25

All you should need to do is setup which configuration should be used within the TFS build definition.

  1. Goto Team Explorer > Builds
  2. Edit your build definition (or create new)
  3. Under the "Process" step there is a settings for "Configurations to Build".

In my case I have setup a configuration specifically for CI that then performs the correct web.config transformations. Ensure that you have added the "CI" transform file and you should be good to go.

查看更多
叼着烟拽天下
5楼-- · 2019-01-22 14:30

I found another way to accomplish this instead of creating a custom activity. You just need to modify the visual studio project file of the web application that is being built.

Add the following (a placeholder for the 'AfterBuild' target can be found towards the end of the project file):

<Target Name="AfterBuild" Condition="$(IsAutoBuild)=='True'"> 
  <ItemGroup> 
         <DeleteAfterBuild Include="$(WebProjectOutputDir)\Web.*.config" /> 
  </ItemGroup> 
  <TransformXml Source="Web.config" Transform="$(ProjectConfigTransformFileName)" Destination="$(WebProjectOutputDir)\Web.config"/> 
  <Delete Files="@(DeleteAfterBuild)" />
</Target> 

Then you just have to add /p:IsAutoBuild="True" to the 'MSBuild Arguments' field found in the 'Advanced' section of the build definition.

This will force TFS 2010 to do the transformations on the web.config when TFS does the build.

More details can be found on Kevin Daly's Blog.

查看更多
神经病院院长
6楼-- · 2019-01-22 14:32

To do this in WorkFlow, you have to create a Custom Activity. There's quite a good article about it here

For this specific activity you need to create and Activity project (change it from .Net 4 Client profile to .Net 4) and reference Microsoft.Build.Framework and Microsoft.Build.Utilities.v4.0 from the GAC and then Microsoft.Web.Publishing.Tasks from %programfiles%\msbuild\Microsoft\VisualStudio\v10.0\WebApplications (%programfiles(x86)% if you're on a 64bit system).

When that's done, you add these two classes:

First, there's a stub:

internal class BuildEngineStub : IBuildEngine
{
    public bool BuildProjectFile(string projectFileName, string[] targetNames, System.Collections.IDictionary globalProperties, System.Collections.IDictionary targetOutputs)
    {
        throw new NotImplementedException();
    }

    public int ColumnNumberOfTaskNode
    {
        get { throw new NotImplementedException(); }
    }

    public bool ContinueOnError
    {
        get { throw new NotImplementedException(); }
    }

    public int LineNumberOfTaskNode
    {
        get { throw new NotImplementedException(); }
    }

    public void LogCustomEvent(CustomBuildEventArgs e)
    {
    }

    public void LogErrorEvent(BuildErrorEventArgs e)
    {
    }

    public void LogMessageEvent(BuildMessageEventArgs e)
    {
    }

    public void LogWarningEvent(BuildWarningEventArgs e)
    {
    }

    public string ProjectFileOfTaskNode
    {
        get { throw new NotImplementedException(); }
    }
}

Then theres the activity class it self:

[BuildActivity(HostEnvironmentOption.Agent)]
public sealed class WebConfigTransform : CodeActivity
{
    private const string WEB_CONFIG = "Web.config";
    private const string WEB_CONFIG_TRANSFORM_FORMAT = "Web.{0}.config";

    private IBuildEngine _buildEngine { get { return new BuildEngineStub(); } }

    [RequiredArgument]
    public InArgument<string> TransformationName { get; set; }
    [RequiredArgument]
    public InArgument<string> SourceFolder { get; set; }
    [RequiredArgument]
    public InArgument<string> DestinationFolder { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        var transformationName = context.GetValue(this.TransformationName);
        var sourceFolder = context.GetValue(this.SourceFolder);
        var destinationFolder = context.GetValue(this.DestinationFolder);

        var source = Path.Combine(sourceFolder, WEB_CONFIG);
        var destination = Path.Combine(destinationFolder, WEB_CONFIG);
        var destinationbackup = string.Format("{0}.bak", destination);
        var transform = Path.Combine(sourceFolder, string.Format(WEB_CONFIG_TRANSFORM_FORMAT, transformationName));

        if(!File.Exists(source))
            throw new ArgumentException("Web.config file doesn't exist in SourceFolder");
        if (!File.Exists(transform))
            throw new ArgumentException("Web.config transformation doesn't exist in SourceFolder");
        if (File.Exists(destination))
        {
            File.Copy(destination, destinationbackup);
            File.Delete(destination);
        }

        var transformation = new TransformXml();
        transformation.Source = new TaskItem(source);
        transformation.Destination = new TaskItem(destination);
        transformation.Transform = new TaskItem(transform);
        transformation.BuildEngine = _buildEngine;

        if (transformation.Execute())
        {
            File.Delete(destinationbackup);
        }
        else
        {
            File.Copy(destinationbackup, destination);
            File.Delete(destinationbackup);
        }
    }
}

The reason for the BuildEngineStub is that the TransformXml class uses it to do logging.

The only thing you need to be careful about is that the TransformXml.Execute function locks the source configuration file until the build process is finished.

查看更多
Juvenile、少年°
7楼-- · 2019-01-22 14:40

Here's an easier answer for you. :)

http://social.msdn.microsoft.com/Forums/en-US/tfsbuild/thread/d5c6cc7b-fbb1-4299-a8af-ef602bad8898/

From the link (in case it gets moved/404/etc):

Here is how I solved this. The key was to edit the *.csproj file on the website and add the following to the AfterBuild target (be sure to move the end comment above it). This is for our website build project in Team Foundation Server.

<Target Name="AfterBuild">
    <TransformXml Condition="Exists('$(OutDir)\_PublishedWebsites\$(TargetName)')"
                  Source="Web.config"
                  Transform="$(ProjectConfigTransformFileName)"
                  Destination="$(OutDir)\_PublishedWebsites\$(TargetName)\Web.config" />
</Target>

To keep the web.debug.config, web.release.config, etc... from being published be sure to set the "Build Action" in the properties window for each of the config transformation files to "None". Only the main web.config should have a "Build Action" of "Content"

One easy way to edit the csproj file is to load either the "PowerCommands for Visual Studio 2010" or "Productivity Power Tools" Extension to Visual Studio 2010 available from the Visual Studio Gallery. Once loaded all you have to do is right click on the project in your solution and select "Unload Project". Then you can right click again and select "Edit..." to edit the csproj file XML directly. Then when done just right click again and select "Reload Project".

查看更多
登录 后发表回答