BYTE-force columns
Company news, team and friends.

Copy a subtree with MSBuild


In MSBuild, to copy a part of subtree, resembling directory structure, you have to write small and simple task.

The problem

I have a directory with files and subdirectories which I want to copy to another location as a part of build process. I do not need all files though. I want to specify necessary subdirectories, file masks and also some excludes.

In NAnt i could easily do it like this:

<copy todir="sdfTestWeb/private"> <fileset basedir="${solution.path}/sdf.SPanel/private"> <include name="*.sdf"/> <include name="*.aspx"/> <include name="*.asmx"/> <include name="*.inc"/> <include name="web.config"/> <include name="images/**/*.*"/> <include name="scripts/**/*.*"/> <include name="styles/**/*.*"/> <include name="xml/**/*.*"/> <include name="xsl/**/*.*"/> <exclude name="**/*.vss"/> </fileset> </copy>

I turned this script to MSBuild analogue, using Items and Copy task.

<ItemGroup> <SPanelFiles Include="..\sdf.SPanel\private\*.sdf" /> <SPanelFiles Include="..\sdf.SPanel\private\*.aspx" /> <SPanelFiles Include="..\sdf.SPanel\private\*.asmx" /> <SPanelFiles Include="..\sdf.SPanel\private\*.inc" /> <SPanelFiles Include="..\sdf.SPanel\private\web.config" /> <SPanelFiles Include="..\sdf.SPanel\private\images\**\*.*" exclude="**\*.vss" /> <SPanelFiles Include="..\sdf.SPanel\private\scripts\**\*.*" Exclude="**\*.vss" /> <SPanelFiles Include="..\sdf.SPanel\private\styles\**\*.*" Exclude="**\*.vss" /> <SPanelFiles Include="..\sdf.SPanel\private\xml\**\*.*" Exclude="**\*.vss" /> <SPanelFiles Include="..\sdf.SPanel\private\xsl\**\*.*" Exclude="**\*.vss" /> </itemgroup> <Target Name="CopyCPanelFiles"> <Copy SourceFiles="@(SPanelFiles)" DestinationFolder="private" SkipUnchangedFiles="true" /> </target>

As you see it looks much more complicated. And, alas, it doesn't work as expected. The directory structure is not copied. All files from subtree are placed right into the "private" directory.

Google said...

Search gave virtually nothing:

The solution

I didn't want to write my own copying code. Yes, it's simple, but with good testing it anyway would take me a lot of time. So I decided to tweak standard Copy task a bit:

  • Create our class, inherited from Microsoft.Build.Tasks.Copy.
  • Make a property SourceRoot, which will specify a root directory of source files. As you see, there is no "basedir" attribute in MSBuild.
  • Overwrite Execute method:
    • It scan SourceFiles collection, 
    • Strips SourceRoot from the beginning of paths, 
    • Combine with DestinationFolder path, 
    • Write into the DestinationFiles collection, 
    • At the end - call base.Execute.

Surprisingly, it's working. Here is the source code:

using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;

namespace sdf.Tasks
  public class CopySubtree : Copy
      private ITaskItem _sourceRoot;

      public override bool Execute()
        if( DestinationFiles != null )
            Log.LogError( "DestinationFiles must not be specified." );
            return false;

        if( DestinationFolder == null )
            Log.LogError( "DestinationFolder must be specified." );
            return false;

        if( SourceFiles.Length > 0 )
            DestinationFiles = new ITaskItem[SourceFiles.Length];

            string srcRoot = _sourceRoot.GetMetadata( "FullPath" );
            for( int i = 0; i < SourceFiles.Length; i++ )
                ITaskItem srcFile = SourceFiles[i];
                string srcPath = srcFile.GetMetadata( "FullPath" );

                if( srcPath.StartsWith( srcRoot ) )
                    srcPath = srcPath.Substring( srcRoot.Length + 1 );
                    srcPath = Path.Combine( DestinationFolder.ItemSpec,
                                            srcPath );

                DestinationFiles[i] = new TaskItem( srcPath );
                srcFile.CopyMetadataTo( DestinationFiles[i] );

            DestinationFolder = null;

        return base.Execute();

    public ITaskItem SourceRoot
      get { return _sourceRoot; }
      set { _sourceRoot = value; }

And fragment of a build file:

<UsingTask TaskName="sdf.Tasks.CopySubtree" AssemblyFile="bin\sdf.TestWeb.dll"/> ... <CopySubtree SourceFiles="@(SPanelFiles)" SourceRoot="..\sdf.SPanel\private" DestinationFolder="private" SkipUnchangedFiles="true" />

Posted мар 02 2006, 06:07 by Andrew Mayorov
Filed under: ,


«XOR's Post» wrote Back to MSBuild tasks
on 06-21-2006 16:14
MSBuild Commuinty Tasks project looks mature today. Most of the tasks are complete. Among useful for...
Eamonn's Blog wrote MSBuild not very Nant User Friendly
on 09-19-2006 20:58
As part of my conversion to Asp.Net 2 I had decided to utilise Msbuild over Nant. In my 1.1 build
Copyright ©2004-2009 BYTE-force
Powered by Community Server (Non-Commercial Edition), by Telligent Systems