How to precompile ASP.NET web application from TeamCity? - asp.net

I have a question about precompiling ASP.NET web application projects from TeamCity. This is sort of a follow-up question to the following thread:
How to deploy after a build with TeamCity?
I'm done implementing CI from unit testing to autodeploy using the above thread and now I'd like to complement the process with precompiling the project. The project is rather large and I want to avoid any unnecessary delays in the response time after a new deploy.
So, is there a way to do this from TeamCity? Like calling MSBuild with some specific arguments?

Sure, it can be done with a custom MSBuild script. Here's the one we run to precompile our ASP.NET MVC 3 website (not that it really varies by ASP.NET version).
First it runs the regular build by running MSBuild against the solution file, then runs this custom MSBuild code:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
<PropertyGroup>
<WebProject>Web\ChatPast.Web\ChatPast.Web.csproj</WebProject>
<WebProjectFolder>Web\ChatPast.Web</WebProjectFolder>
<WebPublishFolder>ChatPastWebPublish</WebPublishFolder>
</PropertyGroup>
<ItemGroup>
<ZipFiles Include="$(teamcity_build_workingDir)\src\ChatPast\$(WebPublishFolder)\**\*.*" />
</ItemGroup>
<Target Name="Build">
<!-- Compilation of all projects -->
<MSBuild Projects="ChatPast.sln" Properties="Configuration=Release"/>
<!-- Creating web publish folder. -->
<RemoveDir Directories="$(WebPublishFolder)"/>
<MakeDir Directories="$(WebPublishFolder)"/>
<!-- Running ASP.NET publish -->
<MSBuild Projects="$(WebProject)"
Targets="ResolveReferences;_CopyWebApplication"
Properties="Configuration=Release;WebProjectOutputDir=..\..\$(WebPublishFolder);OutDir=..\..\$(WebPublishFolder)\bin\" />
</Target>
</Project>

Related

AspNetCompiler task in SharePoint project

