Use File Input To Copy Specific Folders With MSBuild - msbuild

I have a working directory with
folder1, folder2, folder3, folder4, .. folder10
I have a text file with project (folder) names in it:
file content:
folder1
folder2
folder3
I want to read the lines from the file, and then copy only those folders to a new folder while maintaining directory structure.
mynewfolder{ folder1, folder2, folder3 }
There will be more than one file for input. The resulting working directory (I'll call it parent) would change like this:
parent before msbuild:
parent{ folder1, folder2, .., folder10, mybuild.xml}
parent after msbuild:
parent{ folder1, folder2, .., folder10, mybuild.xml,
mynewfolder{folder1, folder2, folder3},
myothernewfolder{folder5, folder7, folder9} }
The closest I've been able to get is to copy into mynewfolder all the contents of folder1, folder2, and folder3 (so, structure was lost)
I've tried to use FindInList, and I've tried using the transform method, and I also tried to adjust everything to match this example:
MSBuild - Comparing ItemGroups metadata
But I haven't had much luck.
I've only had about 5 hours experience with MSBuild so I'm a bit lost at this point. Thanks in advance for any help you can offer!

Is this what you want?
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<Target Name="Sample">
<ItemGroup>
<_File Include="fileWithDirectorieNames.txt" />
</ItemGroup>
<PropertyGroup>
<_DestDir>d:\copy\</_DestDir>
</PropertyGroup>
<ReadLinesFromFile File="#(_File)" >
<Output TaskParameter="Lines" ItemName="ItemsFromFile"/>
</ReadLinesFromFile>
<ItemGroup>
<_files2Copy Include="%(ItemsFromFile.Identity)\**\*" >
<lastDir>%(ItemsFromFile.Filename)%(ItemFromFile.Extension)\</lastDir>
</_files2Copy>
</ItemGroup>
<Copy SourceFiles="#(_files2Copy)"
DestinationFiles="#(_files2Copy->'$(_DestDir)%(lastDir)%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>

Related

How do I set properties for items in an `ItemGroup` based on the name of the item being added?

Say I have the following ItemGroup in my msbuild file:
<ItemGroup>
<!-- build all the .proto files -->
<MyGroup Include="**/*.txt" MyProperty="[something here to extract metadata for each item]" />
</ItemGroup>
What can I put in the brackets to set the property as the itemgroup is filled? Specifically, I would like to get the project-relative path for the file (without the filename). Is something like this possible?
You can use the well-known item metadata inside the item.
<Project>
<ItemGroup>
<TextFiles Include="**/*.txt"
MyProperty="Included file %(Filename)(Extension: %(Extension)) in directory %(RelativeDir)" />
</ItemGroup>
<Target Name="ListTextFiles">
<Message Importance="high" Text="#(TextFiles->'%(Identity): %(MyProperty)', '%0A')" />
</Target>
</Project>
with a file structure of
fileA.txt
SomeSubfolder\fileB.txt
SomeSubfolder\fileC.txt
prints:
>dotnet msbuild -t:ListTextFiles -nologo
fileA.txt: Included file fileA(Extension: .txt) in directory
SomeSubfolder\fileB.txt: Included file fileB(Extension: .txt) in directory SomeSubfolder\
SomeSubfolder\fileC.txt: Included file fileC(Extension: .txt) in directory SomeSubfolder\

Remove Files and Folders Copied From AfterBuild Target

I would like to avoid hard coding the dll and folder names in the AfterClean target, is there a dynamic way to do this? Ideally it would only delete the files and folders created by the Copy in the AfterBuild target.
I tried to simplify this by changing the DestinationFolder to include a subdirectory in the OutputPath. The AfterClean target would only have to remove that subdirectory at this point. However, some of the library's DLLImport paths don't take that subdirectory into consideration which results in a crash.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="AfterBuild">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)..\lib\native\**\*.*" />
</ItemGroup>
<Copy SourceFiles="#(NativeLibs)" DestinationFolder="$(OutputPath)\%(RecursiveDir)" />
</Target>
<Target Name="AfterClean">
<Delete Files="$(OutputPath)\LumiAPI.dll" />
<Delete Files="$(OutputPath)\LumiCore.dll" />
<Delete Files="$(OutputPath)\LumiInOpAPI.dll" />
<RemoveDir Directories="$(OutputPath)\SPM" />
<RemoveDir Directories="$(OutputPath)\plugin" />
</Target>
</Project>
Project Structure:
src
ConsumingProject
ConsumingProject.csproj
ConsumingProject.sln
packages
my-project.5.7.0.12
build
lib
native
plugin
VenusDvc.dll
SPM
sSPM_1.bin
LumiAPI.dll
LumiCore.dll
LumiInOpAPI.dll
net45
my-project.5.7.0.12.nupkg
Essentially I want to delete all the files and folders that were copied from the native folder to the output of the project (ie LumiAPI.dll, LumiCore.dll, SPM (folder), eSPM_1.bin, etc). However I want it to be generic enough so that if I add another folder to the native directory, it will delete those folders/files as well.
Use a seperate target which lists input and output files, then use that list in both other targets. Note this uses the DestinationFiles attribute from the Copy task instead of DestinationFolders. And it might print some messages about non-existing directories being passed to RemoveDir because the top directory gets removed already before child directories.
update since you don't want to remove the root output directory as it still has files, figured applying the 'only remove output directory if it's empty' principle for any destination directory is probably the safest way to go. Credits go to the answer here.
<Target Name="GetMyOutputFiles">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)..\lib\native\**\*.*" />
<!--Now add some metadata: output dir and output file-->
<NativeLibs>
<DestinationDir>$(OutputPath)\%(RecursiveDir)</DestinationDir>
<Destination>$(OutputPath)\%(RecursiveDir)%(FileName)%(Extension)</Destination>
</NativeLibs>
</ItemGroup>
</Target>
<Target Name="AfterBuild" DependsOnTargets="GetMyOutputFiles">
<!--Copy one-to-one-->
<Copy SourceFiles="#(NativeLibs)" DestinationFiles="#(NativeLibs->'%(Destination)')" />
</Target>
<Target Name="AfterClean" DependsOnTargets="GetMyOutputFiles">
<Delete Files="#(NativeLibs->'%(Destination)')" />
<!--Find number of files left in each destination directory-->
<ItemGroup>
<NativeLibs>
<NumFiles>0</NumFiles>
<!--Condition is to avoid errors when e.g. running this target multiple times-->
<NumFiles Condition="Exists(%(DestinationDir))">$([System.IO.Directory]::GetFiles("%(DestinationDir)", "*", System.IO.SearchOption.AllDirectories).get_Length())</NumFiles>
</NativeLibs>
</ItemGroup>
<!--Only remove empty directories, use 'Distinct' to skip duplicate directories-->
<RemoveDir Directories="#(NativeLibs->'%(DestinationDir)'->Distinct())" Condition="%(NumFiles)=='0'" />
</Target>

