MSBuild afterbuild step to copy code into a temp folder - msbuild

I'm trying to place my compiled web application into a temporary directory after it has been built.
I have the following but it doesn't seem to work. It drops and create directors but the msbuild task doesn't seem to copy the compile output into the obj/publish directory that i need it to?
<Target Name="AfterBuild">
<CallTarget Targets="Publish" />
</Target>
<Target Name="Publish">
<RemoveDir Directories="$(SolutionDir)AsycLearn\obj\publish\" ContinueOnError="true" />
<MakeDir Directories="$(SolutionDir)AsycLearn\obj\publish\"/>
<MSBuild Projects="AsycLearn.csproj" Targets="ResolveReferences;_CopyWebApplication" Properties="WebProjectOutputDir=$(SolutionDir) AsycLearn\obj\publish\;OutDir=$(SolutionDir) AsycLearn\obj\publish\bin\" />
</Target>
any ideas? Thanks

It looks like you need to quote your output paths because they contain spaces. Otherwise the value that gets read will be truncated at the first space. That will also terminate the parameter set that gets passed to any other tasks.
Properties="WebProjectOutputDir=$(SolutionDir) AsycLearn\obj\publish\;OutDir=$(SolutionDir) AsycLearn\obj\publish\bin\"
should be
Properties="WebProjectOutputDir="$(SolutionDir) AsycLearn\obj\publish\";OutDir="$(SolutionDir) AsycLearn\obj\publish\bin\""
The escape sequence " is needed because we want the resolved value to include quotes.

Related

Deploying from MSBuild without overwriting specific files

So here's what I want to do:
I want a build script that will xcopy deploy build outputs for a legacy winform app to a given directory. I want to specify a list of files to not overwrite (some config files).
I would rather have the list of files to not overwrite be passed as a parameter than hard code them.
This seems to be really unexpectedly hard. Here's what I have so far:
<!-- A property that is passed a semicolon delimited list of file names -->
<PropertyGroup>
<ProtectedFiles/>
</PropertyGroup>
<--! An ItemGroup to pick up the files>
<ItemGroup>
<FilesToDelete Include=$(DeploymentTargetFolder)\*.* Exclude="#(ProtectedFiles->'$(DeployTargetFolder)\%(identity)')"
<ItemGroup/>
<--! the delete isn't working, so I will stop just with that to keep the code brief -->
<Delete Files="#(FilesToDelete)"/>
The delete just ignores the exclude files and deletes everything
Is there a better way to do this? It doesn't seem too crazy -- I just want to
Delete all files from the target directory, except for the config files
Copy all of the files from the build outputs to the target directory, without overwriting the config files.
The first problem with your particular markup appears to confuse MsBuild $(properties) with MsBuild %(items) and MsBuild #(itemgroups).
ProtectedFiles is a property:
<!-- A property that is passed a semicolon delimited list of file names -->
<PropertyGroup>
<ProtectedFiles/>
</PropertyGroup>
But it's being treated as an Item and wouldn't have any %item.metadata:
<--! An ItemGroup to pick up the files>
<ItemGroup>
<FilesToDelete Include=$(DeploymentTargetFolder)\*.* Exclude="#(ProtectedFiles->'$(DeployTargetFolder)\%(identity)')"
<ItemGroup/>
Save the following markup locally as "foo.xml", then call "msbuild.exe foo.xml" and observe the output:
<Project ToolsVersion="4.0" DefaultTargets="foo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<FilesProp>FileA.txt;FileB.txt</FilesProp>
</PropertyGroup>
<ItemGroup>
<ProtectedFiles Include="FileA.txt" />
<ProtectedFiles Include="FileA.txt" />
</ItemGroup>
<Target Name="foo">
<Message Importance="high" Text="ProtectedFiles ItemGroup: #(ProtectedFiles)" />
<Message Importance="high" Text="ProtectedFiles ItemGroup transform: #(ProtectedFiles->'%(Identity)')" />
<Message Importance="high" Text="FilesProp Property: $(FilesProp)" />
<Message Importance="high" Text="FilesProp Property: #(FilesProp->'%(FilesProp.Identity)')" />
</Target>
</Project>
Will yield the following output:
foo:
ProtectedFiles ItemGroup: FileA.txt;FileA.txt
ProtectedFiles ItemGroup transform: FileA.txt;FileA.txt
FilesProp Property: FileA.txt;FileB.txt
FilesProp Property:
If you're unable to change the design and need to convert a Property comprising a semi-colon delimited list of file paths, use the MsBuild <CreateItem /> task.
Add this markup to foo.xml occurring after the Foo target, then invoke msbuild again, but using the "bar" target (e.g. msbuild.exe foo.xml /t:bar)
<Target Name="bar">
<CreateItem Include="$(FilesProp)">
<Output TaskParameter="Include" ItemName="TheFiles"/>
</CreateItem>
<Message Text="TheFiles ItemGroup: #(TheFiles)" Importance="high" />
<Message Text="Output each item: %(TheFiles.Identity)" Importance="high" />
</Target>
Will yield the following output:
bar:
TheFiles ItemGroup: FileA.txt;FileB.txt
Output each item: FileA.txt
Output each item: FileB.txt
Next you should rethink some of your assumptions. I don't believe the file extension should be the determining factor when deciding which files to update, rather you should rely on MsBuild's ability to build tasks incrementally allowing it to perform a task only if the inputs are newer than the outputs. You can do this by using an MsBuild <Copy /> task configured to skip unchanged files.
Add this markup to the above Xml file, then modify the $(SourceFolder) and $(TargetFolder) to point to a source folder you'd like to copy recursively, and a destination folder to place the files. Build using "msbuild.exe foo.xml /t:Deployment" and observe the output.
<Target Name="Deployment">
<PropertyGroup>
<SourceFolder>c:\sourcefolder\</SourceFolder>
<TargetFolder>c:\destinationfolder\</TargetFolder>
</PropertyGroup>
<CreateItem Include="$(SourceFolder)\**\*.*">
<Output TaskParameter="Include" ItemName="FilesToCopy" />
</CreateItem>
<Copy SourceFiles="#(FilesToCopy)" DestinationFolder="$(TargetFolder)%(RecursiveDir)" SkipUnchangedFiles="true" />
</Target>
Without modifying any of the source files, run the command again and note that no files were copied.
Modify a file in the source folder, then run the command again. Notice that only the updated files were copied?
I hope this gets you on the right track.
There seems to be an already existing post, similar to this. Please check this Trying to exclude certain extensions doing a recursive copy (MSBuild)