I would like to know how to compile aspx files after build in a sharepoint solution to see the run-time errors, e.g. missing resource (when translating thep page using resources).
When I add the AspNetCompiler task into my csproj like this (only showing the end of the project file):
...
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\SharePointTools\Microsoft.VisualStudio.SharePoint.targets" Condition="'$(VSToolsPath)' != ''" />
<Target Name="AfterBuild">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)" />
<!-- ToolPath="C:\Windows\Microsoft.NET\Framework64\v2.0.50727" -->
</Target>
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>
, then when building the project I get the following error in the aspx file:
Could not load file or assembly '$SharePoint.Project.AssemblyFullName$' or one of its dependencies. The system cannot find the file specified.
Obviously the asp compiler tries to build the aspx files in the project directory which do not have the tokens replaced. However, I was unable to finalise the task so it would operate on fiels with the tokens replaced. My knowledge of MSBuild is limited which might be the problem.
The only way to do the token replacement is to create the wsp package because the replacement occurs there. The solution is then to create the wsp, unpack it to a target folder, create a new directory there called "bin", move the dlls to the bin directory and then run aspnet compiler on the target folder. The whole process can be done in MSBUild (csproj) file. This is my solution - at the end of the csproj file, after the sharepoint targets are imported:
<Import Project="$(VSToolsPath)\SharePointTools\Microsoft.VisualStudio.SharePoint.targets" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup>
<!-- schedule creating the package in the build, the task is defined in sharepoint targets -->
<BuildDependsOn>$(BuildDependsOn);CreatePackage</BuildDependsOn>
<PostBuildEvent>
</PostBuildEvent>
<!-- define file/folder properties -->
<ExpandDest>$(TargetDir)_wsp</ExpandDest>
<WspBinPath>$(ExpandDest)\bin</WspBinPath>
<WspPath>$(TargetDir)MyProject.wsp</WspPath>
</PropertyGroup>
<!-- override "after build" target, it must depend on the package creation,
which ensures that the target runs after we have the wsp package ready -->
<Target Name="AfterBuild" DependsOnTargets="CreatePackage">
<!-- create the folder where we unpack our wsp -->
<MakeDir Directories="$(ExpandDest)" />
<!-- use expand to unpack the wsp -->
<Message Text="$(ExpandWsp)" Importance="high" />
<Exec Command="expand "$(WspPath)" -F:* "$(ExpandDest)"" />
<!-- create the "bin" folder -->
<MakeDir Directories="$(WspBinPath)" />
<!-- move all dlls and xmls from the root to the bin folder -->
<Exec Command="move /Y "$(ExpandDest)\*.dll" "$(WspBinPath)"" />
<Exec Command="move /Y "$(ExpandDest)\*.xml" "$(WspBinPath)"" />
<!-- run the aspnet compiler on the wsp folder,
the tool path param ensures that .net 2 compiler will be used,
we need that because we compile sharepoint 2010 which is .net 3.5
and the latest aspnet compiler for .net 3.5 is in .net 2 -->
<AspNetCompiler
VirtualPath="/"
PhysicalPath="$(ExpandDest)"
Clean="true"
ContinueOnError="false"
ToolPath="C:\Windows\Microsoft.NET\Framework\v2.0.50727" />
</Target>
</Project>
EDIT: The process is a bit more complicated. According to your own project, you have to move some files to specific folders. Basically, follow what you are told by the compiler.
As you probably know required files are already copied (as part of the packaging process in Visual Studio) to a folder before creating the wsp file and you can easily point to that folder thus no need to do all those folder creation, moving and extraction. So the best solution IMHO would be to use only the following:
<Target Name="AfterBuild" DependsOnTargets="CreatePackage">
<Message Text="Validating Asp.Net files (PhysicalPath: $(LayoutPath), ToolPath: $(FrameworkDir)$(FrameworkVersion))..." Importance="high" />
<AspNetCompiler
VirtualPath="/"
PhysicalPath="$(LayoutPath)"
Clean="true" ContinueOnError="false"
ToolPath="$(FrameworkDir)$(FrameworkVersion)" />
<Message Text="Asp.Net validation is complete" Importance="high" />

How to get aspnet_compiler invoked from Visual Studio during build?

