MSBuild Task dll locked by Visual Studio

Through MSBuild we are able to control how we wish to process and build our software. A nice feature of this is the ability to create custom Tasks.

A Task is a unit of executable code used by MSBuild to perform atomic build operations. MSDN

Creating a custom Tasks allows us to hook into the build pipeline where we can execute custom functionality. Examples of native Tasks are:

  • MakeDir Task
  • Copy Task

Writing a custom Task is fairly easy. But once we start building a task used by another project a problem emerges.

Assembly gets locked

Imagine the following setup where we have a solution containing 2 projects.

  • TaskProject
  • WebProject

WebProject uses the following RandomNumberTask from the TaskProject.dll.

public class RandomNumberTask : AppDomainIsolatedTask
{
	[Output]
	public int Number { get; set; }

	public override bool Execute()
	{
		Number = new Random().Next(1, 1000);
		return true;
	}
}
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	...

  <UsingTask TaskName="RandomNumberTask" AssemblyFile="$(SolutionDir)\TaskProject\bin\debug\TaskProject.dll" />
  <Target Name="BeforeBuild">
	<RandomNumberTask>
		<Output TaskParameter="Number" PropertyName="OutputNumber" />
	</RandomNumberTask>

	<Exec Command="echo Number is: $(OutputNumber)" />
  </Target>
</Project>

As we start out writing said Task and build the TaskProject everything works as expected. We can build TaskProject as many times as we want.

Then we want to use the Task in action and we build WebProject. This works perfectly! Later we wish to make a change to RandomNumberTask and try to build it. Sadly, we are met by an error stating “TaskProject.dll” cannot be overwritten since it is being used by another process.

It turns out Visual Studio, via MSBuild.exe, has attained a lock on TaskProject.dll which is only released when we restart Visual Studio.

Your DLL is a custom task assembly, once the Visual Studio process launches the task DLL, it will not release the DLL until the Visual studio be terminated. MSDN Community Support

Of course, this totally cripples the workflow we are aiming for so we need to find a solution where we can use & build our Task without needing to restart Visual Studio every time we make even the smallest change.

Run MSBuild process manually

The only solution I have been able to find is to manually run an instance of msbuild.exe outside the normal cycle imposed by Visual Studio. By doing this, we ensure that once the process has finished running, it will exit and thus release any file currently locked.

Instead of running our Task in WebProject.csproj we now execute a new instance of msbuild.exe passing as argument a new project file “RandomBuild.xml” now responsible for running the Task.

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	...

  <PropertyGroup>
    <PrebuildProject>$(ProjectDir)RandomBuild.xml</PrebuildProject>
  </PropertyGroup>
  <Target Name="BeforeBuild">
    <Exec Command="$(MSBuildToolsPath)\msbuild.exe &quot;$(PrebuildProject)&quot; /p:SolutionDir=&quot;$(SolutionDir)&quot;" />
  </Target>
</Project>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<UsingTask TaskName="RandomNumberTask" AssemblyFile="$(SolutionDir)\TaskProject\bin\debug\TaskProject.dll" />
	<Target Name="Build">
		<RandomNumberTask>
			<Output TaskParameter="Number" PropertyName="OutputNumber" />
		</RandomNumberTask>

		<Exec Command="echo Number is: $(OutputNumber)" />
	</Target>
</Project>

We are now able to do fast iterations over introducing changes to RandomNumberTask and testing them by building WebProject.

The cost

The custom execution of msbuild.exe comes at a price. Since we are now doing the executing on our own we have to make every variable we need available. From the example above it shows that we are manually passing the $(SolutionDir) parameter along. We would have to do this for every environment variable we wish to use making it quite cumbersome.

Furthermore, we cannot communicate information, such as variables, to other parts of WebProject.csproj since everything we do is contained in RandomBuild.xml. These trade-offs should be taken into account when using the method described above.

Comments

  1. Tomas Havlik says:

    Thank you for very useful guide.
    Just tiny issue – isn’t there missing “"” at the end of this line?

  2. Alex Stankiewicz says:

    What about usage of such env var libe below(you can also use such param like nr = node reuse)?
    MSBUILDDISABLENODEREUSE=1

Leave a Reply to Alex Stankiewicz Cancel reply

*