Incremental Build of Nuget Packages

I want to execute an msbuild project which uses batching to determine that one or more csproj projects have been freshly-built, and therefore require fresh nuget packaging. The script I've made so far seems like a reasonable start, but it the incremental-build mechanism isn't working. The MainBuild target executes every time, no matter what.
Here is what I have:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="MainBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)'=='' ">Debug</Configuration>
<Content>content\plugins\</Content>
</PropertyGroup>
<ItemGroup>
<Nuspec Include="$(MSBuildProjectDirectory)\plugins\*\*.nuspec" />
</ItemGroup>
<Target Name="MainBuild"
Inputs="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll"
Outputs="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" >
<ItemGroup>
<Inputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll" />
<Outputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" />
</ItemGroup>
<Message Text="INPUTS: %(Inputs.FullPath)" />
<Message Text="OUTPUTS: #(Outputs->'%(FullPath)')" />
<Copy SourceFiles="#(Inputs)" DestinationFiles="#(Outputs->'%(FullPath)')" />
</Target>
</Project>
The Copy task is just a debugging placeholder for calling-out to nuget and creating a new package.
The idea is that if any files in the bin\Debug directory are newer than the corresponding .nuspec file (found two folders above bin\Debug), then the MainBuild target should execute.
Any ideas?
p.s. The Inputs and Outputs attributes of the Target presumably each create an item. I think it strange that the items created can't be referenced inside the target. In the above example, I had to make a target-interna dynamic ItemGroup to re-create the items, just so that I could access them. Is there a way around that?
I read this in the MSBuild Batching documentation
If a task inside of a target uses batching, MSBuild needs to determine
if the inputs and outputs for each batch of items is up-to-date.
Otherwise, the target is executed every time it is hit.
Which may be the cuprit. Try changing your copy target to use batching instead of an ite transform (I don't think using item metadata in an item group satisfies the above requirement).
<Target Name="MainBuild"
Inputs="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll"
Outputs="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" >
<ItemGroup>
<Inputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)bin\$(Configuration)\*.dll" />
<Outputs Include="%(Nuspec.RootDir)%(Nuspec.Directory)%(FileName).pkg" />
</ItemGroup>
<Message Text="INPUTS: %(Inputs.FullPath)" />
<Message Text="OUTPUTS: #(Outputs->'%(FullPath)')" />
<Copy SourceFiles="#(Inputs)" DestinationFiles="%(Outputs.FullPath)" />
</Target>
It looks like the number of inputs may be different than the number of outputs (I suspect there is more than one .dll files in the output directory for each project), which will also cause the target to execute.

