MSBuild - keep ItemGroup in separate file - msbuild

I have the following MSBuild target working.
<Target Name="MyTarget">
<ItemGroup>
<ExcludeList Include="$(ProjectPath)\**\.svn\**"/>
<ExcludeList Include="$(ProjectPath)\**\obj\**"/>
<ExcludeList Include="$(ProjectPath)\**\*.config"/>
<ExcludeList Include="$(ProjectPath)\**\*.cs"/>
<ExcludeList Include="$(ProjectPath)\**\*.csproj"/>
<ExcludeList Include="$(ProjectPath)\**\*.user"/>
</ItemGroup>
<ItemGroup>
<ZipFiles Include="$(ProjectPath)\**\*.*" Exclude="#(ExcludeList)" />
</ItemGroup>
<Zip Files="#(ZipFiles)"
WorkingDirectory="$(ProjectPath)"
ZipFileName="$(PackageDirectory)\$(ProjectName).package.zip"
ZipLevel="9" />
</Target>
I'd like to store the ExcludeList ItemGroup in a separate file, because I will have multiple msbuild targets in separate files that all need to use that list, and I don't want to recreate it and maintain multiple copies.
What is the best way to externalize an ItemGroup and load it into multiple msbuild scripts?

Create your ItemGroup in a separate msbuild file, then you can include it with Import Element statement.
Make.targets
<Project DefaultTargets = "Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<ItemGroup Condition="'$(ProjectPath)' != ''">
<ExcludeList Include="$(ProjectPath)\**\.svn\**"/>
<ExcludeList Include="$(ProjectPath)\**\obj\**"/>
<ExcludeList Include="$(ProjectPath)\**\*.config"/>
<ExcludeList Include="$(ProjectPath)\**\*.cs"/>
<ExcludeList Include="$(ProjectPath)\**\*.csproj"/>
<ExcludeList Include="$(ProjectPath)\**\*.user"/>
<ExcludeList Include="$(ProjectPath)\**\*.proj"/>
</ItemGroup>
</Project>
Make.proj
<Project DefaultTargets = "Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
<PropertyGroup>
<ProjectPath>D:\Temp</ProjectPath>
</PropertyGroup>
<Import Project=".\Make.targets" Condition="'$(ProjectPath)' != ''" />
<Target Name = "Build">
<Message Text="Exclude = #(ExcludeList)" />
</Target>
</Project>
When I run msbuild from D:\temp (with the two files, otherwise empty) i get:
Build started 24-01-2012 16:50:33.
Project "D:\Temp\Make.proj" on node 1 (default targets).
Build:
Exclude = D:\Temp\Make.proj
Done Building Project "D:\Temp\Make.proj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)

Related

msbuild to copy to multiple locations defined in item metadata

I have an item with metadata I want to copy to perform some actions on and the result to occur in multiple locations,
for example, I want to copy the file to multiple locations:
<ItemGroup>
<MyItem Include="myFile.txt">
<Out>c:\blah;c:\test</Out>
</MyItem>
</ItemGroup>
how would I setup a target to create c:\blah and c:\test if they dont exist, then copy myFile.txt to c:\blah\myFile.txt and c:\test\myFile.txt
I also want to get the list of full output paths (c:\blah\myFile.txt and c:\test\myFile.txt) if I want to clean them during a clean.
If you dont want to change the structure of you ItemGroup, you need to handle that you have a nested ItemGroup (the MetaDataElement Out). Therefor you will need to batch the ItemGroup MyItem to the target and inside you can batch Out. I made a small example project:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="CopyFiles" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<ItemGroup>
<MyItem Include="myFile.txt">
<Out>c:\blah;c:\test</Out>
</MyItem>
<MyItem Include="myFile2.txt">
<Out>c:\blah2;c:\test2</Out>
</MyItem>
</ItemGroup>
<Target Name="CopyFiles"
Inputs="%(MyItem.Identity)"
Outputs="%(MyItem.Identity)\ignore_this.msg">
<PropertyGroup>
<File>%(MyItem.Identity)</File>
</PropertyGroup>
<ItemGroup>
<Folders Include="%(MyItem.Out)" />
</ItemGroup>
<Message Text="%(Folders.Identity)\$(File)" />
</Target>
</Project>
The Output will be:
Project "D:\TEMP\test.proj" on node 1 (default targets).
CopyFiles:
c:\blah\myFile.txt
c:\test\myFile.txt
CopyFiles:
c:\blah2\myFile2.txt
c:\test2\myFile2.txt
Done Building Project "D:\TEMP\test.proj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
What you want to do is a concept called MSBuild Batching.
It allows you to divide item lists into different batches and pass each of those batches into a task separately.
<Target Name="CopyFiles">
<ItemGroup Label="MyFolders">
<Folder Include="c:\blah" />
<Folder Include="C:\test" />
</ItemGroup>
<Copy SourceFiles="myFile.txt" DestinationFolder="%(Folder.Identity)\">
<Output TaskParameter="CopiedFiles" ItemName="FilesCopy" />
</Copy>
</Target>
How about this:
<Target Name="CopyFiles">
<!--The item(s)-->
<ItemGroup>
<MyItem Include="myFile.txt"/>
</ItemGroup>
<!--The destinations-->
<ItemGroup>
<MyDestination Include="c:\blah"/>
<MyDestination Include="c:\test"/>
</ItemGroup>
<!--The copy-->
<Copy SourceFiles="#(MyItem)" DestinationFolder="%(MyDestination.FullPath)" />
<ItemGroup>
<FileWrites Include="%(MyDestination.FullPath)\*" />
</ItemGroup>
<!--The output -->
<Message Text="FileWrites: #(FileWrites)" Importance="high"/>
</Target>