I want Visual Studio to precompile my ASP.NET application which is used as an Azure web role payload. So I've found this post that explains how to call aspnet_compiler to validate views.
I tried to add the following to "post-build event" of my ASP.NET application:
call "%VS100COMNTOOLS%\vsvars32.bat"
aspnet_compiler -v / -p $(ProjectDir)
or alternatively this (application name specified explicitly):
call "%VS100COMNTOOLS%\vsvars32.bat"
aspnet_compiler -v /ASP.NET-Application-ProjectNameHere -p $(ProjectDir)
In both cases when the build runs I see the following in the build output:
Setting environment for using Microsoft Visual Studio 2010 x86 tools.
Utility to precompile an ASP.NET application
Copyright (C) Microsoft Corporation. All rights reserved.
and clearly no precompilation happens because if I change any .aspx or .cshtml file "Build Action" to "None" it doesn't get to the Azure service package and the view no longer opens once the package is deployed to Azure.
How do I setup aspnet_compiler for precompiling from within Visual Studio?
If you want to use Asp.NET Compiler within your Visual Studio / msbuild then you can add
AspNetCompiler Task to your project file (.csproj/.vbproj) and set MvcBuildViews to true.
Example:
<Project>
<PropertyGroup>
<MvcBuildViews>true</MvcBuildViews>
</PropertyGroup>
<!-- ... -->
<Target Name="PrecompileWeb" AfterTargets="build" Condition="'$(MvcBuildViews)'=='true'">
<Message Text="Starting AspNetCompiler for $(ProjectDir)" Importance="high" />
<AspNetCompiler
VirtualPath="temp"
PhysicalPath="$(WebProjectOutputDir)"
Force="true"
/>
</Target>
<!-- ... -->
</Project>
You may also set TargetPath attribute to specify destination directory.
AfterTargets="build" is similar to "post-build event". See Target Build Order for more.
Integrate ASPX compilation into Visual Studio
One of the principles I insist on is to always try my build on a clean environment and simulate installation as if it was done by QA. Lately I've noticed that I keep falling on errors hidden deep in the aspx files. So, why not using the old and familiar aspnet_compiler.exe tool? It is located at C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727 and it is quite easy to use.
As a VS add-ins freak I've started thinking on an amazing add-in that will integrate to the VS and will listen to build events and display the results at the output pane. Heck, why not add some coffee serving capabilities?
It took me about 10 minutes of googling to stumble on this blog. Mike Hadlow had a genius in its simplicity idea. Use the POST BUILD EVENT!
All I need to do is put the following line in the post build event: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v / -p "$(ProjectDir)\"
Now, All that is left is to make the process of adding this line to each and every web project in our team to be automatic.
I have just the add-in for that :)
enter link description here
The answer from Matej was helpful for me, but I was not able to use it as-is and still get it to work for both local builds within Visual Studio and automated builds via TFS.
I had to add some extra msbuild settings. Actually, there were 2 different scenarios that I had. One project was an Web App that built into the _PublishedWebsites folder and one was an MVC Web App that did not build into the _PublishedWebsites folder.
First, add the following if it is not already in your project file:
<PropertyGroup>
<MvcBuildViews>true</MvcBuildViews>
</PropertyGroup>
For the one WITH _PublishedWebsites:
<Choose>
<When Condition="'$(BuildingInsideVisualStudio)' == true">
<PropertyGroup>
<AspNetCompilerPhysicalPath>$(ProjectDir)</AspNetCompilerPhysicalPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<AspNetCompilerPhysicalPath>$(WebProjectOutputDir)</AspNetCompilerPhysicalPath>
</PropertyGroup>
</Otherwise>
</Choose>
<Target Name="PrecompileWeb" AfterTargets="build" Condition="'$(MvcBuildViews)'=='true'">
<!-- aspnet_compiler.exe needs to be run on the folder that has the aspx files and the "bin" subfolder.
When running locally, the value needs to be the project directory, which is $(ProjectDir).
When running the TFS build, the value needs to be (BuildFolder)\(ProjectName)\_PublishedWebsites\(ProjectName).
The $(AspNetCompilerPhysicalPath) will hold the correct value for both types of builds.
-->
<Message Text="Starting AspNetCompiler for $(ProjectName) at $(AspNetCompilerPhysicalPath)" Importance="high" />
<AspNetCompiler
VirtualPath="/"
PhysicalPath="$(AspNetCompilerPhysicalPath)"
TargetPath="$(AspNetCompilerPhysicalPath)\bin_precompile"
Force="true"
/>
</Target>
For the one WITHOUT _PublishedWebsites:
<Choose>
<When Condition="'$(BuildingInsideVisualStudio)' == true">
<PropertyGroup>
<AspNetCompiler_CopyFilesFirst>false</AspNetCompiler_CopyFilesFirst>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<AspNetCompiler_CopyFilesFirst>true</AspNetCompiler_CopyFilesFirst>
</PropertyGroup>
<ItemGroup>
<AllOutputFiles Include="$(OutDir)\\**\*.*" />
</ItemGroup>
</Otherwise>
</Choose>
<Target Name="PrecompileWeb" AfterTargets="build" Condition="'$(MvcBuildViews)'=='true'">
<!-- aspnet_compiler.exe needs to be run on the folder that has the cshtml files and the "bin" subfolder. I could not find a setting that was appropriate for both.
When running locally, the value needs to be the project directory, which is $(ProjectDir).
When running the TFS build, there is no folder that matches both of those criteria.
So first we will copy the output into the source code folder's "bin" subfolder,
then run it against the source $(ProjectDir), the same as if we were building locally.
-->
<Message Text="Before running AspNetCompiler, copy files from $(OutDir) to $(ProjectDir)\bin" Importance="high" />
<Exec Command="( robocopy.exe /mir $(OutDir) $(ProjectDir)\bin ) ^& IF %25ERRORLEVEL%25 LEQ 1 exit 0" Condition="'$(AspNetCompiler_CopyFilesFirst)'=='true'" />
<Message Text="Starting AspNetCompiler for $(ProjectName) at $(ProjectDir)" Importance="high" />
<AspNetCompiler
VirtualPath="/"
PhysicalPath="$(ProjectDir)"
TargetPath="$(ProjectDir)\bin_precompile"
Force="true"
/>
</Target>

