How do I modify a .csproj file in order to have it run my arbitrary build step?


Step 1 - Figure out where you want your step to execute


In Whidbey, 100% of the build process is expressed through MSBuild in what we call "Target Files". Those files are located under %windir%\microsoft.net\framework\v2.0.xxxx and consist of:
* Microsoft.common.Targets - This file defines all the common steps required in order to build a .NET application.
* Microsoft.csharp.Targets - This file defines all the c# specific steps required in order to build a C# application.
* Microsoft.visualbasic.Targets - This file defines all the vb specific steps required in order to build a VB application.

Visual Studio generated projects <Import ... /> the language specific target file (VB or C#), which in turn will <Import ... /> the Common target file. In union these target files define many different targets, which can be hard to parse in one sitting. With this in mind I've boiled it down to the main targets that I forsee people wanting to interact with. They are:

* PrepareForBuild
* PreBuildEvent
* UnmanagedUnregistration
* ResolveReferences
* PrepareResources
* ResolveKeySource
* TimeStampBeforeCompile
* Compile
* TimeStampAfterCompile
* CreateSatelliteAssemblies
* BuildManifests
* PrepareForRun
* UnmanagedRegistration

Step 2 - Link your custom step into the proper place with a target


Now that you have chosen where your step belongs adding your custom step is really easy. Think of our target files as being one large linked list. Just like a linked list you can insret your custom step at any point before or after any other step. Imagine a world where you had Target A->Target B (where -> equates to dependency). Now imagine you wanted to put your step between target A and B. You'd do so by simply:

* Removing the link between A and B. This results in the picture looking like A__B (where __ means they are not related)
* Adding a link between A and Custom, resulting in the picture looking like A->Custom__B
* Adding a link between Custom and B, resulting in the picture looking like A->Custom->B

In our target files the -> (ie links) are represented as Target dependencies or <Target ... DependsOnTargets="<insertlinkshere>" />. The <insertlinkshere> is represented as an MSBuild property which by pattern is named TargetNameDependsOn. We represent them as properties so that you can easily override them from within your project file without ever having to touch the target files themselves. Let's walk through an example to make things clearer. In my example let's say I want to insert my target between the compile step and whatever happens before compilation. I'd do that by simply:

Removing the link between compile and whatever happens before compilation.

		 simple.csproj
		 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
		  [<ItemGroup>]
		    <Compile Include="*.cs" />
		  [</ItemGroup>]
		  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
		  [<PropertyGroup>]
		    [<CompileDependsOn] />
		  [</PropertyGroup>]
		 </Project>
	

Basically with this step you have taken ownership of the Compilation dependecy chain and you have made it not depend on anything

Adding a link between the compilation step and MySpecialSauceTarget

		 simple.csproj
		 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
		  [<ItemGroup>]
		    <Compile Include="*.cs" />
		  [</ItemGroup>]
		  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
		  [<PropertyGroup>]
		    [<CompileDependsOn>MySpecialSauceTarget</CompileDependsOn>]
		  [</PropertyGroup>]
		 </Project>
	

Essentially all I had to do is add my special target into the dependency chain and now we have Compile->MySpecialSauceTarget

Fix the linking so that Compilation continues to depend on all the other steps just like before

		 simple.csproj
		 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
		  [<ItemGroup>]
		    <Compile Include="*.cs" />
		  [</ItemGroup>]
		  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
		  [<PropertyGroup>]
		    [<CompileDependsOn>MySpecialSauceTarget;$(CompileDependsOn)</CompileDependsOn>]
		  [</PropertyGroup>]
		 </Project>
	

Because properties are simple key, value pair, and because they are like environment variables you can play tricks with them the same way you would with %path%. You can easily re-use the previous definition as part of the new definition, and this is essentially what I've done in the above example.

Step 3 - Author a target that contains your custom step

Now that we've linked MySpecialSauceTarget in the overall build process all we have left is creating the target in my simple project file. Inside that target I can do many things like:

* Call my custom set of tasks
* Call custom tools with exec
* Call my custom set of targets by making my very own target depend on many other targets

Calling a custom set of tasks from within my target

		 simple.csproj
		 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
		  [<ItemGroup>]
		    <Compile Include="*.cs" />
		  [</ItemGroup>]
		  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
		  [<PropertyGroup>]
		    [<CompileDependsOn>MySpecialSauceTarget;$(CompileDependsOn)</CompileDependsOn>]
		  [</PropertyGroup>]
		  <Target Name="MySpecialSauceTarget" >
		     [<MySpecialTask1] Attrib1="..." Attrib2="..." ... />
		     [<MySpecialTask2] Attrib1="..." Attrib2="..." ... />
		     [<MySpecialTask3] Attrib1="..." Attrib2="..." ... />
		  </Target>
		 </Project>
	

Calling custom tools with exec

		 simple.csproj
		 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
		  [<ItemGroup>]
		    <Compile Include="*.cs" />
		  [</ItemGroup>]
		  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
		  [<PropertyGroup>]
		    [<CompileDependsOn>MySpecialSauceTarget;$(CompileDependsOn)</CompileDependsOn>]
		  [</PropertyGroup>]
		  <Target Name="MySpecialSauceTarget" >
		     <Exec Command="commandlinetool.exe arg1 arg 2" />
		     <Exec Command="commandlinetoo2.exe arg1 arg 2" />
		     <Exec Command="commandlinetoo3.exe arg1 arg 2" />
		  </Target>
		 </Project>
	

Call my custom set of targets by making my very own target depend on many other targets

		 simple.csproj
		 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
		  [<ItemGroup>]
		    <Compile Include="*.cs" />
		  [</ItemGroup>]
		  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
		  [<PropertyGroup>]
		    [<CompileDependsOn>MySpecialSauceTarget;$(CompileDependsOn)</CompileDependsOn>]
		  [</PropertyGroup>]
		  <Target Name="MySpecialSauceTarget" DependsOnTargets="CustomTarget1;CustomTarget2;CustomTarget3" />
		 </Project>
	

It goes without saying that you can, and are encouraged to use any of these in combination. You can use custom tasks with existing tasks with other targets etc.

Coming soon ... in Beta 2, you'll be able to avoid the "chaining" of the target dependency chain that you see above. Instead, you can just give your target the name of a target that's already in a dependency chain in a useful place.


In this example, instead of "MySpecialSauceTarget" you could name your target "BeforeCompile" and it would run before compilation. You won't need to modify the <CompileDependsOn> property.
Microsoft Communities