MSBuild passing parameters to CallTarget

I'm trying to make a reusable target in my MSBuild file so I can call it multiple times with different parameters.
I've got a skeleton like this:
<Target Name="Deploy">
<!-- Deploy to a different location depending on parameters -->
</Target>
<Target Name="DoDeployments">
<CallTarget Targets="Deploy">
<!-- Somehow indicate I want to deploy to dev -->
</CallTarget>
<CallTarget Targets="Deploy">
<!-- Somehow indicate I want to deploy to testing -->
</CallTarget>
</Target>
But I can't work out how to allow parameters to be passed into the CallTarget, and then in turn the Target itself.
MSBuild targets aren't designed to receive parameters. Instead, they use the properties you define for them.
<PropertyGroup>
<Environment>myValue</Environment>
</PropertyGroup>
<Target Name="Deploy">
<!-- Use the Environment property -->
</Target>
However, a common scenario is to invoke a Target several times with different parameters (i.e. Deploy several websites). In that case, I use the MSBuild MSBuild task and send the parameters as Properties:
<Target Name="DoDeployments">
<MSBuild Projects ="$(MSBuildProjectFullPath)"
Properties="VDir=MyWebsite;Path=C:\MyWebsite;Environment=$(Environment)"
Targets="Deploy" />
<MSBuild Projects ="$(MSBuildProjectFullPath)"
Properties="VDir=MyWebsite2;Path=C:\MyWebsite2;Environment=$(Environment)"
Targets="Deploy" />
</Target>
$(MSBuildProjectFullPath) is the fullpath of the current MSBuild script in case you don't want to send "Deploy" to another file.
You can 'foreach' over an ItemGroup with a target, only you have to do it in declaritive manner. You can even have additional metadata in items, like in the code example:
<ItemGroup>
<What Include="Dev">
<How>With bugs</How>
</What>
<What Include="Test">
<How>With tests</How>
</What>
<What Include="Chicken">
<How>Deep fried</How>
</What>
</ItemGroup>
<Target Name="Deploy">
<Message Text="#(What), %(How)" />
</Target>
Using an item group as a scalar value #(What) inside a target does the trick, and %(How) references a metadata element in a foreach item.
It's a natural way of doing things in msbuild, for example you can find this pattern everywhere in project files generated with Visual Studio.
There might be a better way to do this in MSBuild, but in Ant, I would use global properties to carry information from one task to the next. It was a lousy solution, but I didn't see a better way at the time. You should be able to do this in MSBuild, but bear in mind that you will need to use the CreateProperty task to dynamically assign a property.
On the other hand, it's pretty easy to implement tasks in C# (or VB or whatever). Maybe that's a better solution for you.
<CreateProperty
Value="file1">
<Output
TaskParameter="Value"
PropertyName="filename" />
</CreateProperty>
<CallTarget Targets="Deploy"/>
<Message Text="$(filename)"/>
<CreateProperty
Value="file2">
<Output
TaskParameter="Value"
PropertyName="filename" />
</CreateProperty>
<Message Text="$(filename)"/>
<CallTarget Targets="Deploy"/>

MSBuild ContinueOnError

