67

Trying to copy a whole folder, but when I do this:

<Copy SourceFiles="$(TargetDir)\*.*" DestinationFolder="$(BuildOutput)\SomeDir" />

the copy attempts to do this: copy c:\source\. c:\destination\SomeDir\. and fails with illegal characters error

user1623521
  • 340
  • 1
  • 16
Sonic Soul
  • 23,855
  • 37
  • 130
  • 196
  • 2
    A similar question has been asked here: http://stackoverflow.com/questions/119271/copy-all-files-and-folders-using-msbuild – joshhendo Feb 25 '11 at 23:07

8 Answers8

89

Specify your ItemGroup for SourceFiles explicitly.

<ItemGroup>
    <_CopyItems Include="$(TargetDir)\*.*" />
</ItemGroup>
<Copy
    SourceFiles="@(_CopyItems)"
    DestinationFolder="$(BuildOutput)\SomeDir"
    />

Note that _CopyItems is an item type, so it's referenced using '@' symbol rather than '$'.

Community
  • 1
  • 1
Brian Kretzler
  • 9,748
  • 1
  • 31
  • 28
  • 14
    Better add `false` to the item group, otherwise you will see all your bin folder contents in solution explorer. ` <_CopyItems Include="$(TargetDir)*.*"> false ` – Zelenov Sep 20 '16 at 10:27
59

Copying files can be done with the following code snippet which handles antivirus programs and subdirectories

  <ItemGroup>
        <SomeAppStuff Include="$(SolutionDir)\ProjectXXX\bins\**\*.*" />
  </ItemGroup>
  <Copy 
      SourceFiles="@(SomeAppStaff)" 
      DestinationFolder="$(OutputPath)\%(RecursiveDir)" 
      SkipUnchangedFiles="true"
      OverwriteReadOnlyFiles="true" 
      Retries="3"
      RetryDelayMilliseconds="300"/>

Specifying $(OutputPath)\%(RecursiveDir) will ask Copy task to respect subfolders, so it will place subfolders of source directory to subfolders of target directories.

SkipUnchangedFiles will increase build speed on computers with enough memory, because Windows optimizes IO for frequently used files when there's enough RAM.

Retries and RetryDelayMilliseconds handles issues related a) Compressed NTFS file system, when builds fails in seldom cases b) Antivirus Software with SSD drives.

Siarhei Kuchuk
  • 5,296
  • 1
  • 28
  • 31
  • What are the ramifications of setting the `DestinationFolder` to path\%(RecursiveDir)? Will it place the directory at the intended location, or whatever is farthest down the recursion pathing, – kayleeFrye_onDeck Jul 01 '16 at 19:31
  • 1
    kayleeFrye_onDeck, It will place subfolders of source dir in subfolders of destination directory, if to remove that, content of all subfolders of source directory will be placed to destination directory. – Siarhei Kuchuk Jul 01 '16 at 21:13
  • Thank you, my folders were being created but all files put into the root until I chaned to `%(RecursiveDir)`. – Aaron Aug 12 '19 at 02:22
  • Curious can MS Build cope with a drive letter I have to create a physical folder at time of msbuild but it cant be in the main web site – c-sharp-and-swiftui-devni May 28 '21 at 13:54
39

If you put the folder in the root of your c# project then you can simple put this in your csproj.

<ItemGroup>
    <None Update="FolderToCopy\**\*.*">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
</ItemGroup>

I have only tested in the 2017 version of csproj, but I assume it's backwards compatible. Could be wrong on that though

Terence
  • 755
  • 6
  • 7
14

Looking at the MSDN documentation, I believe the SourceFiles parameter requires an ITaskItem[] value. See MSDN MSBuild Copy Task

The last example on the above link is to do a recursive copy from one directory to another, maintaining the folder structure.

RichTea
  • 288
  • 1
  • 2
  • 9
9

Succeeded to accomplish this task like this

<Target Name="AfterBuild">
  <ItemGroup>
    <SomeDir Include="$(SolutionDir)\SomeOtherProject\SomeDir\**\*" />
  </ItemGroup>
  <Copy 
    SourceFiles="@(SomeDir)" 
    DestinationFiles="@(SomeDir->'$(OutDir)\SomeDir\%(RecursiveDir)%(Filename)%(Extension)')" 
    SkipUnchangedFiles="true" 
    OverwriteReadOnlyFiles="true" 
    Retries="3" 
    RetryDelayMilliseconds="300" />

Arthur
  • 2,601
  • 1
  • 22
  • 19
8

For me what worked was this: - kept folder structure - copied all files within the folder - works for any folder, doesn't have to be in the project or the same project folder

<ItemGroup>
    <_CopyItems Include="<path relative to project>\**\*.*" />
</ItemGroup>

<Target Name="AfterBuild">
  <Copy SourceFiles="@(_CopyItems)" DestinationFiles="@(_CopyItems->'$(OutDir)\<output folder>\%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>

legend:

  • <path relative to project>: this could be any path, using ..\ for going above the proj folder works
  • <output folder>: folder you want the whole file structure to be dropped into, excluding the source folder.
  • $(OutDir) will be bin\Debug or whatever build mode you have, if you want something else, change that.
Ricardo Rodrigues
  • 2,198
  • 2
  • 18
  • 22
4

In Visual Studio 15.4+, there's a feature that makes this easier - you can set LinkBase or Link to control the destination path:

<ItemGroup>
  <Content Include="FolderToCopy\**" LinkBase="FolderInOutput\" CopyToOutputDirectory="Always" />
</ItemGroup>

source: https://github.com/dotnet/msbuild/issues/2949#issuecomment-362823310

Tereza Tomcova
  • 4,928
  • 4
  • 30
  • 29
  • Thanks! Simple and concise! [Common macros](https://learn.microsoft.com/en-us/cpp/build/reference/common-macros-for-build-commands-and-properties?view=msvc-170) like `$(ProjectDir)` are working with this approach as well. – riskeez Aug 10 '23 at 14:24
-2

The best solution for me was to use the magic XCOPY as I had to copy all files and sub directories

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
  <PropertyGroup>
    <FilesSource>$(ProjectDir)\lib</FilesSource>
    <FilesDestination Condition=" '$(SolutionName)' == 'any name a' ">$(ProjectDir)\..\..\Something\lib</FilesDestination>
    <FilesDestination Condition=" '$(SolutionName)' == 'the top solution name' ">$(SolutionDir)\Something\lib</FilesDestination>
  </PropertyGroup>
  <Error Condition=" '$(FilesDestination)' == '' " Text="Lib not delivered. To disable this message, remove the 'Target' tag from the project file" />
  <Exec Command="RD /S /Q &quot;$(FilesDestination)&quot;" />
  <Exec Command="XCOPY &quot;$(FilesSource)&quot; &quot;$(FilesDestination)&quot; /E /I /H /R /K /Y" />
  <Exec Command="RD /S /Q &quot;$(FilesSource)&quot;" />
</Target>

This build event is fired once build succeed, it cleans the FilesDestination folder then it copies all files with directory structure from FilesSource to FilesDestination and then it delete the FilesSource folder to keep everything "bien propre" :)

NOTE

For FilesDestination, make sure you use the Condition attribute or remove it to have the copy process to its end

Fabrice T
  • 648
  • 9
  • 23