using dirPath Provider with WebDeploy

I have a wcf application hosted in iis that i am trying to package using webdeploy. Everything works great with the visual studio tools, but i need to also create a logs folder and set permissions on it. For this i created a ProjectName.wpp.target file in my web project.
The file looks like this
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CreateLogsDirectory" AfterTargets="AddIisSettingAndFileContentsToSourceManifest">
<!-- This must be declared inside of a target because the property
$(_MSDeployDirPath_FullPath) will not be defined at that time. -->
<ItemGroup>
<MsDeploySourceManifest Include="dirPath">
<Path>$(_MSDeployDirPath_FullPath)\logs</Path>
<enableRule>DoNotDeleteRule</enableRule>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
<Target Name="DeclareCustomParameters" AfterTargets="AddIisAndContentDeclareParametersItems">
<!-- This must be declared inside of a target because the property
$(_EscapeRegEx_MSDeployDirPath) will not be defined at that time. -->
<ItemGroup>
<MsDeployDeclareParameters Include="LogsDirectoryPath">
<Kind>ProviderPath</Kind>
<Scope>dirPath</Scope>
<Match>^$(_EscapeRegEx_MSDeployDirPath)\\logs$</Match>
<Value>$(_DestinationContentPath)/log</Value>
<ExcludeFromSetParameter>True</ExcludeFromSetParameter>
</MsDeployDeclareParameters>
</ItemGroup>
</Target>
</Project>
i can see that dirPath provider is added to the sourcemanifest file, but when i deploy the package it tries to create the source file path. Essentially the LogsDirectoryPAth item is not replacing the path. can someone point out what i need to do ? thanks !
Considering your additional directory is inside your web application, it's not really necessary to include another dirPath provider and doing so would only lead to more headaches (additional parameter declarations, etc).
Here are some helpers I use to help with this kind of thing. Your application specific values can be declared in your wpp.targets file:
<!-- Items specific to your application (these should be in your wpp.targets) -->
<ItemGroup>
<SkipDeleteFiles Include="logs" />
<EmptyDirectoriesToDeploy Include="logs" />
<AdditionalAcls Include="logs">
<AclAccess>Write</AclAccess>
</AdditionalAcls>
</ItemGroup>
And the following convention-based definitions can be either put in you wpp.targets or in a common targets file that can be imported into your wpp.targets:
<!--
Empty directories
-->
<PropertyGroup>
<BeforeAddContentPathToSourceManifest>
$(BeforeAddContentPathToSourceManifest);
CreateEmptyDirectories;
</BeforeAddContentPathToSourceManifest>
</PropertyGroup>
<Target Name="CreateEmptyDirectories">
<MakeDir Directories="$(_MSDeployDirPath_FullPath)\%(EmptyDirectoriesToDeploy.Identity)"
Condition="'#(EmptyDirectoriesToDeploy)' != ''" />
</Target>
<!--
Additional ACLs
-->
<ItemDefinitionGroup>
<AdditionalAcls>
<AclAccess>Write</AclAccess>
<ResourceType>Directory</ResourceType>
</AdditionalAcls>
</ItemDefinitionGroup>
<PropertyGroup>
<AfterAddIisSettingAndFileContentsToSourceManifest>
$(AfterAddIisSettingAndFileContentsToSourceManifest);
AddAdditionalAclsToSourceManifest;
</AfterAddIisSettingAndFileContentsToSourceManifest>
<AfterAddIisAndContentDeclareParametersItems>
$(AfterAddIisAndContentDeclareParametersItems);
AddAdditionalAclsDeclareParameterItems
</AfterAddIisAndContentDeclareParametersItems>
</PropertyGroup>
<Target Name="AddAdditionalAclsToSourceManifest">
<ItemGroup Condition="'#(AdditionalAcls)' != ''">
<MsDeploySourceManifest Include="setAcl">
<Path>$(_MSDeployDirPath_FullPath)\%(AdditionalAcls.Identity)</Path>
<setAclResourceType Condition="'%(AdditionalAcls.ResourceType)' != ''">%(AdditionalAcls.ResourceType)</setAclResourceType>
<setAclAccess>%(AdditionalAcls.AclAccess)</setAclAccess>
<AdditionalProviderSettings>setAclResourceType;setAclAccess</AdditionalProviderSettings>
</MsDeploySourceManifest>
</ItemGroup>
</Target>
<Target Name="AddAdditionalAclsDeclareParameterItems">
<ItemGroup Condition="'#(AdditionalAcls)' != ''">
<MsDeployDeclareParameters Include="Add %(AdditionalAcls.AclAccess) permission to %(AdditionalAcls.Identity) Folder">
<Kind>ProviderPath</Kind>
<Scope>setAcl</Scope>
<Match>^$(_EscapeRegEx_MSDeployDirPath)\\#(AdditionalAcls)$</Match>
<Description>Add %(AdditionalAcls.AclAccess) permission to %(AdditionalAcls.Identity) Folder</Description>
<DefaultValue>{$(_MsDeployParameterNameForContentPath)}/#(AdditionalAcls)</DefaultValue>
<DestinationContentPath>$(_DestinationContentPath)/#(AdditionalAcls)</DestinationContentPath>
<Tags>Hidden</Tags>
<ExcludeFromSetParameter>True</ExcludeFromSetParameter>
<Priority>$(VsSetAclPriority)</Priority>
</MsDeployDeclareParameters>
</ItemGroup>
</Target>
<!--
Skip delete files and directories
-->
<PropertyGroup>
<ImportPublishingParameterValuesDependsOn>
$(ImportPublishingParameterValuesDependsOn);
AddSkipDirectives;
</ImportPublishingParameterValuesDependsOn>
</PropertyGroup>
<ItemGroup>
<SkipDeleteItems Include="#(SkipDeleteFiles)"
Condition="'#(SkipDeleteFiles)' != ''">
<Provider>filePath</Provider>
</SkipDeleteItems>
<SkipDeleteItems Include="#(SkipDeleteDirectories)"
Condition="'#(SkipDeleteDirectories)' != ''">
<Provider>dirPath</Provider>
</SkipDeleteItems>
</ItemGroup>
<!-- Uses MSBuild trickery to add an escaped version of the skip path to as
"EscapedPath" metadata -->
<Target Name="AddRegexEscapedPathMetadata" Outputs="%(SkipDeleteItems.EscapedPath)">
<EscapeTextForRegularExpressions Text="%(SkipDeleteItems.Identity)">
<Output TaskParameter="Result"
PropertyName="_Temp_EscapeRegEx_SkipDeleteItemPath" />
</EscapeTextForRegularExpressions>
<ItemGroup>
<SkipDeleteItems Condition="'%(SkipDeleteItems.Identity)' == '%(Identity)'" >
<EscapedPath>$(_Temp_EscapeRegEx_SkipDeleteItemPath)</EscapedPath>
</SkipDeleteItems>
</ItemGroup>
<PropertyGroup>
<!-- Clear value -->
<_Temp_EscapeRegEx_SkipDeleteItemPath></_Temp_EscapeRegEx_SkipDeleteItemPath>
</PropertyGroup>
</Target>
<Target Name="AddSkipDirectives" DependsOnTargets="AddRegexEscapedPathMetadata">
<ItemGroup>
<MsDeploySkipRules Include="%(SkipDeleteItems.Identity)">
<SkipAction>Delete</SkipAction>
<ObjectName>%(SkipDeleteItems.Provider)</ObjectName>
<AbsolutePath>%(SkipDeleteItems.EscapedPath)</AbsolutePath>
</MsDeploySkipRules>
</ItemGroup>
</Target>
NB If you go to the extra effort to separate your packaging process from your deployment process, then technically your SkipDeleteFiles should be in your pubxml rather than your wpp.targets.

