I'm trying to build an ItemGroup in an MSBuild script which contains a list of folders directly below a given 'Root' folder. So - in this example...
+ Root folder
---- Sub Folder 1
-------- Sub-Sub Folder 1
-------- Sub-Sub Folder 2
---- Sub Folder 2
---- Sub Folder 3
... I would want my ItemGroup to contain "Sub Folder 1", "Sub Folder 2" and "Sub Folder 3".
There may be a number of files at any point in the hierarchy, but I'm only interested in the folders.
Can anyone help!?
In MSBuild 4.0 this is possible:
<ItemGroup>
<Folders Include="$([System.IO.Directory]::GetDirectories("$(RootFolder)"))" />
</ItemGroup>
Property Functions: http://msdn.microsoft.com/en-us/library/dd633440.aspx
<PropertyGroup>
<RootFolder>tmp</RootFolder>
</PropertyGroup>
<ItemGroup>
<AllFiles Include="$(RootFolder)\**\*"/>
<OnlyDirs Include="#(AllFiles->'%(Directory)')"/>
</ItemGroup>
#(OnlyDirs) might contain duplicates, so you could either use the RemoveDuplicatesTask :
<Target Name="foo">
<RemoveDuplicates Inputs="#(OnlyDirs)">
<Output TaskParameter="Filtered" ItemName="UniqueDirs"/>
</RemoveDuplicates>
</Target>
or use CreateItem with batching for %(AllFiles.Identity) or with msbuild 3.5:
<Target Name="foo">
<ItemGroup>
<UniqueDirs Include="%(AllFiles.Directory)"/>
</ItemGroup>
</Target>
MSBuild 4.0:
<PropertyGroup>
<RootFolder>tmp</RootFolder>
</PropertyGroup>
<ItemGroup>
<AllFiles Include="$(RootFolder)\**\*"/>
<OnlyDirs Include="#(AllFiles->'%(RootDir)%(Directory)'->Distinct())"/>
</ItemGroup>
The MSBuild Extension pack has a task called FindUnder, which returns an itemgroup of files or folders below a certain path. The following task will achieve what you want, returning an itemgroup containing Sub Folder 1, Sub Folder 2, and Sub Folder 3, but not Sub-Sub Folder 1 or Sub-Sub Folder 2:
<MSBuild.ExtensionPack.FileSystem.FindUnder
TaskAction="FindDirectories"
Path="$(RootFolder)"
Recursive="False">
<Output ItemName="FoundFolders" TaskParameter="FoundItems" />
</MSBuild.ExtensionPack.FileSystem.FindUnder>
This MSDN Forum post has a custom task that deals with the empty directory case (upvoted accepted as its a v useful answer)
Related
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>
Right now in my msbuild script is a task to delete a folder
<RemoveDir Directories="$(Bin)"/>
However I'd rather delete the contents of the folder but leave the folder be (in case someone has the folder open in Windows Explorer). How can I do that?
This will remove all files and subfolders:
<Target Name="CleanFolder">
<PropertyGroup>
<TargetFolder>c:\clean</TargetFolder>
</PropertyGroup>
<ItemGroup>
<FilesToClean Include="$(TargetFolder)\**\*"/>
<Directories Include="$([System.IO.Directory]::GetDirectories('$(TargetFolder)', '*', System.IO.SearchOption.AllDirectories))"
Exclude="$(TargetFolder)"/>
</ItemGroup>
<Delete Files="#(FilesToClean)" ContinueOnError="true"/>
<RemoveDir Directories="#(Directories)" />
</Target>
It would also be good to drop open connections using the openfiles tool:
openfiles /disconnect /ID *
Download and install the msbuild extension pack then use
<MSBuild.ExtensionPack.FileSystem.Folder TaskAction="RemoveContent" Path="$(Bin)" />
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
I am writing my first MSBuild script and ran into a problem.
I have several projects, defined in an itemgroup
<ItemGroup>
<Projects Include="Project1Dir\Project1.csproj"/>
<Projects Include="Project2Dir\Project2.csproj"/>
</ItemGroup>
Then, on deployment step, I am trying to do this:
The following should collect all the files for deployment into separate itemgroups for each project ("Project1deploymentFiles" and "Project2deploymentFiles")
<CreateItem Include="$(WebPublishDir)\%(Projects.Filename)\**\*.*">
<Output ItemName="%(Projects.Filename)deploymentFiles" TaskParameter="Include"/>
</CreateItem>
Thes line, should copy each project's files into separate folder
<Copy SourceFiles="#(%(Projects.Filename)deploymentFiles)" DestinationFolder="$(DeploymentDir)\%(Projects.Filename)\%(RecursiveDir)\" />
But it seems that MSBuild resolves %(RecursiveDir) metadata to empty string, as all the files are copied to the same root folder (different for each project).
Any suggestions what am I doing wrong here?
I've found a solution myself:
<CreateItem Include="$(WebPublishDir)\%(Projects.Filename)\**\*.*" AdditionalMetadata="ProjectDir=%(Projects.Filename)\">
<Output ItemName="deploymentFiles" TaskParameter="Include"/>
</CreateItem>
<Copy SourceFiles="#(deploymentFiles)" DestinationFolder="$(DeploymentDir)\%(ProjectDir)\%(RecursiveDir)\" />
Main idea here is to use one item for all projects and just add AdditionalMetadata to values, containing projectname
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>