I am trying to get a MSBuild script to work for building and extracting for deployment a fairly large web application. (We are talking a few thousand files in several dozen directories, here. It's pretty much an inherited, legacy codebase, so not much I can do about it.) The build itself runs fine, but I can't seem to get copying the files to a drop location working properly.
Here is a snippet showing what I have in the build script:
<PropertyGroup Condition=" '$(UseBuildNumber)' == 'true' ">
<ReleaseDirectory>$(ReleaseBaseDirectory)$(ReleaseName)\$(BuildNumber)</ReleaseDirectory>
</PropertyGroup>
<ItemGroup>
<OutputFiles Include="
$(SourceRoot)**\*.aspx;
$(SourceRoot)**\*.dll;
$(SourceRoot)**\*.gif;
$(SourceRoot)**\*.ascx;
" />
</ItemGroup>
<Message Text="Output files ==> @(OutputFiles)" />
<Copy
SourceFiles="@(OutputFiles)"
DestinationFolder="$(ReleaseDirectory)"
SkipUnchangedFiles="false"
/>
I have put a few extra <Message/>
in there to verify that the individual paths expand correctly, and both $(SourceRoot)
and $(ReleaseDirectory)
do indeed have the correct paths. However, in the output, I get (full path elided for brevity):
Task "Message"
Output files ==> ...\Requirement1866**\*.aspx;...\Requirement1866**\*.dll;...\Requirement1866**\*.gif;...\Requirement1866**\*.ascx
Done executing task "Message".
Obviously, wildcard expansion has not been performed, and the following Copy
then (predictably enough) fails. I'm including only one; in reality this is repeated for each of the file name wildcard patterns (which in turn are many more than I included in the build script snippet above).
Using "Copy" task from assembly "Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "Copy"
Copying file from "*basedir*\Requirement1866**\*.aspx" to "*targetdir*\Requirement1866\*buildnumber*\*.aspx".
Command:
copy /y "*basedir*\Requirement1866**\*.aspx" "*targetdir*\Requirement1866\*buildnumber*\*.aspx"
d:\Builds\215\BuildType\TFSBuild.proj(120,5): error MSB3021: Unable to copy file "*basedir*\Requirement1866**\*.aspx" to "*targetdir*\Requirement1866\*buildnumber*\*.aspx". Illegal characters in path.
followed by:
d:\Builds\215\BuildType\TFSBuild.proj(120,5): error MSB3021: Unable to copy file "*basedir*\Requirement1866**\*.aspx" to "*targetdir*\Requirement1866\*buildnumber*\*.aspx". Illegal characters in path.
The only reasonably relevant hit I have come across in my efforts at Googling this was Copy Task - Illegal characters in path, but I'm already using an <ItemGroup>
, and the <CreateItem>
example (adapted) didn't work at all, aborting early with an error saying that TaskParameter
was unknown (sorry, I don't have the exact error message in front of me).
As a stopgap measure, I could go in and manually copy the relevant files from the build output directory to where I want them, but I want that process to be automated.
How do I, then, using MSBuild, copy files which are created during the build process into an output directory, while preserving the relative directory structure, without listing them one by one?
Basically, **
is a single path-element and thus must be correspondingly backslash-delimited in the Include
directive:
<ItemGroup>
<OutputFiles Include="
$(SourceRoot)\**\*.aspx;
$(SourceRoot)\**\*.dll;
$(SourceRoot)\**\*.gif;
$(SourceRoot)\**\*.ascx;
" />
</ItemGroup>
Note the difference between specifying e.g. $(SourceRoot)\**\*.aspx
as opposed to the broken $(SourceRoot)**\*.aspx
. If $(SourceRoot)
had ended with a \
, wildcard expansion probably would have worked at first too.
Second, in <Copy/>
, use %(RecursiveDir)
:
<Copy
SourceFiles="@(OutputFiles)"
DestinationFolder="$(ReleaseDirectory)\%(RecursiveDir)"
SkipUnchangedFiles="false"
/>
With these changes, it appears to work just like I want it to: files are copied to the intended target directory, and the directory structure at the source is preserved in the target location.
Another Suggestion, instead of adding Includes for every file type, it is easy to add Excludes.
<ItemGroup>
<OutputFiles Include="$(SourceRoot)**\*.*" Exclude="$(SourceRoot)**\*.cs;$(SourceRoot)**\*.resx" " />
</ItemGroup>
This way the output will include any JS, HTML, DHTML and other file types that are required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With