How to invoke the same msbuild target twice with different parameters from within msbuild project file itself

I have the following piece of msbuild code:
<PropertyGroup>
<DirA>C:\DirA\</DirA>
<DirB>C:\DirB\</DirB>
</PropertyGroup>
<Target Name="CopyToDirA"
Condition="Exists('$(DirA)') AND '#(FilesToCopy)' != ''"
Inputs="#(FilesToCopy)"
Outputs="#(FilesToCopy -> '$(DirA)%(Filename)%(Extension)')">
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="$(DirA)" />
</Target>
<Target Name="CopyToDirB"
Condition="Exists('$(DirB)') AND '#(FilesToCopy)' != ''"
Inputs="#(FilesToCopy)"
Outputs="#(FilesToCopy -> '$(DirB)%(Filename)%(Extension)')">
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="$(DirB)" />
</Target>
<Target Name="CopyFiles" DependsOnTargets="CopyToDirA;CopyToDirB"/>
So invoking the target CopyFiles copies the relevant files to $(DirA) and $(DirB), provided they are not already there and up-to-date.
But the targets CopyToDirA and CopyToDirB look identical except one copies to $(DirA) and the other - to $(DirB). Is it possible to unify them into one target first invoked with $(DirA) and then with $(DirB)?
Thanks.
You should be able to generate an ItemGroup containing the Dirs and then % on that.
<ItemGroup>
<Dirs Include="C:\DirA\;C:\DirB\">
</ItemGroup>
<Target Name="CopyFiles"
Condition="Exists('%(Dirs)') AND '#(FilesToCopy)' != ''"
Inputs="#(FilesToCopy)"
Outputs="#(FilesToCopy -> '%(Dirs)%(Filename)%(Extension)')">
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="%(Dirs)" />
</Target>
Or you can do 2 explicit calls:
<Target Name="CopyFiles">
<MsBuild Projects="$(MSBuildProjectFullPath)" Targets="CopyASetOfFiles" Properties="FilesToCopy=#(FilesToCopy);DestDir=$(DirA)" />
<MsBuild Projects="$(MSBuildProjectFullPath)" Targets="CopyASetOfFiles" Properties="FilesToCopy=#(FilesToCopy);DestDir=$(DirB)" />
</Target>
<Target Name="CopyASetOfFiles"
Condition="Exists('$(DestDir)') AND '#(FilesToCopy)' != ''"
Inputs="#(FilesToCopy)"
Outputs="#(FilesToCopy -> '$(DestDir)%(Filename)%(Extension)')">
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="$(DestDir)" />
</Target>
I haven't tested either syntax, but am relatively more confident of the second.
(The answer, if there is one, is in my Sayed Hashimi book on my desk - you'll have to wait until the first of:
Get the book
I get bored
Sayed finds this post and comes up with a brilliant tested answer)
As someone already mentiond the answer is batching.
Here are some links:
MSBuild Batching Part 1
MSBuild Batching Part 2
MSBuild Batching Part 3
MSBuild RE: Enforcing the Build Agent in a Team Build
Yes, what you want is called batching in MSBuild. The
;%(Dirs.Identity)
Defined in the Outputs will cause this task to be executed for each item in the Dirs ItemGroup.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="CopyFiles"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="3.5">
<ItemGroup>
<Dirs Include="C:\DirA" />
<Dirs Include="C:\DirB" />
</ItemGroup>
<Target Name="CopyFiles"
Inputs="#(FilesToCopy);#(Dirs)"
Outputs="#(FilesToCopy -> '%(Dirs.Identity)%(Filename)%(Extension)');%(Dirs.Identity)" >
<Message Text="%(Dirs.Identity)" />
</Target>
</Project>
Outputs:
Build started 8/19/2009 10:11:57 PM.
Project "D:\temp\test.proj" on node 0 (default targets).
C:\DirA
CopyFiles:
C:\DirB
Done Building Project "D:\temp\test.proj" (default targets).
Change the Message task to Copy task with the following condition and you are done:
Condition="Exists('%(Dirs.Identity)') AND '#(FilesToCopy)' != ''"

