I have a custom project system, that uses the standard net sdk targets.
During the build, I produce an extra zip file. I'd like this extra file to be included in an output group, so that when I query my projects output groups (from vs) it shows up.
My project file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
... stuff
<ItemGroup>
<PackageReference Include="DnnVsProjectSystem.BuildTools" Version="0.0.5">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
<Import Project="$(CustomProjectExtensionsPath)DnnVsProjectSystem.targets"/>
</Project>
Notice, I am using the "sdk" attribute, which is a fairly new feature of msbuild.
The PackageReference that you see, is a nuget package that imports a .props and a .targets which augment the build with some custom build tasks. These are the ones that produce the zip file.
I have drilled into the net sdk targets and found this:
<Target Name="AllProjectOutputGroups" DependsOnTargets="
BuiltProjectOutputGroup;
DebugSymbolsProjectOutputGroup;
DocumentationProjectOutputGroup;
SatelliteDllsProjectOutputGroup;
SourceFilesProjectOutputGroup;
ContentFilesProjectOutputGroup;
SGenFilesOutputGroup" />
<!--
This is the key output for the BuiltProjectOutputGroup and is meant to be read directly from the IDE.
Reading an item is faster than invoking a target.
-->
<ItemGroup Condition=" '$(OutputType)' != 'winmdobj' ">
<BuiltProjectOutputGroupKeyOutput Include="#(IntermediateAssembly->'%(FullPath)')">
<IsKeyOutput>true</IsKeyOutput>
<FinalOutputPath>$(TargetPath)</FinalOutputPath>
<TargetPath>$(TargetFileName)</TargetPath>
<COM2REG Condition="'$(RegisterForComInterop)'=='true' and '$(OutputType)'=='library'">true</COM2REG>
</BuiltProjectOutputGroupKeyOutput>
</ItemGroup>
<ItemGroup Condition=" '$(OutputType)' == 'winmdobj' ">
<WinMDExpOutputWindowsMetadataFileItem Include="$(_IntermediateWindowsMetadataPath)" Condition="'$(_IntermediateWindowsMetadataPath)' != ''" />
<BuiltProjectOutputGroupKeyOutput Include="#(WinMDExpOutputWindowsMetadataFileItem->'%(FullPath)')">
<IsKeyOutput>true</IsKeyOutput>
<FinalOutputPath>$(TargetPath)</FinalOutputPath>
<TargetPath>$(TargetFileName)</TargetPath>
</BuiltProjectOutputGroupKeyOutput>
</ItemGroup>
This appears to be the target that is called by VS, when it wants information about output groups.
The problem is, i am not sure how I can get my item included in one of those output groups, as If i just add the item to the item group, in my own targets - my targets are irrelevent at this point, as they are not included in this dependency chain.
I also can't override any of the targets, because, as i'm using the sdk attribute, it looks like the sdk targets will always be imported last, overwriting anything that I declare.
Any guidance much appreciated.
If your only concern is to hook into the target or its dependency chain, I suggest using msbuild's BeforeTargets functionality:
<Target Name="IncludeMyCustomOutputGroup" BeforeTargets="AllProjectOutputGroups" DependsOnTargets="ResolveMyCustomPropertiesAndItems">
<ItemGroup>
<!-- Assuming #(MyCustomOutput) items are generated by your ResolveMyCustomPropertiesAndItems target, or just add anything else -->
<BuiltProjectOutputGroupKeyOutput Include="#(MyCustomOutput->'%(FullPath)')">
<IsKeyOutput>true</IsKeyOutput>
<FinalOutputPath>$(TargetPath)</FinalOutputPath>
<TargetPath>$(TargetFileName)</TargetPath>
</BuiltProjectOutputGroupKeyOutput>
</ItemGroup>
</Target>
Related
Executive Summary: I want to set properties in property groups based on conditions that are present only late in the build pipeline and am looking for a way to solve this earlier.
I have a fairly simple Directory.build.props file
<Project>
<PropertyGroup>
<MyMode>Default</MyMode>
</PropertyGroup>
<!-- This one overrides the default group above -->
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<MyMode>Changed to Debug</MyMode>
</PropertyGroup>
<!-- This one is not applied -->
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.7.2' ">
<MyMode>Framework</MyMode>
</PropertyGroup>
<Target Name="Stats" AfterTargets="Build">
<Message Importance="High" Text="::::: Mode set to $(MyMode)" />
<Message Importance="High" Text="::::: Target Framework set to $(TargetFrameworkVersion)" />
</Target>
</Project>
And a simple project structure
E:.
│ Directory.build.props
│ MSBuild_Test.sln
│
├───ConsoleAppNet
│ App.config
│ ConsoleAppNet.csproj
│ Program.cs
│
└───MSBuild_Test
Class1.cs
LibStandard.csproj
LibStandard is a .net standard library, ConsoleAppNet is a .net framework project which also has a build dependency to LibStandard
When I execute the msbuild script above I get this output
LibStandard -> E:\temp\MSBuild_Test\MSBuild_Test\bin\Debug\netstandard2.0\LibStandard.dll
::::: Mode set to Changed to Debug
::::: Target Framework set to v2.0
ConsoleAppNet -> E:\temp\MSBuild_Test\ConsoleAppNet\bin\Debug\ConsoleAppNet.exe
::::: Mode set to Changed to Debug
::::: Target Framework set to v4.7.2
As you can see, the console output should have triggered the property group with the condition resulting in MyMode being Framework, but it did not work out. This one was never matched:
<PropertyGroup Condition=" '$(TargetFrameworkVersion)' == 'v4.7.2' ">
<MyMode>Framework</MyMode>
</PropertyGroup>
Is there a good way to apply PropertyGroups during load based on the condition above?
I am aware that I can place PropertyGroup overrides in a Target, e.g.:
<Target Name="TooLate" BeforeTargets="BeforeBuild" Condition=" '$(TargetFrameworkVersion' == 'v4.7.2' ">
<PropertyGroup >
<MyMode>Framework</MyMode>
</PropertyGroup>
</Target>
and it also gets executed correctly but at this point in time I cannot set important other variables.
My intention is to redirect output directories based on different conditions. When I set $(OutputPath) in a target, it is already too late. The project ignores this output for the entire build of this project:
<Target Name="TooLate" BeforeTargets="BeforeBuild" Condition=" '$(TargetFrameworkVersion)' == 'v4.7.2' ">
<PropertyGroup >
<OutputPath>New_Output_Directory</OutputPath>
</PropertyGroup>
</Target>
I can even echo the OutputPath variable and it points to the correct value but the build uses the old value and not redirecting the output.
High five me, I found the solution for all the coming up Samuels asking about the same issue.
Quick answer
At the time of import of the Directory.build.props no other properties (e.g TargetFramework) are already imported and will default to empty. This is why the checks on them fail. Use Directory.build.targets instead!
Directory.build.props imported very early, allowing you to set properties at the beginning
Directory.build.targets imported very late, allowing you to customize the build chain
Resources
Here are some very useful pages regarding msbuild
Explanation of available targets
How to customize your build
Explanation
Here is a quote from the paragraph on the customization page (so long the current documents are alive ...)
Import order
Directory.Build.props is imported very early in
Microsoft.Common.props, and properties defined later are unavailable
to it. So, avoid referring to properties that are not yet defined (and
will evaluate to empty).
Directory.Build.targets is imported from Microsoft.Common.targets
after importing .targets files from NuGet packages. So, it can
override properties and targets defined in most of the build logic,
but sometimes you may need to customize the project file after the
final import.
By reading this the implication is somewhat fuzzy about the targets but Directory.Build.targets is the best place to override properties and use conditional checks.
I have a test project which reference NUnit3TestAdapter. I do not this reference to be copied over to the projects that depend on this one.
I thought setting PrivateAssets = All would do it, but apparently I misunderstand how it works, because it does not have the desired effect.
Here is the code:
Rollup\Rollup.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\UITests\UITests.csproj"/>
</ItemGroup>
</Project>
UITests\UITests.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NUnit3TestAdapter" Version="3.11.2">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
Directory.Build.rsp
.\Rollup.sln /restore /v:m
After I run msbuild all is built, but I can see NUnit3TestAdapter is in the bin folder for Rollup.
What am I missing?
(https://github.com/Microsoft/msbuild/issues/3996)
PrivateAssets works as expected but the NUnit test adapter NuGet package adds an MSBuild target to the build that adds a few dll files as content items to the project, which then flow transitively through the build - this has the same effect as if you added a text file and set its "Copy to Output Directory" property.
The NUnit3TestAdapter.props contains definitions like:
<Content Include="$(MSBuildThisFileDirectory)NUnit3.TestAdapter.dll">
<Link>NUnit3.TestAdapter.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Visible>False</Visible>
</Content>
You should see these files if you click the "Show All Files" in the Visual Studio solution explorer.
Note that test projects aren't really supposed to be packaged or referenced. They should be leaf projects. The test project templates even contain an <IsPackable>false</…> definition and XUnit's core package also adds it as an imported MSBuild file. The test frameworks expect you to use their abstraction libraries and not runtime assemblies / test adapter packages for projects that share tests or test logic.
I'd like to create a MSBuild project that reflects the project dependencies in a solution and wraps the VS projects inside reusable targets.
The problem I like solve doing this is to svn-export, build and deploy a specific assembly (and its dependencies) in an BizTalk application.
My question is: How can I make the targets for svn-exporting, building and deploying reusable and also reuse the wrapped projects when they are built for different dependencies?
I know it would be simpler to just build the solution and deploy only the assemblies needed but I'd like to reuse the targets as much as possible.
The parts
The project I like to deploy
<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ExportRoot Condition="'$(Export)'==''">Export</ExportRoot>
</PropertyGroup>
<Target Name="Clean_Export">
<RemoveDir Directories="$(ExportRoot)\My.Project.Dir" />
</Target>
<Target Name="Export_MyProject">
<Exec Command="svn export svn://xxx/trunk/Biztalk2009/MyProject.btproj --force" WorkingDirectory="$(ExportRoot)" />
</Target>
<Target Name="Build_MyProject" DependsOnTargets="Export_MyProject">
<MSBuild Projects="$(ExportRoot)\My.Project.Dir\MyProject.btproj" Targets="Build" Properties="Configuration=Release"></MSBuild>
</Target>
<Target Name="Deploy_MyProject" DependsOnTargets="Build_MyProject">
<Exec Command="BTSTask AddResource -ApplicationName:CORE -Source:MyProject.dll" />
</Target>
</Project>
The projects it depends upon look almost exactly like this (other .btproj and .csproj).
Wow, this is a loaded question for a forum post. I wrote about 20 pages on creating reusable .targets files in my book, but I'll get you started here with the basics here. I believe that the key to creating reusable build scripts (i.e. .targets files) is three elements:
Place behavior (i.e. targets) into separate files
Place data (i.e. properties and items, these are called .proj files) into their own files
Extensibility
.targets files should validate assumptions
The idea is that you want to place all of your targets into separate files and then these files will be imported by the files which will be driving the build process. These are the files which contain the data. Since you import the .targets files you get all the targets as if they had been defined inline. There will be a silent contract between the .proj and .targets files. This contract is defined in properties and items which both use. This is what needs to be validated.
The idea here is not new. This pattern is followed by .csproj (and other projects generated by Visual Studio). If you take a look your .csproj file you will not find a single target, just properties and items. Then towards the bottom of the file it imports Microsoft.csharp.targets (may differ depending on project type). This project file (along with others that it imports) contains all the targets which actually perform the build.
So it's layed out like this:
SharedBuild.targets
MyProduct.proj
Where MyProdcut.proj might look like:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This uses a .targets file to off load performing the build -->
<PropertyGroup>
<Configuration Condition=" '$(Configuration)'=='' ">Release</Configuration>
<OutputPath Condition=" '$(OutputPath)'=='' ">$(MSBuildProjectDirectory)\BuildArtifacts\bin\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Projects Include="$(MSBuildProjectDirectory)\..\ClassLibrary1\ClassLibrary1.csproj"/>
<Projects Include="$(MSBuildProjectDirectory)\..\ClassLibrary2\ClassLibrary2.csproj"/>
<Projects Include="$(MSBuildProjectDirectory)\..\ClassLibrary3\ClassLibrary3.csproj"/>
<Projects Include="$(MSBuildProjectDirectory)\..\WindowsFormsApplication1\WindowsFormsApplication1.csproj"/>
</ItemGroup>
<Import Project="SharedBuild.targets"/>
</Project>
And SharedBuild.targets might look like:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This represents a re-usable build file -->
<Target Name="SharedBuild_Validate">
<!-- See http://sedodream.com/2009/06/30/ElementsOfReusableMSBuildScriptsValidation.aspx for more info
about this validation pattern
-->
<ItemGroup>
<_RequiredProperties Include ="Configuration">
<Value>$(Configuration)</Value>
</_RequiredProperties>
<_RequiredProperties Include ="OutputPath">
<Value>$(OutputPath)</Value>
</_RequiredProperties>
<_RequiredItems Include="Projects">
<RequiredValue>%(Projects.Identity)</RequiredValue>
<RequiredFilePath>%(Projects.Identity)</RequiredFilePath>
</_RequiredItems>
</ItemGroup>
<!-- Raise an error if any value in _RequiredProperties is missing -->
<Error Condition="'%(_RequiredProperties.Value)'==''"
Text="Missing required property [%(_RequiredProperties.Identity)]"/>
<!-- Raise an error if any value in _RequiredItems is empty -->
<Error Condition="'%(_RequiredItems.RequiredValue)'==''"
Text="Missing required item value [%(_RequiredItems.Identity)]" />
<!-- Validate any file/directory that should exist -->
<Error Condition="'%(_RequiredItems.RequiredFilePath)' != '' and !Exists('%(_RequiredItems.RequiredFilePath)')"
Text="Unable to find expeceted path [%(_RequiredItems.RequiredFilePath)] on item [%(_RequiredItems.Identity)]" />
</Target>
<PropertyGroup>
<BuildDependsOn>
SharedBuild_Validate;
BeforeBuild;
CoreBuild;
AfterBuild;
</BuildDependsOn>
</PropertyGroup>
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>
<Target Name="BeforeBuild"/>
<Target Name="AfterBuild"/>
<Target Name="CoreBuild">
<!-- Make sure output folder exists -->
<PropertyGroup>
<_FullOutputPath>$(OutputPath)$(Configuration)\</_FullOutputPath>
</PropertyGroup>
<MakeDir Directories="$(_FullOutputPath)"/>
<MSBuild Projects="#(Projects)"
BuildInParallel="true"
Properties="OutputPath=$(_FullOutputPath)"/>
</Target>
</Project>
Don't look too much at the SharedBuild_Validate target yet. I put that there for completeness but don't focus on it. You can find more info on that at my blog at http://sedodream.com/2009/06/30/ElementsOfReusableMSBuildScriptsValidation.aspx.
The important parts to notice are the extensibility points. Even though this is a very basic file, it has all the components of a reusable .targets file. You can customize it's behavior by passing in different properties and items to build. You can extend it's behavior by overriding a target (BeforeBuild, AfterBuild or even CoreBuild) and you can inject your own targets into the build with:
<Project ...>
...
<Import Project="SharedBuild.targets"/>
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
CustomAfterBuild
</BuildDependsOn>
</PropertyGroup>
<Target Name="CustomAfterBuild">
<!-- Insert stuff here -->
</Target>
</Project>
In your case I would create an SvnExport.targets file which uses the required properties:
SvnExportRoot
SvnUrl
SvnWorkingDirectory
You will use these properties to do the Export.
Then create another one for Biztalk build and deploy. You could split this up into 2 if necessary.
Then inside of your .proj file you just import both and setup the targets to build in the right order, and your off.
This is only really the beginning of creating reusable build elements, but this should get the wheels turning in your head. I am going to post all of this to my blog as well as download links for all files.
UPDATE:
Posted to blog at http://sedodream.com/2010/03/19/ReplacingSolutionFilesWithMSBuildFiles.aspx
As part of a solution containing many projects, I have a project that references (via a <ProjectReference> three other projects in the solution, plus some others). In the AfterBuild, I need to copy the outputs of 3 specific dependent projects to another location.
Via various SO answers, etc. the way I settled on to accomplish that was:
<MSBuild
Projects="#(ProjectReference)"
Targets="Build"
BuildInParallel="true"
Condition="'%(Name)'=='ProjectA' OR '%(Name)'=='ProjectB' OR '%(Name)'=='ProjectC'">
<Output TaskParameter="TargetOutputs" ItemName="DependentAssemblies" />
</MSBuild>
<Copy SourceFiles="#(DependentAssemblies)" DestinationFolder="XX" SkipUnchangedFiles="true" />
However, I ran into problems with this. The <MSBuild step's IncrementalClean task ends up deleting a number of the outputs of ProjectC. When running this under VS2008, a build.force file being deposited in the obj/Debug folder of ProjectC which then triggers ProjectC getting rebuilt if I do a Build on the entire solution if the project containing this AfterBuild target, whereas if one excludes this project from the build, it [correctly] doesn't trigger a rebuild of ProjectC (and critically a rebuild of all dependents of ProjectC). This may be VS-specific trickery in this case which would not occur in the context of a TeamBuild or other commandline MSBuild invocation (but the most common usage will be via VS so I need to resolve this either way)
The dependent projects (and the rest of the solution in general) have all been created interactively with VS, and hence the ProjectRefences contain relative paths etc. I've seen mention of this being likely to causing issues - but without a full explanation of why, or when it'll be fixed or how to work around it. In other words, I'm not really interested in e.g. converting the ProjectReference paths to absolute paths by hand-editing the .csproj.
While it's entirely possible I'm doing something stupid and someone will immediately point out what it is (which would be great), be assured I've spent lots of time poring over /v:diag outputs etc. (although I havent tried to build a repro from the ground up - this is in the context of a relatively complex overall build)
As noted in my comment, calling GetTargetPath on the referenced project only returns the Primary output assembly of that project. To get all the referenced copy-local assemblies of the referenced project it's a bit messier.
Add the following to each project that you are referencing that you want to get the CopyLocals of:
<Target
Name="ComputeCopyLocalAssemblies"
DependsOnTargets="ResolveProjectReferences;ResolveAssemblyReferences"
Returns="#(ReferenceCopyLocalPaths)" />
My particular situation is that I needed to recreate the Pipeline folder structure for System.AddIn in the bin folder of my top level Host project. This is kinda messy and I was not happy with the MSDN suggested solutions of mucking with the OutputPath - as that breaks on our build server and prevents creating the folder structure in a different project (eg a SystemTest)
So along with adding the above target (using a .targets import), I added the following to a .targets file imported by each "host" that needs the pipeline folder created:
<Target
Name="ComputePipelineAssemblies"
BeforeTargets="_CopyFilesMarkedCopyLocal"
Outputs="%(ProjectReference.Identity)">
<ItemGroup>
<_PrimaryAssembly Remove="#(_PrimaryAssembly)" />
<_DependentAssemblies Remove="#(_DependentAssemblies)" />
</ItemGroup>
<!--The Primary Output of the Pipeline project-->
<MSBuild Projects="%(ProjectReference.Identity)"
Targets="GetTargetPath"
Properties="Configuration=$(Configuration)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<Output TaskParameter="TargetOutputs"
ItemName="_PrimaryAssembly" />
</MSBuild>
<!--Output of any Referenced Projects-->
<MSBuild Projects="%(ProjectReference.Identity)"
Targets="ComputeCopyLocalAssemblies"
Properties="Configuration=$(Configuration)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<Output TaskParameter="TargetOutputs"
ItemName="_DependentAssemblies" />
</MSBuild>
<ItemGroup>
<ReferenceCopyLocalPaths Include="#(_PrimaryAssembly)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<DestinationSubDirectory>%(ProjectReference.PipelineFolder)</DestinationSubDirectory>
</ReferenceCopyLocalPaths>
<ReferenceCopyLocalPaths Include="#(_DependentAssemblies)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<DestinationSubDirectory>%(ProjectReference.PipelineFolder)</DestinationSubDirectory>
</ReferenceCopyLocalPaths>
</ItemGroup>
</Target>
I also needed to add the required PipelineFolder meta data to the actual project references. For example:
<ProjectReference Include="..\Dogs.Pipeline.AddInSideAdapter\Dogs.Pipeline.AddInSideAdapter.csproj">
<Project>{FFCD0BFC-5A7B-4E13-9E1B-8D01E86975EA}</Project>
<Name>Dogs.Pipeline.AddInSideAdapter</Name>
<Private>False</Private>
<PipelineFolder>Pipeline\AddInSideAdapter\</PipelineFolder>
</ProjectReference>
Your original solution should work simply by changing
Targets="Build"
to
Targets="GetTargetPath"
The GetTargetPath target simply returns the TargetPath property and doesn't require building.
You may protect your files in ProjectC if you call a target like this first:
<Target Name="ProtectFiles">
<ReadLinesFromFile File="obj\ProjectC.csproj.FileListAbsolute.txt">
<Output TaskParameter="Lines" ItemName="_FileList"/>
</ReadLinesFromFile>
<CreateItem Include="#(_DllFileList)" Exclude="File1.sample; File2.sample">
<Output TaskParameter="Include" ItemName="_FileListWitoutProtectedFiles"/>
</CreateItem>
<WriteLinesToFile
File="obj\ProjectC.csproj.FileListAbsolute.txt"
Lines="#(_FileListWitoutProtectedFiles)"
Overwrite="true"/>
</Target>
My current workaround is based on this SO question, i.e, I have:
<ItemGroup>
<DependentAssemblies Include="
..\ProjectA\bin\$(Configuration)\ProjectA.dll;
..\ProjectB\bin\$(Configuration)\ProjectB.dll;
..\ProjectC\bin\$(Configuration)\ProjectC.dll">
</DependentAssemblies>
</ItemGroup>
This however will break under TeamBuild (where all the outputs end up in one directory), and also if the names of any of the outputs of the dependent projects change.
EDIT: Also looking for any comments on whether there's a cleaner answer for how to make the hardcoding slightly cleaner than:
<PropertyGroup>
<_TeamBuildingToSingleOutDir Condition="'$(TeamBuildOutDir)'!='' AND '$(CustomizableOutDir)'!='true'">true</_TeamBuildingToSingleOutDir>
</PropertyGroup>
and:
<ItemGroup>
<DependentAssemblies
Condition="'$(_TeamBuildingToSingleOutDir)'!='true'"
Include="
..\ProjectA\bin\$(Configuration)\ProjectA.dll;
..\ProjectB\bin\$(Configuration)\ProjectB.dll;
..\ProjectC\bin\$(Configuration)\ProjectC.dll">
</DependentAssemblies>
<DependentAssemblies
Condition="'$(_TeamBuildingToSingleOutDir)'=='true'"
Include="
$(OutDir)\ProjectA.dll;
$(OutDir)\ProjectB.dll;
$(OutDir)\ProjectC.dll">
</DependentAssemblies>
</ItemGroup>
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_ ...).