How to use MSBuild to xcopy only modified or new files recursively?

I need some help using MSBuild to xcopy files to two directories.
First I found out about Sayed's after.xyz.sln.targets. This will ensure that copy file tasks will run regardless of actual Build occurs or not.
I wanted to use that to copy only new or modified only files to two target directories. So I need some sort of comparison code also. Steps that I can think of.
compare dir1 to dir2 and find any new or modified files in dir1.
copy those files to dir2 recursively.
then copy the exact file sets to dir3 recursively.
TIA.
You will need to use a parameter SkipUnchangedFiles of the Copy task. Example:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<MySourceFiles Include="c:\MySourceTree\**\*.*"/>
</ItemGroup>
<Target Name="CopyFiles">
<Copy
SourceFiles="#(MySourceFiles)"
DestinationFiles="#(MySourceFiles->'c:\MyDestinationTree\%(RecursiveDir)%(Filename)%(Extension)')"
SkipUnchangedFiles="true"
/>
</Target>
</Project>

MSBuild project file: Copy item to specific location in output directory

In the process of cleaning up the folder/file structure on a project I inherited, I'm running into a problem with organizing the required external libraries. I want to keep them in their own .\dll\ folder, but they're not being copied to the build directory properly. They should be in the root build directory, but they're being moved to a subfolder instead.
My .csproj file contains the following xml:
<Project>
<ItemGroup>
<None Include="dlls\libraryA.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Then, on build, the libraryA.dll file is copied to the bin\Debug\dll\ folder, but I want it in the bin\Debug\ folder.
I tried this and msbuild always wants to copy the files using their directory path, but there is a workaround...
Edit the csproj file and after this line:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Add these lines:
<PropertyGroup>
<PrepareForRunDependsOn>$(PrepareForRunDependsOn);MyCopyFilesToOutputDirectory</PrepareForRunDependsOn>
</PropertyGroup>
<Target Name="MyCopyFilesToOutputDirectory">
<Copy SourceFiles="#(None)" DestinationFolder="$(OutDir)" />
</Target>
The copy of the output files happens in the PrepareForRun target. This adds your own target to the list of targets that are executed as part of PrepareForRun.
This example copies all items in the None item group. You could create your own item group (e.g. MyFiles) and do the copy on that item group if you have other "None" files you don't want copied. When I tried this I had to change the item group name by editing the csproj file directly. Visual Studio did not allow me to set the item group of a file from the UI, but after I edited the csproj and changed it, Visual Studio displayed my custom item group name correctly.
If you only want to change it for one file, it may be easier to use the property:
<None Include="dlls\libraryA.dll">
<Link>%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Including content files in .csproj that are outside the project cone
This approach works
If you need to force copy of a specific file/nuget package into an asp.net core project (2.2), add at the end of your csproj :
<!-- Force copy MathNet because we need it in compilation -->
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="Build">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll'))" />
</Target>
<ItemGroup>
<ContentWithTargetPath Include="..\packages\MathNet.Numerics.4.8.1\lib\netstandard2.0\MathNet.Numerics.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>MathNet.Numerics.dll</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
In SDK-style csproj you can write something like:
<Target Name="CopyFilesTargetName" AfterTargets="Build">
<Copy SourceFiles="$(OutDir)\dlls\Some.dll;$(OutDir)\dlls\SomeOther.dll" DestinationFolder="$(OutDir)" />
</Target>
You can also use <Move instead of <Copy to move files