TFS Build SourceTfs.Checkout

I am trying to get my build to checkout some files (using Microsoft.Sdc.Common.tasks) and then to check them in after the build has finished.
But I can't seem to get this working at all, let alone before and after the build.
Whaereabouts should this sort of code live?
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="DesktopBuild;MyProjectDbUpdate" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets" />
<PropertyGroup>
<TasksPath>C:\Program Files\MSBuild\sdc\</TasksPath>
</PropertyGroup>
<Import Project="$(TasksPath)\Microsoft.Sdc.Common.tasks" />
<Target Name="MyProjectDbUpdate">
<Message Text="MyProjectDbUpdate checkin start"/>
<SourceTfs.Checkout Path="$/MyProject/Code/MyProjectDbUpdate" TfsVersion="2008" workingDirectory="C:\buildagent\MyProject\ContinuousIntegration\Sources\Code" />
<SourceTfs.Checkin Path="$/MyProject/Code/MyProjectDbUpdate" workingDirectory="C:\buildagent\MyProject\ContinuousIntegration\Sources\Code" Comments="Build checkout/checkin." TfsVersion="2008" Override="Build overrides checkin policy" />
<Message Text="MyProjectDbUpdate checkin complete"/>
</Target>
<ProjectExtensions>
<ProjectFileVersion>2</ProjectFileVersion>
<Description>Build</Description>
<BuildMachine>MYSERVER</BuildMachine>
</ProjectExtensions>
<PropertyGroup>
<TeamProject>MyProject</TeamProject>
<BuildDirectoryPath>c:\buildagent\MyProject\ContinuousIntegration</BuildDirectoryPath>
<DropLocation>\\UNKNOWN\drops</DropLocation>
<RunTest>false</RunTest>
<RunCodeAnalysis>Never</RunCodeAnalysis>
<WorkItemType>Bug</WorkItemType>
<WorkItemFieldValues>System.Reason=Build Failure;System.Description=Start the build using Team Build</WorkItemFieldValues>
<WorkItemTitle>Build failure in build:</WorkItemTitle>
<DescriptionText>This work item was created by Team Build on a build failure.</DescriptionText>
<BuildlogText>The build log file is at:</BuildlogText>
<ErrorWarningLogText>The errors/warnings log file is at:</ErrorWarningLogText>
<UpdateAssociatedWorkItems>true</UpdateAssociatedWorkItems>
<AdditionalVCOverrides></AdditionalVCOverrides>
<CustomPropertiesForClean></CustomPropertiesForClean>
<CustomPropertiesForBuild></CustomPropertiesForBuild>
</PropertyGroup>
<ItemGroup>
<SolutionToBuild Include="$(BuildProjectFolderPath)/../../Code/MyProject.sln">
<Targets></Targets>
<Properties></Properties>
</SolutionToBuild>
</ItemGroup>
<ItemGroup>
<ConfigurationToBuild Include="Release|Any CPU">
<FlavorToBuild>Release</FlavorToBuild>
<PlatformToBuild>Any CPU</PlatformToBuild>
</ConfigurationToBuild>
</ItemGroup>
<ItemGroup>
</ItemGroup>
<PropertyGroup>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
</Project>
Specifying your target as a default target is not going to call it as Team build explicity sets the target it is going to call.
Try renaming the Target to AfterGet or overriding the the GetDependsOn property to include your target
<GetDependsOn>
$(GetDependsOn)
MyProjectDbUpdate;
</GetDependsOn>