How do you use Web.config Transformation?

When I create a deployment package web.config is changed, but I don't understand this part:
I have two web.config transformation files, web.debug.config and web.release.config.
Are these transformation files only available or working when we make a web deployment or make a deployment package? Are the web.config transformations not used when the project runs locally from visual studio (e.g. via IIS Express)?
You are correct.
Config transformations are applied when you deploy or run a deployment package.
They do not transform on compilation.
If you need the transformed config file during compilation, you can get it by editing the project file (.csproj) and adding the below code.
<Target Name="AfterBuild">
<TransformXml Source="$(SolutionDir)WCFServices\Web.config"
Transform="$(SolutionDir)WCFServices\Web.Release.config"
Destination="$(OutDir)WebRelease.config"
StackTrace="true" />
</Target>
Multiple TransformXml tags can be added to get all the required config files. Also, This can be done before or after build.
You can invoke it using MSBuild and an extension called SlowCheetah.
There is an other VS extension called Configuration Transform good for this. If you don't want to install it, but to achieve this, just follow the examples shown in the demo solution to add different build config files and add some new MSBuild tasks in the project files. The download link for the demo solution can be found on the extension's Visual Studio Gallery webpage. This approach doesn't require any extra packages since MSBuild uses XSLT to do the XML tranformation.
Below are the MSBuild tasks added into a project file from the demo solution. In my case, when I followed it for a VS2015 ASP.NET MVC project, I didn't have to put <UsingTask TaskName="TransformXml" AssemblyFile=... in.
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="Exists('App.$(Configuration).config')">
<!--Generate transformed app config in the intermediate directory-->
<TransformXml Source="App.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="App.$(Configuration).config" />
<!--Force build process to use the transformed configuration file from now on.-->
<ItemGroup>
<AppConfigWithTargetPath Remove="App.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
<!--Override After Publish to support ClickOnce AfterPublish. Target replaces the untransformed config file copied to the deployment directory with the transformed one.-->
<Target Name="AfterPublish">
<PropertyGroup>
<DeployedConfig>$(_DeploymentApplicationDir)$(TargetName)$(TargetExt).config$(_DeploymentFileMappingExtension)</DeployedConfig>
</PropertyGroup>
<!--Publish copies the untransformed App.config to deployment directory so overwrite it-->
<Copy Condition="Exists('$(DeployedConfig)')" SourceFiles="$(IntermediateOutputPath)$(TargetFileName).config" DestinationFiles="$(DeployedConfig)" />
</Target>
Here is the way I applied in my .csproj file, quite simple:
<Target Name="AfterBuild" Condition="Exists('Web.$(Configuration).config')">
<Exec Command="attrib -R Web.config" />
<TransformXml Source="Web.config" Transform="Web.$(Configuration).config" Destination="Web.config" StackTrace="true" />
</Target>
Also there is a good post on this.
Further, for web.config transformation, Since VS2012 we can add a publish profile - Publish.pubxml (ProjectFolder/Properties/PublishProfiles/Publish.pubxml) to do a FileSystem publish, thus the web.config transformation will happen by default then. Below is a sample
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<publishUrl Condition="$(OutDir) != ''">$(OutDir)\_PublishedWebsites\$(ProjectName)</publishUrl> <!-- For MSBuild -->
<publishUrl Condition="$(OutDir) == ''">$(MSBuildThisFileDirectory)..\..\_PublishedWebsite\</publishUrl> <!-- For Visual Studio...cant use $(ProjectName) -->
<DeleteExistingFiles>True</DeleteExistingFiles>
</PropertyGroup>
</Project>