MSBuild CopyTask: Copying the same file to multiple locations

Is there a way to get the CopyTask to copy the same file to multiple locations?
eg. I've generated an AssemblyInfo.cs file and want to copy it across to all my projects before building.
Check out the RoboCopy build task which is part of the Community Build Tasks library which you can find here. RoboCopy can copy one source file to multiple destinations.
On a side note: why don't you use one AssemblyInfo file on solution level and link to that in your projects if you need the same information in every project? Check out my accepted answer on this question: Automatic assembly version number management in VS2008
Right, well maybe I should attempt to do the things I want to do before asking for help :)
<ItemGroup>
<AssemblyInfoSource
Include="AssemblyInfo.cs;AssemblyInfo.cs" />
<AssemblyInfoDestination
Include="$(Destination1)\AssemblyInfo.cs;$(Destination2)\AssemblyInfo.cs" />
</ItemGroup>
<Copy SourceFiles="#(AssemblyInfoSource)" DestinationFiles="#(AssemblyInfoDestination)" />
I had a need to copy the contents of a directory to multiple locations, this is what I came up with that works. So I am posting it here ins case anyone else is in similar need and comes across this question like I did.
<!-- Create a list of the objects in PublishURL so it will copy to multiple directories -->
<ItemGroup>
<PublishUrls Include="$(PublishUrl)"/>
</ItemGroup>
<PropertyGroup>
<Files>$(OutputPath)\**\*</Files>
</PropertyGroup>
<!-- CopyNewFiles will copy all the files in $(OutputPath) to all the directories in the
in $(PublishUrl). $(PublishUrl) can be a single directory, or a list of directories
separated by a semicolon -->
<Target Name ="CopyNewFiles">
<!-- Get list of all files in the output directory; Cross product this with all
the output directories. -->
<CreateItem Include ="$(Files)"
AdditionalMetadata="RootDirectory=%(PublishUrls.FullPath)">
<Output ItemName ="OutputFiles" TaskParameter ="Include"/>
</CreateItem>
<Message Text="'#(OutputFiles)' -> '%(RootDirectory)\%(RecursiveDir)'"/>
<Copy SourceFiles="#(OutputFiles)"
DestinationFolder ="%(RootDirectory)\%(RecursiveDir)"/>
</Target>
If you want to copy AssemblyInfo.cs to Folders A and B you would set the property Files="AssemblyInfo.cs" and PublishUrls="A;B"
What makes this work is the extra metadata in the CreateItem task AdditionalMetadata="RootDirectory=%(PublishUrls.FullPath)" so for each files found in File it creates 1 entry for each item found in PublishUrls. In your case of a single file the equivelent in writing out the xml would be:
<ItemGroup>
<OutputFiles Include="AssemblyInfo.cs">
<RootDirectory>A</RootDirectory>
</OutputFiles>
<OutputFiles Include="AssemblyInfo.cs">
<RootDirectory>B</RootDirectory>
</OutputFiles>
</ItemGroup>
Now if you copied the contents of a folder that had files 1.txt and 2.txt copied to A and B the equivalent xml would be:
<ItemGroup>
<OutputFiles Include="1.txt">
<RootDirectory>A</RootDirectory>
</OutputFiles>
<OutputFiles Include="2.txt">
<RootDirectory>A</RootDirectory>
</OutputFiles>
<OutputFiles Include="1.txt">
<RootDirectory>B</RootDirectory>
</OutputFiles>
<OutputFiles Include="2.txt">
<RootDirectory>B</RootDirectory>
</OutputFiles>
</ItemGroup>