MSBuild Copy task not copying files the first time round

I created a build.proj file which consists of a task to copy files that will be generated after the build is complete. The problem is that these files are not copied the first time round and I have to run msbuild again on the build.proj so that the files can be copied. Please can anyone tell me whats wrong with the following build.proj file:
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<SourcePath Condition="'$(SourcePath)' == ''">$(MSBuildProjectDirectory)</SourcePath>
<BuildDir>$(SourcePath)\build</BuildDir>
</PropertyGroup>
<ItemGroup>
<Projects
Include="$(SourcePath)\src\myApp\application.csproj">
</Projects>
</ItemGroup>
<Target Name="Build">
<Message text = "Building project" />
<MSBuild
Projects="#(Projects)"
Properties="Configuration=$(Configuration)" />
</Target>
<ItemGroup>
<OutputFiles Include ="$(MSBuildProjectDirectory)\**\**\bin\Debug\*.*"/>
</ItemGroup>
<Target Name="CopyToBuildFolder">
<Message text = "Copying build items" />
<Copy SourceFiles="#(OutputFiles)" DestinationFolder="$(BuildDir)"/>
</Target>
<Target Name="All"
DependsOnTargets="Build; CopyToBuildFolder"/>
</Project>
The itemgroups are evaluated when the script is parsed. At that time your files aren't there yet. To be able to find the files you'll have to fill the itemgroup from within a target.
<!-- SQL Scripts which are needed for deployment -->
<Target Name="BeforeCopySqlScripts">
<CreateItem Include="$(SolutionRoot)\04\**\Databases\**\*.sql">
<Output ItemName="CopySqlScript" TaskParameter="Include"/>
</CreateItem>
</Target>
This example creates the ItemGroup named "CopySqlScript" using the expression in the Include attribute.
Edit:
Now I can read your script: add the CreateItem tag within your CopyToBuildFolder target