I have a web deployment project that is misbehaving. I have inherited an App_Data folder that contains a substantial number of .pdf files. Some of the filenames include invalid characters and are overly long. In my deployment project file I include the following ItemGroup at the end:
...
<ItemGroup>
<ExcludeFromBuild Include="$(SourceWebPhysicalPath)\**\*.pdf" />
</ItemGroup>
</Project>
But when I build the project I keep getting the following error:
error : Copying file $([System.IO.Path]::Combine($(_WDPSourceWebPhysicalPath),
App_Data\CWM2\393S097 Connection of an Embedded Network to elided's Network v1.pdf))
to obj\Debug\Source\App_Data\CWM2\393S097 Connection of an Embedded Network to
elided's Network v1.pdf failed. The path is not of a legal form.
I've tried adding wildcards to the App_Data folder but it's just not working. I guess it's conceivable that msbuild is unable to match those files for exclusion because the filename is invalid. Help?
I've seen a similar bug with my deployment project. I believe that VS2010 Web Deployment Projects has a bug that prevents copying any file that contains a quote character.
I filed a connect bug here: https://connect.microsoft.com/VisualStudio/feedback/details/631995/
The only workaround I know is to remove the quote character from the name of the file.
I found a workaround! you can change the file "C:\Program Files (x86)\MSBuild\Microsoft\WebDeployment\v10.0\Microsoft.WebDeployment.targets" by replacing the non working part with the one from the 2008 version. To do that, search for
<Target Name="_CopyBeforeBuild"
and replace the content in the xml tag with the following (taken from webdeploymentproject 2008)
<Target Name="_CopyBeforeBuild" Condition=" '$(EnableCopyBeforeBuild)' == 'true' or '#(ExcludeFromBuild)' != '' ">
<CreateItem Include="$(SourceWebPhysicalPath)\**\*.*" Exclude="#(ExcludeFromBuild)">
<Output ItemName="_WebFiles" TaskParameter="Include" />
</CreateItem>
<RemoveDir Directories="$(CopyBeforeBuildTargetPath)"/>
<MakeDir Directories="$(CopyBeforeBuildTargetPath)"/>
<Copy SourceFiles="#(_WebFiles)" DestinationFolder="$(CopyBeforeBuildTargetPath)\%(_WebFiles.SubFolder)%(_WebFiles.RecursiveDir)" />
<CreateProperty Value="$(CopyBeforeBuildTargetPath)">
<Output TaskParameter="Value" PropertyName="_AspNetCompilerSourceWebPath" />
</CreateProperty>
</Target>
Related
MSBuild quite powerful and important tool, but sometimes configuration of it is the same hard as making a spaceship or flying to Mars. Answers on next questions can help makes it a little bit more usefull and developer friendly:
- How the MSBuild create a Package(zip)?
- Does it use some temporary folders or direct from builds output ?
- Why can the output folder content differ from a package content
- What events can be used to add some custom logic?
The answers to this question could help me with the next long story short:
I'm working with a .Net Core solution, that has the dependency on some other solutions DLLs files( that are not referenced directly, but required to be in the solution folder). On post-build event CL there is the option added as an entry point "\TaskEP" that is a starting point of the next pipeline :
<PropertyGroup>
<PipelineCopyAllFilesToOneFolderForMsdeployDependsOn>
TaskEP;
Task2;
Task3;
$(PipelineCopyAllFilesToOneFolderForMsdeployDependsOn);
</PipelineCopyAllFilesToOneFolderForMsdeployDependsOn>
<RunPostBuildEvent>Always</RunPostBuildEvent>
Tasks are mostly done for add extra DLLs to package and in general looks like :
<Target Name="TaskEP" Condition="$(SomeConditions) == 'True'">
<Message Importance="high" Text="TaskEP: Copying some files..." />
<ItemGroup>
<ItemsName Include="$(MSBuildProjectDirectory)\..\..somepath..\Extra.dll;....dll" />
<FilesForPackagingFromProject Include="%(ItemsName.Identity)">
<DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</FilesForPackagingFromProject>
</ItemGroup>
</Target>
Basically, for me, this instruction to MSBuild to add "Extra.dll" to output package.
Pubxml is quite usual :
<PropertyGroup>
<WebPublishMethod>Package</WebPublishMethod>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<SiteUrlToLaunchAfterPublish />
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<_PackagePathShortened Condition="'$(_PackagePathShortened)' == ''">SuperWebSite</_PackagePathShortened>
<ExcludeApp_Data>False</ExcludeApp_Data>
<PackageLocation>..\..\..\..\BIN\WEB\Release\Publish\Super.WebApplication.zip</PackageLocation>
<PackageAsSingleFile>true</PackageAsSingleFile>
<TargetFramework>netcoreapp2.1</TargetFramework> ....
And one instruction that replaces the long long path in the package to Short and defined one:
<Target Name="AddReplaceRuleForAppPath" BeforeTargets="BeforePublish">
<Message Text="Adding replace rules for application path '$(PublishIntermediateOutputPath)' replace with '$(_PackagePathShortened)'" Importance="high" />
<EscapeTextForRegularExpressions Text="$(PublishIntermediateOutputPath)">
<Output PropertyName="_PackagePathRegex" TaskParameter="Result" />
</EscapeTextForRegularExpressions>
<!-- Add a replace rule for VSMSDeploy resp. MSdeploy to update the path -->
<ItemGroup>
<MsDeployReplaceRules Include="replaceFullPath">
<Match>$(_PackagePathRegex)</Match>
<Replace>$(_PackagePathShortened)</Replace>
</MsDeployReplaceRules>
</ItemGroup>
I also duplicated the same "Copy" tasks here in .pubxml file , play with different events "AfterBuild", "BeforePublish" but seems that all of the just ignoring. I can see the "Extra" DLLs files in the build output directory and all Info Messages on publishing, but not in the final "Super.WebApplication.zip" file !
The solution I found to copy extra files in .Net Core is taken from this documentation
<ItemGroup>
<DotnetPublishFiles Include="$(MSBuildProjectDirectory)\..\..somepath..\Extra.dll;....dll" >
<DestinationRelativePath>bin\x86\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
</DotnetPublishFiles>
</ItemGroup>
I have AssemblyInfo.cs file automatically generated during build. Here's part of .csproj file:
<PropertyGroup>
<Major>2</Major>
<Minor>3</Minor>
<Build>0</Build>
<Revision>0</Revision>
</PropertyGroup>
<Target Name="BeforeBuild">
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="C:\Program Files\VisualSVN Server\bin">
<Output TaskParameter="Revision" PropertyName="Revision" />
</SvnVersion>
<AssemblyInfo CodeLanguage="CS"
OutputFile="$(MSBuildProjectDirectory)\Properties\VersionInfo.cs"
AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)"
AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)"/>
</Target>
But I don't know how to specify Major and Minor properties outside .csproj file so I don't have to unload project every time I want to change version. I need either to load them from special text file inside project or to somehow set them in project properties dialog. Any suggestions?
Used ReadLinesFromFile to make version in separate file:
<ReadLinesFromFile File="Properties\Version.txt">
<Output TaskParameter="Lines" ItemName="Ver" />
</ReadLinesFromFile>
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="C:\Program Files (x86)\VisualSVN Server\bin">
<Output TaskParameter="Revision" PropertyName="Revision" />
</SvnVersion>
<Message Text="Version: #(Ver).$(Revision)" />
<AssemblyInfo
CodeLanguage="CS"
OutputFile="$(MSBuildProjectDirectory)\Properties\VersionInfo.cs"
AssemblyVersion="#(Ver).$(Revision)"
AssemblyFileVersion="#(Ver).$(Revision)"/>
It is possible to do this using the ability of PropertyFunctions to call certain .NET functions directly. Essentially, you can call File.ReadAllText() when setting a property value.
<PropertyGroup>
<Version>$([System.IO.File]::ReadAllText("Version.txt"))</Version>
</PropertyGroup>
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="C:\Program Files (x86)\VisualSVN Server\bin">
<Output TaskParameter="Revision" PropertyName="Revision" />
</SvnVersion>
<Message Text="Version: $(Version).$(Revision)" />
<AssemblyInfo
CodeLanguage="CS"
OutputFile="$(MSBuildProjectDirectory)\Properties\VersionInfo.cs"
AssemblyVersion="$(Version).$(Revision)"
AssemblyFileVersion="$(Version).$(Revision)"/>
The Version.txt file would just contain the first three version numbers, i.e. "1.2.3" or whatever.
I'm not sure exactly when this was added to MSBuild, but it was probably after this question was asked and answered.
Use property pages, so you could set these properties on the project property sheets dialogs.
You will need to create a properties file and edit your project file (only once) to add an import directive to your properties file. Here is an example.
You can use your external tools
<Exec Command="newversion incMinor AssemblyInfo.cs > newversion.log" />
If it's locking of the csproj files by VS that's your concern, my answer to this question - How to turn off caching of build definitions in Visual studio might help you.
You could move the content of your BeforeBuild task (including the version propertygroup) into a separate proj file and invoke it with an MSBuild task (using a random filename generated by the example in the linked answer above). This would allow you to manually edit your version number properties without having to unload/load your csproj file.
I created a custom common target "RealClean" which remove every files in the output and "intermediate output" directory. I put it in the Microsoft.Common.targets file.
When I run MsBuild on my csproj everything is fine.
But when I run MsBuild on my sln (which just references a list of csproj) I have the following error
error MSB4057: The target "RealClean" does not exist in the project.
Here is the command line I enter to run MsBuild
C:\Windows\Microsoft .NET\Framework\v3.5\MsBuild.exe /p:Configuration="Release";OutputPath="..\..\MSBuild.Referentiel.net35";nowarn="1591,1573" /t:RealClean mySolution.sln
Any hint?
I had the same issue but didn't want to modify things outside of the source tree in order to get this to work. Adding files to C:\Program Files... means that you have to do this manually on every dev machine to get the same behavior.
I did three things:
1) Created a Custom targets file which I import into every C# and/or VB/F# project in my solution by adding the following to each proj file:
<!-- Rest of project file -->
<PropertyGroup Condition="'$(SolutionDir)' == '' or '$(SolutionDir)' == '*undefined*'">
<!-- Relative path to containing solution folder -->
<SolutionDir>..\</SolutionDir>
</PropertyGroup>
<Import Project="$(SolutionDir)CommonSettings.targets" />
2) Added a clean target which gets called after the real Clean (using the AfterTargets attribute from MSBuild 4.0):
<Target Name="CleanCs" AfterTargets="Clean">
<Message Text="Deep cleaning C# project..." />
<CreateItem Include="$(OutDir)**\*.*; $(ProjectDir)\obj\**\*.*; $(IntermediateOutputPath)**\*.*"
Exclude="**\bin\**\*.vshost.exe; $(IntermediateOutputPath)**\*.log">
<Output TaskParameter="Include" ItemName="AfterClean_FilesToDelete"/>
</CreateItem>
<Delete Files="#(AfterClean_FilesToDelete)" />
<CreateItem Include="$(ProjectDir)\obj\" >
<Output TaskParameter="Include" ItemName="AfterClean_DirectoriesToDelete" />
</CreateItem>
<CreateItem Include ="$(ProjectDir)\bin\" Condition="'$(TargetExt)' != '.exe'" >
<Output TaskParameter="Include" ItemName="AfterClean_DirectoriesToDelete"/>
</CreateItem>
<RemoveDir ContinueOnError="true" Directories="#(AfterClean_DirectoriesToDelete)" />
</Target>
3) In my continuous integration MSBuild project I check and make sure that all proj files have #1:
<ItemGroup>
<!-- Exclude viewer acceptance tests as they must compile as x86 -->
<CheckProjects_CsProjects Include="**\*.csproj" />
</ItemGroup>
<Target Name="CheckProjects">
<!--
Look for C# projects that don't import CommonSettingsCs.targets
-->
<XmlRead XPath="//n:Project[count(n:Import[#Project[contains(string(), 'CommonSettingsCs.targets')]]) = 0]/n:PropertyGroup/n:AssemblyName/text() "
XmlFileName="%(CheckProjects_CsProjects.Identity)"
Namespace="http://schemas.microsoft.com/developer/msbuild/2003"
Prefix="n" >
<Output TaskParameter="Value" ItemName="CheckProjects_CsMissingImports"/>
</XmlRead>
<Error Text="Project missing CommonSettingsCs.targets: %(CheckProjects_CsMissingImports.Identity)"
Condition="'%(CheckProjects_CsMissingImports.Identity)' != ''" />
</Target>
This prevents developers from forgetting to add #1. You could create your own project template to ensure that al new projects have this by default.
The advantage to this approach is setting up a new source tree enlistment doesn't involve anything more than getting the current source tree. The downside is that you have to edit the project files once when you create them.
To work on solution file, MSBuild creates a temporary MSBuild project file containing only some targets like Build and Clean. So you can't call your custom target on a solution file.
Madgnome is probably right. But I wanted to add that you should not be editing the Microsoft.common.targets files. If you do so you risk having a different build process on that machine versus what everybody else has. In your case you could have created a new MSBuild file with just the RealClean target and placed it at
C:\Program Files (x86)\MSBuild\v4.0\Custom.After.Microsoft.Common.targets
or for 32 bit
C:\Program Files\MSBuild\v4.0\Custom.After.Microsoft.Common.targets
and essentially that would be the same as putting that file inside of Microsoft.Common.targets, except you don't have to modify that file.
I am having some trouble with a MSBuild file that I am trying to compile some custom libraries.
<PropertyGroup>
<FullVersion>10.8.0.0</FullVersion>
</PropertyGroup>
<ItemGroup>
<LibsToBuild Include=".\Lib1">
<Bin>bin\*.*</Bin>
<Project>Library 1</Project>
<Build>ReleaseNoProtect</Build>
<Version>CurrentVersion</Version>
</LibsToBuild>
<LibsToBuild Include=".\Lib2">
<Bin>bin\*.*</Bin>
<Project>Library 2</Project>
<Build>ReleaseLibrary</Build>
<Version>CurrentVersion</Version>
</LibsToBuild>
</ItemGroup>
<ItemGroup>
<LibsToCopy Include="#(LibsToBuild->'%(FullPath)\%(Version)\%(Bin)')" />
</ItemGroup>
<Target Name="BuildLibs">
<MSBuild
Projects="#(LibsToBuild->'%(FullPath)\%(Version)\Build\Build.proj')"
Targets="%(LibsToBuild.Build)"
Properties="Configuration=Release;APP_VERSION=$(FullVersion);PROJECT_NAME=%(LibsToBuild.Project)"
/>
<Copy
SourceFiles="#(LibsToCopy)"
DestinationFiles="#(LibsToCopy->'.\Libraries\CurrentVersion\%(RecursiveDir)%(Filename)%(Extension)')"
/>
<!--
<Exec Command='xcopy /y #(LibsToCopy) .\Libraries\CurrentVersion' />
-->
</Target>
When I run this through MSBuild, all of the compiles work, but the copy files does not. MSBuild complains with the following errors:
Copying file from "X:\Projects\Lib1\Master\bin\*.*" to ".\Libraries\CurrentVersion\*.*".
X:\Projects\Test Release.build(35,3): error MSB3021: Unable to copy file "X:\Projects\Lib1\Master\bin\*.*" to ".\Libraries\CurrentVersion\*.*". Illegal characters in path.
Copying file from "X:\Projects\Lib2\Master\bin\*.*" to ".\Libraries\CurrentVersion\*.*".
X:\Projects\Test Release.build(35,3): error MSB3021: Unable to copy file "X:\Projects\Lib1\Master\bin\*.*" to ".\Libraries\CurrentVersion\*.*". Illegal characters in path.
I am unable to figure out why the transform in the "LibsToCopy" ItemGroup isn't expanding the filename wildcards.
I have also attempted to use xcopy, but it doesn't like the wildcards either.
Thanks!
Dave
I had a similar problem. Try this, just before the <Copy> task
<CreateItem Include="#(LibsToBuild->'%(FullPath)\%(Version)\%(Bin)')">
<Output TaskParameter="Include" ItemName="LibsToCopy" />
</CreateItem>
Unfortunately the documentation says CreateItem task is deprecated, so I don't know how to solve tis problem in the future.
After MSbuild has built my solution (with an asp.net website), and the webdeployment project has built and put the website in the directory _PublishedWebsites:
c:\mybuilds\buildName\Daily_20090519.3\Release_PublishedWebsites\MyWebsite.
How do I copy this to the fixed directory where IIS points to for the test website?
I have found loads of code snippets, but I cannot seem to find one that will take into account the fact that this directory name changes.
This is pretty easy. You can edit the project and insert something similar to the following.
<PropertyGroup>
<OutputDest>$(MSBuildProjectDirectory)\..\OutputCopy\</OutputDest>
</PropertyGroup>
<Target Name="AfterBuild">
<!-- Create an item with all the output files -->
<ItemGroup>
<_OutputFiles Include="$(OutputPath)**\*" Exclude="$(OutputPath)obj\**\*" />
</ItemGroup>
<!-- You probably don't want to include the files in the obj folder so exclude them. -->
<Message Text="OutputDest : $(OutputDest)" />
<Copy SourceFiles="#(_OutputFiles)"
DestinationFiles="#(_OutputFiles->'$(OutputDest)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
Is this what you are looking for?
My Book: Inside the Microsoft Build Engine : Using MSBuild and Team Foundation Build
I'm using different technique.
<PropertyGroup>
<BinariesRoot>c:\BinariesForIis\</BinariesRoot>
</PropertyGroup>
The c:\BinariesForIis\ will be used for direct output compiled binaries (before copy to ...\Daily_20090519.3\Release_ ...).