How do I compile an ASP.Net MVC project using MSBuild

How do I compile an ASP.Net MVC project using MSBuild? We use a Continuous Integration server to compile and deploy our applications. To keep things simple I created an MVC 1.0 project in VS2008. I immediately created an MSBuild script file to compile it. I did not change any code in the project. The MSBuild script contained the following target.
<AspNetCompiler
VirtualPath="/"
PhysicalPath="C:\Development\mvc1\"
TargetPath="c:\publish\xxx"
Force="true"
Debug="false"
Updateable="true"
The MVC project sln file is contained in the c:\development\mvc1\ directory. I am running XP/Pro.
I am receiving an error ASPCONFIG: it is an error to use a section registered as allowDefintion='MachineToApplication' beyond application level.. I removed the authenication mode, membership provider, etc. from the web config file until I finally saw a different error message. I am now receiving an error message saying that the file '/views/shared/site.master' does not exist.
What is going on? Thanks in advance for your help!
Am I using the wrong MSBuild command?
If you compile your sln-file (msbuild mysolution.sln) or
<MSBuild Projects="msbuild mysolution.sln" Targets="Rebuild" ContinueOnError="false"
StopOnFirstFailure="false" /><!-- -d -errorstack -->
and the sln-file has the ASP.NET MVC-project .csproj-file then the .csproj-file does have everything you need. Open the .csproj with notepad and look for:
1) This should be true:
<MvcBuildViews>false</MvcBuildViews>
2) Target Name="AfterBuildCompiler":
<Target Name="AfterBuildCompiler" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="SomeVirtualDir" PhysicalPath="C:\Development\mvc1\" TargetPath="c:\publish\xxx\" />
</Target>
I didn't do anything else and it worked. I actually made my config so that only release build deploy the application (by moving MvcBuildViews-property under PropertyGroups. Then I can use the same .csproj in the development (debug) and deployment (release).
This build script compiles an asp.net MVC 3 application. Since the entire internet appears to have forgotten the concept of "Build Script" this one does not require you to have Visual Studio installed on the target Machine or to "lol, you just have to edit your csproj file to get msbuild!!"
Moving on.
Make sure you have .NET 4 and MVC3 installed. By the way, my build scripts only work with msbuild 4, so make sure you're using the proper one.
The general process is as follows (thanks to many hints and answers I got here!)
1) Build the dependencies (you DLL's)
2) Build the DLL for your web application.
3) Call the asp.net compiler task.
4) Check the scripts for additional comments.
Note that this is called from an outside script that compiles other DLL's (Business, data access, etc.)
<Project ToolsVersion="4.0" DefaultTargets="build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildDir>..\..\dist</BuildDir>
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup >
<Reference Include="System.dll" />
<Reference Include="System.Core.dll" />
<Reference Include="System.Web.Abstractions.dll" />
<!-- add the remaining DLL's required. Check your References folder inside VS2010 and add the relevant entries here. It's a lot of references. I ommited them to make the post more compact.
For reasons that are beyond me, I only managed to get some DLL's referenced by full path. Go figure... -->
<Reference Include="C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Web.Helpers\v4.0_1.0.0.0__31bf3856ad364e35\System.Web.Helpers.dll" />
<Reference Include="C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Web.Mvc\v4.0_3.0.0.0__31bf3856ad364e35\System.Web.Mvc.dll" />
<Reference Include="C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Web.WebPages\v4.0_1.0.0.0__31bf3856ad364e35\System.Web.WebPages.dll" />
<!-- The "main build script" compiles the other DLL's from the project and places them on the BuildDir folder. Just reference it here-->
<Reference Include="$(BuildDir)\*.dll"></Reference>
</ItemGroup>
<!-- Build a DLL for the code file inside your web project (controllers, models, the lot...) place it together with the other DLL's
WARNING: Simple build command. Resource files are not included in this.
-->
<Target Name="BuildWebDll">
<ItemGroup>
<CodeFiles Include=".\**\*.cs" />
</ItemGroup>
<CSC Sources="#(CodeFiles)" TargetType="Library" References="#(Reference)" OutputAssembly="$(BuildDir)\cth.web.dll" >
</CSC>
</Target>
<!-- For reasons also unkown, but covered in a number os posts in this forum, the asp.net compiler requires the necessary DLL's to be placed on the BIN/ folder of your web project. That's why we're copying every DLL we need to said folder. For debugging, check the Bin folder on Visual Studio after you compile the project. You need to replicate that in your BIN/
-->
<Target Name="CopyDLLs">
<ItemGroup>
<DllFiles Include="$(BuildDir)/*.dll"/>
</ItemGroup>
<Copy SourceFiles="#(DllFiles)" DestinationFolder="Bin\"></Copy>
</Target>
<Target Name="build">
<CallTarget Targets="BuildWebDll"></CallTarget>
<CallTarget Targets="CopyDLLs"></CallTarget>
<!-- Call this from the webproject directory. PhysicalPath references ".". TargetPath can be everything you want -->
<AspNetCompiler Updateable="true" VirtualPath="/CTH.Web" PhysicalPath="./" TargetPath="$(BuildDir)/CTH.Web" Force="true" Debug="false" />
</Target>
Remember that you have to include resource files, do any web.config replacements, etc. I really hope this helps.
The easiest way I found was to add a WebDeployment project to your solution.
http://www.microsoft.com/DOWNLOADS/details.aspx?FamilyID=0aa30ae8-c73b-4bdd-bb1b-fe697256c459&displaylang=en
You set the properties for the build in the WebDeployment project (like precompile ) . The Buildserver builds the wdprj.
In my environment I have to start by building the web first. After that I can start the wdprj.
Here is my nant - script. It should be easy to write the same in msbuild. It actually runs in TeamCity.
xml version="1.0"?>
<project name="GreatProjectWeb"
default="build" basedir="."
xmlns="http://nant.sf.net/release/0.85/nant.xsd">
<description>Build Script</description>
<!-- builds only the csproj, not the entire solution-->
<target name="build" description="Compile the project using Debug configuration for more verbose error descriptions">
<echo message="Building..."> </echo>
<exec program="C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe" >
<arg value="GreatProjectWeb\GreatProjectWeb.csproj" />
<arg value="/t:Build" />
<arg value="/p:Configuration=Release" />
</exec>
<echo message="Building Projektfile finished. Starting WDP Project..."> </echo>
<exec program="C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe" >
<arg value="GreatProjectWeb_Build\GreatProjectWeb_Build.wdproj" />
<arg value="/t:Build" />
<arg value="/p:Configuration=Release" />
</exec>
<exec program="7z" >
<arg value="a" />
<arg value="GreatProjectWeb_Deploy\web_GreatProject.zip" />
<arg value="GreatProjectWeb_Deploy\*" />
</exec>
</target>
</project>
You could use NAnt which has a "msbuild" task in it that will just do it for you. NAnt is a great way to go for CI builds.
The NAnt home page
The NAnt Contrib home page
The MSBuild task reference from NAnt Contrib
...the contrib library adds some great functionality that the vanilla NAnt doesn't have. It is very simple. I've included a snippet of my .build file here so you can see how I've used it:
<property name="DeployDestination" value="\\MyTestServerName\DestinationFolder"/>
<property name="Solution.Configuration" value="Debug" overwrite="True" />
<property name="nant.settings.currentframework" value="net-3.5" />
<if test="${WebContentDestination=='Production'}">
<property name="DeployDestination" value="\\MyProductionServer\DestinationFolder"/>
</if>
...<snip>
<target name="Build">
<msbuild project="SolutionFileName.sln">
<arg value="/p:Configuration=${Solution.Configuration}" />
</msbuild>
</target>
<target name="Deploy">
<copy todir="${DeployDestination}" flatten="true" >
<fileset>All files to copy</fileset>
</copy>
</target>

