How to exclude certain folders while copying using MSBUILD - msbuild

Seems like it should be fairly simple but I'm having trouble excluding folders when using the MSBUILD copy task. Here's what I'm doing:
<ItemGroup>
<Compile Include="$(_SolutionPath)$(_SolutionName)" />
<ProjectFiles Include="..\$(_WebDirectory)\*.csproj" Exclude="*.master.csproj"/>
<ExcludeFromBuild Include="..\$(_WebDirectory)\**\*.cs; ..\$(_WebDirectory)\**\*.sln; ..\$(_WebDirectory)\**\*.csproj; ..\$(_WebDirectory)\Web References; ..\$(_WebDirectory)\obj;"/>
<AppFolder Include="..\$(_WebDirectory)\**\*.*" Exclude="$(ExcludeFromBuild)"/>
</ItemGroup>
<Copy SourceFiles="#(AppFolder)" DestinationFiles="c:\test\%(RecursiveDir)%(FileName)%(Extension)"/>
In the item group section I have an ExcludeFromBuild item which lists out the file types i want to exclude. On top of that I want to exclude the "obj" and "Web References" folder.
How can I accomplish this? Please let me know if more information is needed. Thank you.
shahzad

You need to create a new ItemGroup for that. I've added AppFolderWithExclusions below:
<ItemGroup>
<Compile Include="$(_SolutionPath)$(_SolutionName)" />
<ProjectFiles Include="..\$(_WebDirectory)\*.csproj" Exclude="*.master.csproj"/>
<ExcludeFromBuild Include="..\$(_WebDirectory)\**\*.cs; ..\$(_WebDirectory)\**\*.sln; ..\$(_WebDirectory)\**\*.csproj; ..\$(_WebDirectory)\Web References; ..\$(_WebDirectory)\obj;"/>
<AppFolder Include="..\$(_WebDirectory)\**\*.*" Exclude="$(ExcludeFromBuild)"/>
<AppFolderWithExclusions Include="#(AppFolder)" Exclude="obj\**\*.*;Web References\**\*.*" />
</ItemGroup>
(untested; may include syntax typos)

Related

Can a task ItemGroup glob files?

I have an ItemGroup declared as follows:
<ItemGroup>
<MyCustomProjectType Include="..\path_to_my_project">
<Name>MyProjectName</Name>
</MyCustomProjectType>
</ItemGroup>
This is a custom project type that I want to perform some specific manipulations on.
Later I have a Target (example only but it communicates what I am after):
<Target Name="MyTarget">
<ItemGroup>
<CustomProjectReferenceFiles
KeepMetadata="Name"
Include="#(MyCustomProjectType->'%(Identity)\%(Name)\**\*')"
Exclude="**\*.x;**\*.y"
/>
</ItemGroup>
<Message Text="#(CustomProjectReferenceFiles)" />
</Target>
So I have a Target based ItemGroup where I am attempting, using a transform, to create a new Include. This does run, but it appears the Include is literally set to:
..\path_to_my_project\MyProjectName\**\*
AKA that glob/wildcards are not expanded.
I'm pretty new to MSBuild so maybe I am missing something in my search of the documentation. One solution I thought of here would be just just create a new Custom Task that handles pulling out the files I need and setting that Output on an intermediate Target.
I also found this SO question:
https://stackoverflow.com/a/3398872/1060314
Which brings up the point about CreateItem being deprecated which leaves me with not knowing what the alternatives are.
The easiest way is to use an intermediate property so that the actual text is used and not the escaped transformed items:
<PropertyGroup>
<_CustomProjectReferenceFileIncludes>#(MyCustomProjectType->'%(Identity)\%(Name)\**\*')</_CustomProjectReferenceFileIncludes>
</PropertyGroup>
<ItemGroup>
<CustomProjectReferenceFiles
KeepMetadata="Name"
Include="$(_CustomProjectReferenceFileIncludes)"
Exclude="**\*.x;**\*.y"
/>
</ItemGroup>

Using the Zip task in MSBuild