I have an MSBuild project as follows:
<Target Name="StopApplications">
<BizTalk.BuildGenerator.Tasks.StopApplication MessageBoxConnection="$(BizTalkManagementDatabaseConnectionString)" ApplicationName="x.Direct.Brackmills"/>
<BizTalk.BuildGenerator.Tasks.StopApplication MessageBoxConnection="$(BizTalkManagementDatabaseConnectionString)" ApplicationName="x.Direct.Manhattan"/>
</Target>
<Target Name="RemoveApplications">
<Exec Command="BTSTask RemoveApp -ApplicationName:x.Direct.Brackmills -Server:$(BizTalkDatabaseServerName) -Database:$(BizTalkManagementDatabaseName)" />
<Exec Command="BTSTask RemoveApp -ApplicationName:x.Direct.Manhattan -Server:$(BizTalkDatabaseServerName) -Database:$(BizTalkManagementDatabaseName)" />
</Target>
My problem is that when calling the "RemoveApplications" target, the ContinueOnError does not work as I'd expect. I have a long list of applications to stop and remove. They won't all allways be present so I need the script to continue when it finds they're not there. This seems to work find for the "StopApplications" target but when it hits a missing application in the "RemoveApplications" target I get the message:
"Done building target "RemoveApplications" in project "cleardownApplications.proj" -- FAILED. Build continuing because "ContinueOnError" on the task "CallTarget" is set to "true".
But then, it drops out of "RemoveApplications" and moved onto "AddApplications"
Any help gratefully received,
Thanks
Rob.
I've solved this a bit differently and uses a separate target to check if the application exists before removing it.
<Target Name="ApplicationExists">
<BizTalk2006.Application.Exists Application="$(ApplicationName)">
<Output TaskParameter="DoesExist" PropertyName="ApplicationExists" />
</BizTalk2006.Application.Exists>
</Target>
The I use that exists-target as an "condition" in other targets.
<Target Name="DeleteApplication" Condition="$(ApplicationExists)=='True'" DependsOnTargets="ApplicationExists">
<BizTalk2006.Application.Stop Application="$(ApplicationName)"/>
<BizTalk2006.Application.Delete Application="$(ApplicationName)"/>
</Target>

Output all files from a solution in an MSBuild task

In MSBuild, I would like to call a task that extracts all the files in all the project in a specific solution and hold these files in a property that can be passed around to other tasks (for processing etc.)
I was thinking something along the lines of:
<ParseSolutionFile SolutionFile="$(TheSolutionFile)">
<Output TaskParameter="FilesFound" ItemName="AllFilesInSolution"/>
</ParseSolutionFile>
<Message Text="Found $(AllFilesInSolution)" />
which would output the list of all files in the projects in the solution and I could use the AllFilesInSolution property as input to other analysis tasks. Is this an already existing task or do I need to build it myself? If I need to build it myself, should the task output an array of strings or of ITaskItems or something else?
I don't know about tasks, but there are already properties that hold all items. Just look in your typical project file and you'll see which collection they're being added to.
Note the properties Content, Compile, Folder... any time you add a file to a project, it gets put in one of the main collections like this:
<ItemGroup>
<Content Include="Default.aspx" />
<Content Include="Web.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Default.aspx.cs">
<SubType>ASPXCodeBehind</SubType>
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
<Compile Include="Default.aspx.designer.cs">
<DependentUpon>Default.aspx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\" />
</ItemGroup>
Then you can do stuff like this to put the values from existing properties into your properties (the Condition attribute acts as a filter):
<CreateItem Include="#(Content)" Condition="'%(Extension)' == '.aspx'">
<Output TaskParameter="Include" ItemName="ViewsContent" />
</CreateItem>
Or you can do it manually (the Include attribute uses the existing property OutputPath, but it indicates a path that inclues all files):
<CreateItem Include="$(OutputPath)\**\*">
<Output TaskParameter="Include" ItemName="OutputFiles" />
</CreateItem>
There are more details in the MSDN MSBuild documentation that I read when I was mucking with custom build tasks and stuff that was very helpful. Go read up on the CreateItem task and you'll be able to make more sense out of what I posted here. It's really easy to pick up on.
I use the following for solutions with SSRS projects (which dont build under TFS w/o vs installed on the build box). Basically we require that the RDLs be bundled into a build output so we can mark a build for release.
<Target Name="CopyArtifactstoDropLocation">
<CreateItem Include="$(SolutionRoot)\**\*.*">
<Output TaskParameter="Include" ItemName="YourFilesToCopy" />
</CreateItem>
<Copy
SourceFiles="#(YourFilesToCopy)"
DestinationFiles="#(YourFilesToCopy->'$(DropLocation)\$(BuildNumber)\Release\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
Just replace the usage of the Copy Task with whatever you need to do with your bundle. Granted this is going to get everything in your solution root, but if your using TFS then you should only have buildable artifacts.