How can I deploy an ASP.NET web application using Team Build?

I have managed to install Team Foundation Server 2008 and I created a separate build server (which works because my builds are currently failing).
I have created a simple "Hello World" Web application (all is the standard Default.aspx page) and have it in TFS's source control system.
Previously, prior to TFS, I'd simply precompile my web application and xcopy the results on to a pre-created IIS Virtual directory.
Scouring Google for a while, I have yet to find a step by step guide on correctly deploying an application from TFS Source via TeamBuild to a designated test web server. I know MS Build falls into this equation, so any guidance would be helpful.
I have seen bits and pieces about deployments, with folders such as _PublishedWebSites mentioned, but have yet to find anything step by step.
I've had success using a exec task in the AfterDropBuild target in the TFSBuild.proj file.
<Target Name="AfterDropBuild>
<Exec Command="xcopy /Y /E "$(DropLocation)\\$(BuildNumber)\%(ConfigurationToBuild.FlavorToBuild)\_PublishedWebsites\MyWebsite1\*.*" "\\server\MyWebsite1\"" />
<Exec Command="xcopy /Y /E "$(DropLocation)\\$(BuildNumber)\%(ConfigurationToBuild.FlavorToBuild)\_PublishedWebsites\MyWebsite2\*.*" "\\server\MyWebsite2\"" />
</Target>
Note that the permissions need to be setup correctly for the TFS service user to access the folder on the server your are copying to.
Firstly you should be using WebDeployment projects as this will do a lot more compilation and checking of your code and markup. See here for more info.
I have 4 environments setup DV [Development], PY [Prototype], PP [Pre-Production], PD [Production] all matching branches in TFS. Each of these has also has an entry in the sln configuration manager where you can setup what projects are required to be build and the build flags.
Once that is setup correctly you can then start setting up deployment scripts. I prefer use MSbuild to deploy as it will give you a lot more fine-grained approach to deployment. MSbuild is a bit strange to start with however once you get the hang of it it's quite powerful.
My deployment script which is added to the TeamBuild config is below. Basically as you can see I do a bit of post-build cleanup before I copy to the live servers. I also use 2 MSbuild frameworks (imported at the top).
<Import Project="$(MSBuildExtensionsPath)\Microsoft\SDC Tasks - Release 2.1.3155.0\Microsoft.Sdc.Common.tasks"/>
<Import Project="$(MSBuildExtensionsPath)\FreeToDev\MSBuild Tasks Suite 3.5\FreeToDev.MSBuild.tasks"/>
<PropertyGroup>
<InetpubFolder>\\PathToInetPub</InetpubFolder>
<AppFolder>AppFolder</AppFolder>
<AppFolderPath>$(InetpubFolder)$(AppFolder)</AppFolderPath>
<WebDeployName>WebDeployProjectName</WebDeployName>
<Debug>0</Debug>
<AppConfiguration>DV</AppConfiguration>
</PropertyGroup>
<Target Name="AfterDropBuild">
<Message Text="Begin Release to $(AppConfiguration) Webserver" />
<Message Text="DropLocation = $(DropLocation)" />
<CallTarget Targets="PostBuildCleanUp" />
<CallTarget Targets="DeployApp" />
</Target>
<Target Name="DeployApp">
<GetBuildProperties TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)">
<Output TaskParameter="DropLocation" PropertyName="DropLocation"></Output>
</GetBuildProperties>
<PropertyGroup>
<CodeDropLocation>$(DropLocation)\$(AppConfiguration) Release</CodeDropLocation>
</PropertyGroup>
<ItemGroup>
<AppFilesToDelete Include="$(AppFolderPath)\**\*.*" Exclude="$(AppFolderPath)\Library\*.*;$(AppFolderPath)\App_Offline.htm;$(AppFolderPath)\jobs\**\*.*" />
</ItemGroup>
<ItemGroup>
<FilesToDeploy Include="$(CodeDropLocation)\$(AppFolder)\**\*.*" Exclude="" />
</ItemGroup>
<Copy SourceFiles="$(CodeDropLocation)\$(AppFolder)\App_Offline[RemoveToActivate].htm" DestinationFiles="$(AppFolderPath)\App_Offline.htm" OverwriteReadOnlyFiles="true"/>
<Message Text="Deleting files in $(AppFolderPath)" />
<Microsoft.Sdc.Tasks.File.DeleteFiles Files="#(AppFilesToDelete)" Force="true" Condition="$(Debug)==0" />
<Message Text="Copy $(CodeDropLocation)\$(AppFolder) to $(AppFolderPath)" />
<Copy Condition="$(Debug)==0" SourceFiles="#(FilesToDeploy)" DestinationFiles="#(FilesToDeploy->'$(AppFolderPath)\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true"/>
<Message Text="Deploy to $(AppConfiguration) Completed" />
<Microsoft.Sdc.Tasks.File.DeleteFiles Files="$(AppFolderPath)\App_Offline.htm" Force="true" />
<OnError ExecuteTargets="ErrorHandler" />
</Target>
<Target Name="ErrorHandler">
<Message Text="Error encountered!!" />
</Target>
<Target Name="PostBuildCleanUp">
<GetBuildProperties TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)">
<Output TaskParameter="DropLocation" PropertyName="DropLocation"></Output>
</GetBuildProperties>
<PropertyGroup>
<CodeDropLocation>$(DropLocation)\$(AppConfiguration) Release</CodeDropLocation>
</PropertyGroup>
<ItemGroup>
<PostBuildCleanUpFilesToDelete Include="$(CodeDropLocation)\*.*;$(CodeDropLocation)\bin\*.xml;$(CodeDropLocation)\bin\*.pdb"/>
</ItemGroup>
<RemoveDir Directories="$(CodeDropLocation)\_PublishedWebsites\Web" />
<Microsoft.Sdc.Tasks.File.DeleteFiles Files="#(PostBuildCleanUpFilesToDelete)" Force="true">
<Output TaskParameter="DeletedFiles" ItemName="FilesThatWereDeleted" />
</Microsoft.Sdc.Tasks.File.DeleteFiles>
<Message Text="The files that were removed were #(FilesThatWereDeleted)" />
<FTDFolder TaskAction="Move" Path="$(CodeDropLocation)\_PublishedWebsites\$(WebDeployName)" TargetPath="$(CodeDropLocation)\$(AppFolder)"/>
<RemoveDir Directories="$(CodeDropLocation)\_PublishedWebsites" />
<RemoveDir Directories="$(CodeDropLocation)\$(AppFolder)\WebDeploy" />
<OnError ExecuteTargets="ErrorHandler" />
</Target>
Obviously you will need to modify to your system setup. Also it clears down the target folder before it starts to copy the new build accross. This is to make sure they system is clean but obviously you will need to add anything that you need to keep to the ExcludedFiles list.
I also have a folder for each environment in the main application project. This holds the web.config replacements (another feature of WebDeployment projects) and any other environement specifc files.
It will be a long process to get it working correctly but hopefully this will get you started!! (Obviously if you choose this apporach!)
This can be done via the build scripts directly, the Vertigo Software guys usually are the best source of info for a lot of TFS questions like this...unfortunately their blog posts don't usually rank that high on google. This one's by Jeff Atwood, one of the creators of this site:
Copying Web Files After a Team Build

Resources