I have been attempting to use the zip task of msbuild in a project I am working on at the moment.
My project file looks something like this:
<PropertyGroup> <MSBuildCommunityTasksPath>$(SolutionDir)\.build</MSBuildCommunityTasksPath> </PropertyGroup>
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />
<ItemGroup>
<FileToZip include="C:\FilePath"></FilesToZip>
<FileToZip include="C:\FilePath"></FilesToZip>
</ItemGroup>
<Target Name="BeforeBuild">
<PropertyGroup>
<ReleasePath>\releasepath</ReleasePath>
<Zip Files="#(FilesToZip)" WorkingDirectory="$(ReleasePath)" ZipFileName="HTMLeditor.html" ZipLevel="9" />
</Target>
However, the zip file updates but does not contain the files specified in the item group FilesToZip. I cannot figure out why they aren't being recognised! I have double checked file paths and they are correct. Any ideas?
I think you want to do something like this:
<ItemGroup>
<FileToZip include="C:\FilePath;C:\FilePath"/>
</ItemGroup>
As I mentioned in my comment, simply creating a variable (FileToZip) and repeating it twice with different values does not give you an array that contains both of the values. You end up with only the last value (and not an array at all). Your include attribute is a selector which is used to build the array and it can contain multiple values, wildcards and other patterns which are used to build out that array for you.
Here's a link to MSDN that gives you more information on how to use the Include attribute: http://msdn.microsoft.com/en-us/library/ms171454.aspx
I ditched the ItemGroup in the end, and went with another way of doing it.
<Target Name="Zip">
<CreateItem Include="FilesToInclude" >
<Output ItemName="ZipFiles" TaskParameter="Include"/>
<Zip ZipFileName="ZipFile.zip" WorkingDirectory="FolderToWriteZipTo" Files="#(ZipFiles)" />
</Target>
This method seemed to be easier and wasn't adding files to the root of the file.
Thanks for the help though guys.

How do I cherry-pick files to copy in msbuild and preserve the directory structure?

I have a web app which I'm compiling using steal, and then I just want to copy the files from it needed for production use, but I need to preserve the directory structure. So for example, the directory looks like this after running steal's build (which compiles js/css into the production.js/css files):
\WebApp\index.html
\WebApp\app\img\a.png
\WebApp\app\img\b.png
\WebApp\app\js\foo.js
\WebApp\app\js\bar.js
\WebApp\app\css\base.css
\WebApp\app\css\app.css
\WebApp\app\css\widget1.css
\WebApp\app\production.js
\WebApp\app\production.css
\WebApp\steal\steal.js
\WebApp\steal\README.md
\WebApp\steal\build\build.js
Out of this, I want to copy only a few specific files to the same dir structure:
\artifacts\staging\www\index.html
\artifacts\staging\www\app\img\a.png
\artifacts\staging\www\app\img\b.png
\artifacts\staging\www\app\production.js
\artifacts\staging\www\app\production.css
\artifacts\staging\www\steal\steal.js
Ideally I'd have something like this:
<PropertyGroup>
<WorkingDir>WebApp\</WorkingDir>
<OutputDir>artifacts\staging\www\</OutputDir>
</PropertyGroup>
...
<ItemGroup>
<CopyFiles Remove="#(CopyFiles)" /> <!-- clean existing items -->
<CopyFiles Condition="'$(Configuration)'=='Debug'"
Include="$(WorkingDir)\**\*.*"
Exclude="$(WorkingDir)\**\.svn\**" />
<CopyFiles Condition="'$(Configuration)'=='Release'"
Include="$(WorkingDir)\index.html;$(WorkingDir)\app\img\**\*.*;$(WorkingDir)\app\production.*;$(WorkingDir)\steal\steal.js;"
Exclude="$(WorkingDir)\**\.svn\**" />
</ItemGroup>
<Copy SourceFiles="#(CopyFiles)"
DestinationFolder="#(CopyFiles->'$(OutputDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
The problem of course the directory structure isn't preserved, and I actually just get all of the files into the $(OutputDir) with no sub-directories. %(RecursiveDir) is the expansion of ** but since I've explicitly specified most paths, it doesn't actually take effect.
Now I know I can brute force this with a bunch of copy tasks and itemgroups, but that introduces its own problems, aside from being ugly. For one, it's error-prone, since if someone wants to add an item they have to be sure to use a unique itemgroup name (this build script is big and does many other tasks), and ensure several lines are all in sync.
There must be a better way than this?
<ItemGroup>
<IndexFiles Include="$(WorkingDir)\index.html" />
<ImgFiles Include="$(WorkingDir)\app\img\**\*.*" />
<AppFiles Include="$(WorkingDir)\app\production.*" />
...
</ItemGroup>
<Copy SourceFiles="#(IndexFiles)"
DestinationFolder="#(IndexFiles->'$(OutputDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
<Copy SourceFiles="#(ImgFiles)"
DestinationFolder="#(ImgFiles->'$(OutputDir)\app\img\%(RecursiveDir)%(Filename)%(Extension)')" />
<Copy SourceFiles="#(AppFiles)"
DestinationFolder="#(AppFiles->'$(OutputDir)\app\%(RecursiveDir)%(Filename)%(Extension)')" />
....
I had the same problem, and after some struggling I managed to do it. The key is to specify folders you want to include, after "**".
<ItemGroup>
<CopyFiles Include="$(WorkingDir)\index.html" />
<CopyFiles Include="$(WorkingDir)\**\app\img\**\*.*" />
<CopyFiles Include="$(WorkingDir)\**\app\production.*" />
...
</ItemGroup>
As a result, the output directory will contain the app folder with all subdirectories of "img", and all files named "production".
As a note- the part with "RecursiveDir" remains unchanged.
Just to add to the pot.
You can also go with an "Exclude Some Files" strategy.
The below will get all *.txt and *.doc files...but also exclude files of a specific name.
The question is.....are you more interested in including certain files...or.....excluding certain files.
Both "tricks" are needed from time to time.
<ItemGroup>
<MyExcludeFiles Include="$(WorkingDir)\**\SuperSecretStuff.txt" />
<MyExcludeFiles Include="$(WorkingDir)\**\SuperSecretStuff.doc" />
</ItemGroup>
<ItemGroup>
<MyIncludeFiles Include="$(WorkingDir)\**\*.txt" Exclude="#(MyExcludeFiles)"/>
<MyIncludeFiles Include="$(WorkingDir)\**\*.doc" Exclude="#(MyExcludeFiles)"/>
</ItemGroup>
<Copy
SourceFiles="#(MyIncludeFiles)"
DestinationFiles="#(MyIncludeFiles->'$(OutputDir)\%(RecursiveDir)%(Filename)%(Extension)')"
/>
Can you reverse your logic somewhat and include everything using ** and then exclude the files you don't want:
<CopyFiles Condition="'$(Configuration)'=='Debug'"
Include="$(WorkingDir)\**\*.*"
Exclude="$(WorkingDir)\**\.svn\**" />
<CopyFiles Condition="'$(Configuration)'=='Release'"
Include="$(WorkingDir)\**\*.*"
Exclude="$(WorkingDir)\**\.svn\**;$(WorkingDir)\app\css\*.*;$(WorkingDir)\app\js\*.*;$(WorkingDir)\steal\README.md;$(WorkingDir)\steal\build\*.*" />
You can then use the $(RecursiveDir) property.

