Batch copy the list of items using MSBuild Task RoboCopy

1.9k Views Asked by At

There are list of source and destinations directories defined in . I need a single task/target to perform the robocopy as per the defined item group properties.

<ItemGroup>
<ItemToCopy Include="$(RootPath)\Audi">
    <WhereToCopy>$(FinalFolder)\Audi</WhereToCopy>
    <WhatToCopy>*.svc</WhatToCopy>
</ItemToCopy>
<ItemToCopy Include="$(RootPath)\Custom">
    <WhereToCopy>$(FinalFolder)\Custom</WhereToCopy>
    <WhatToCopy>*.svc</WhatToCopy>
</ItemToCopy>
<ItemToCopy Include="$(RootPath)\Audi\bin">
    <WhereToCopy>$(FinalFolder)\Audi\bin</WhereToCopy>
    <WhatToCopy>*.*</WhatToCopy>
</ItemToCopy>
<ItemToCopy Include="$(RootPath)\Custom\bin">
    <WhereToCopy>$(FinalFolder)\Custom\bin</WhereToCopy>
    <WhatToCopy>*.*</WhatToCopy>
</ItemToCopy>

I have tried following code, expecting to perform copy operation for each Items in the deployment folder.

<Target Name="CopyAll">  
   <RoboCopy  
        Source="@(ItemToCopy)"  
        Destination="%(ItemToCopy.WhereToCopy)" Files="ItemtoCopy.Whattocopy"/>  
</Target>

In addition, If we see the items 1 & 2 (also 3 & 4), they are same in the sense of copying similar kinds of files from their %ItemName to same path with subdirectory %ItemName. It could be great if we could also avoid that extra code smell. Hoping something like below to work:

<ItemToCopy Include="$(RootPath)\@PublishProjects">
        <WhereToCopy>$(FinalFolder)\@PublishProjects</WhereToCopy>
        <WhatToCopy>*.svc</WhatToCopy>
    </ItemToCopy>
where,
<ItemGroup>
    <PublishProjects Include="Audi" />
    <PublishProjects Include="Custom" />
  </ItemGroup>
2

There are 2 best solutions below

0
On

You can't mix @ and %. Both of those indicate an operation on an item group. Using % performs the operation once for each item in the item group. @ performs the operation once upon the entire item group. Note that not all tasks support item groups.

If you use @ then you're using a "transform" which has a funky syntax. https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-transforms

Also, item groups are intended to match files for you. Specifying folders does work and it refers to the folder, but one of msbuild's strengths is in finding the files for you.

Instead of

<ItemToCopy Include="$(RootPath)\Audi">
    <WhereToCopy>$(FinalFolder)\Audi</WhereToCopy>
    <WhatToCopy>*.svc</WhatToCopy>
</ItemToCopy>

I'd go with

<ItemToCopy Include="$(RootPath)Audi\*.svc">
    <TargetSubDir>Audi\</TargetSubDir>
</ItemToCopy>

Then your target would be more like

  <Target Name="CopyFiles">
    <Copy Condition="@(ItemToCopy)!=''"
          SourceFiles="@(ItemToCopy)"
          DestinationFiles="@(ItemToCopy->'$(FinalFolder)%(TargetSubDir)%(RecursiveDir)%(Filename)%(Extension)')"
          OverwriteReadOnlyFiles="true"
          SkipUnchangedFiles="true" />
  </Target>

Remember, it is an msbuild convention that all properties/metadata representing a directory include the trailing slash.

0
On

Ok, that answer I gave skips the usage of robocopy which I acknowledge is a specific part of your question.

The @ and % still holds true (you can't mix their usage) and that's mostly where you've gone wrong in your CopyAll target.

<Target Name="CopyAll">  
   <RoboCopy  
        Source="@(ItemToCopy)"  
        Destination="@(ItemToCopy->'%(WhereToCopy)')" Files="@(ItemtoCopy->'%(Whattocopy)')"/>  
</Target>

Check out the help on transforms for a better understanding of how to use them https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-transforms