Including files with directory specified separately in MSBuild

This seems like it should be simple but I can't work it out from the reference and my google-fu is apparently weak.
I just want to specify the file names and base folder separately in the build file...
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TestFilesWithFolder>
B:\Root\Test1.*;
B:\Root\Test2.*
</TestFilesWithFolder>
<TestFiles>Test1.*;Test2.*</TestFiles>
<TestFileRoot>B:\Root</TestFileRoot>
</PropertyGroup>
<Target Name="Build">
<ItemGroup>
<TestFilesGroupWithFolder Include="$(TestFilesWithFolder)" />
<TestFilesGroup Include="$(TestFileRoot)\$(TestFiles)" />
</ItemGroup>
<Warning Text="Source files with folder: #(TestFilesGroupWithFolder)" />
<Warning Text="Source files: #(TestFilesGroup)" />
</Target>
</Project>
When I run this, the first warning shows both files as expected, but the second warning only shows the first file (since the straight string concat put the folder name on the first but not second).
How would I get the ItemGroup "TestFilesGroup" to include both the files given the "TestFiles" and "TestFileRoot" properties?
It is possible to convert a semicolon delimited list of things into an item, which would make this possible, except that the items in your property contain wildcards, so if you want to have MSBuild treat them as items in a list, at the moment MSBuild first sees it the path must be valid. There may be a way to do that but I can't think of one. In other words...
<ItemGroup>
<TestFiles Include="$(TestFiles)" />
</ItemGroup>
...only works if $(TestFiles) contains a delimited list of either things with no wildcards, or qualified paths that actually exist.
Further, MSBuild can't compose a path with a wildcard inside the Include attribute and evaluate it at the same time, so you need a trick to first compose the full path separately, then feed it into the Include attribute. The following will work, but it requires changing your delimited property into a set of items. It batches a dependent target on this item list, with each batched target execution calculating a meta value for one item, which is stored off in a new meta value. When the original target executes, it is able to use that meta value in a subsequent Include.
<PropertyGroup>
<TestFilesWithFolder>
D:\Code\Test1.*;
D:\Code\Test2.*
</TestFilesWithFolder>
<TestFileRoot>D:\Code</TestFileRoot>
</PropertyGroup>
<ItemGroup>
<TestFilePattern Include="TestFilePattern">
<Pattern>Test1.*</Pattern>
</TestFilePattern>
<TestFilePattern Include="TestFilePattern">
<Pattern>Test2.*</Pattern>
</TestFilePattern>
</ItemGroup>
<Target Name="Compose" Outputs="%(TestFilePattern.Pattern)">
<ItemGroup>
<TestFilePattern Include="TestFilePattern">
<ComposedPath>#(TestFilePattern->'$(TestFileRoot)\%(Pattern)')</ComposedPath>
</TestFilePattern>
</ItemGroup>
</Target>
<Target Name="Build" DependsOnTargets="Compose">
<ItemGroup>
<TestFilesGroupWithFolder Include="$(TestFilesWithFolder)" />
</ItemGroup>
<Warning Text="Source files with folder: #(TestFilesGroupWithFolder)" />
<ItemGroup>
<ComposedTestFiles Include="%(TestFilePattern.ComposedPath)" />
</ItemGroup>
<Warning Text="Source files: #(ComposedTestFiles)" />
</Target>
Which produces the following output:
(Build target) ->
d:\Code\My.proj(80,5): warning : Source files with folder:
D:\Code\Test1.txt;D:\Code\Test2.txt
d:\Code\My.proj(84,5): warning : Source files:
D:\Code\Test1.txt;D:\Code\Test2.txt

How do I generate an MsBuild itemList from another item list based on the current item's directory name?

i'm taking a list of files *.config and copying them to a list of directories. The directories are relative to a path C:\branches\ have a name and then the name.UnitTest.
so the copy looks like this without being refactored/batched:
<Target Name="CopyClientConfigs">
<ItemGroup>
<ClientConfigDestinations Include="$(LocalSourcePath)\Module1\Module1.UnitTest\;
$(LocalSourcePath)\Module2\Module2.UnitTest\;
$(LocalSourcePath)\CommonControls\Module3\Module3.UnitTest\;
$(LocalSourcePath)\Administration\Module4\Module4.UnitTest\;
$(LocalSourcePath)\IndividualControls\Configuration\Module5\Module5.UnitTest\" />
<ClientConfigs
Include="$(ClientConfigPath)\*.config"
Exclude="$(ClientConfigPath)\P*.config" >
</ClientConfigs>
</ItemGroup>
<Copy
SourceFiles="#(ClientConfigs)"
DestinationFolder="%(ClientConfigDestinations.FullPath)"
/>
What I want is to be able to use this ItemGroup
<ItemGroup>
<MyModules Include="$(LocalSourcePath)\Module1;
$(LocalSourcePath)\Module2;
$(LocalSourcePath)\CommonControls\Module3;
$(LocalSourcePath)\Administration\Module4;
$(LocalSourcePath)\IndividualControls\Configuration\Module5"
/>
So the task would be like
Copy
SourceFiles="#(ClientConfigs)"
DestinationFolder="%(ClientConfigDestinations.FullPath)\*.UnitTest\"
/>
Or better
Copy
SourceFiles="#(ClientConfigs)"
DestinationFolder="%(ClientConfigDestinations.FullPath)\%(ClientConfigDestinations.NameOnly).UnitTest\"
/>
How do I refactor or properly batch this operation?
If I read your question right, I think you are trying to do a cross-product copy: copy all items in one ItemGroup to all the folders in a different group.
I actually have a neat target that I use to do this, as I hate the way TeamBuild puts all the binaries into a single folder - I want projects to be able to specify that their output is a "bundle" and that the output will also be copied to one or more locations.
To do this, I have two itemgroups: BundleFiles (which is the set of files that I want to copy) and BundleFolders which are the set of folders that I want to copy to.
<ItemGroup>
<BundleOutDir Include="FirstFolder;SecondFolder" />
<BundleFiles Include="file1;file2" />
</ItemGroup>
My target then contains two tasks like this:
<ItemGroup>
<FilesByDirsCrossProduct Include="#(BundleFiles)">
<BundleOutDir>%(BundleOutDir.FullPath)</BundleOutDir>
</FilesByDirsCrossProduct>
</ItemGroup>
This creates an uber item group containing a cross product of files by folders.
The copy is then pretty simple:
<Copy SourceFiles="#(FilesByDirsCrossProduct)"
DestinationFiles="#(FilesByDirsCrossProduct -> '%(BundleOutDir)\%(Filename)%(Extension)' ) "
SkipUnchangedFiles="true" />
This then copies the files to the folder specified within their meta data.
My target is actually a little more clever in that I can declare that bundles will go to sub folders and/or I can rename a file during the copy through meta data, but that's a different story
<Target Name="CopyClientConfigsBatched" Outputs="%(MyModules.FullPath)">
<Message Text="#(MyModules -> '%(FullPath)\%(FileName).UnitTest')"/>
<ItemGroup>
<ClientConfigs
Include="$(ClientConfigPath)\*.config"
Exclude="$(ClientConfigPath)\P*.config" >
</ClientConfigs>
</ItemGroup>
<Copy SourceFiles="#(ClientConfigs)" DestinationFolder="#(MyModules -> '%(FullPath)\%(FileName).UnitTest')"
SkipUnchangedFiles="true"/>
</Target>
target batching seems to have done it!