A couple of days ago, I ran into a mysterious problem when trying to build a web project using MSBuild. The project built fine just a couple of days ago and the changes we made to it since had been pretty minor. But when I tried to build it again, it gave the following error right after the web project finished building:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v /Website -p "{source directory}" -u -f -d -fixednames "{destination directory}"

C:\Program Files\MSBuild\Microsoft\WebDeployment\v8.0\Microsoft.WebDeployment.targets(526,9): error MSB6006: "aspnet_compiler.exe" exited with code 1.

The strange thing is that when I tried to compile just the web project using

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v /Website -p "{source directory}" -u -f -d -fixednames "{destination directory}"

Everything seemed to compile fine.

Puzzled, I tried to search the internet for some clue. Apparently, a lot of people were having similar issues and some articles suggested that it might have something to do with the content of the aspx pages being mal-formatted (e.g. missing tags).

After hours of combing through all the aspx pages in the project, I eventually found out that a missing </head> tag had caused the mishap. And after adding the missing </head> tag back in everything built just fine as before.

While this solved the issue at hand, the error message emitted from MSBuild was certainly not helpful at all. Also, it did not quite explain as to why the MSBuild would fail when the website actually compiled fine. So I decided to dig deeper to figure out exactly how and why such cryptic error message was given when compiling mal-formatted aspx pages so that in the future when similar errors occur I can better diagnose the problem instead of trying to guess what might be wrong.

MSBuild and ASPNET compiler were both written in .Net. So it was not difficult to use a reflection tool (e.g. ILDasm or Lutz Roeder’s .Net Reflector http://www.aisto.com/roeder/dotnet/) to see the actual source code to see what the programs were doing.

The first thing was to trace the error message to the code where it was emitted. The error message MSB6006: "aspnet_compiler.exe" exited with code 1 was in Microsoft.Build.Utilities.Strings.resources in Microsoft.Build.Utilities.dll

ToolTask.ToolCommandFailed=MSB6006: "{0}" exited with code {1}.

And it was emitted from

protected virtual bool HandleTaskExecutionErrors();

in ToolTask class in the same DLL.

When Execute() method in ToolTask errors out, it returns a Microsoft.Build.Utilities:2.0.0.0:b03f5f7f11d50a3a/Microsoft.Build.Utilities.HostObjectInitializationStatus">HostObjectInitializationStatus

public enum HostObjectInitializationStatus { UseHostObjectToExecute, UseAlternateToolToExecute, NoActionReturnSuccess, NoActionReturnFailure }

And when the initialization status is NoActionReturnFailure, we would get the MSB6006: "aspnet_compiler.exe" exited with code 1 error. Here is the code section:

case HostObjectInitializationStatus.NoActionReturnFailure: this.exitCode = 1; return this.HandleTaskExecutionErrors();

Without boring you with much of the details, here is a high-level picture of what happens when MSBuild compiles a web project:

When MSBuild is launched from command prompt, the invocation chain looks like:

Msbuild.exe : class Microsoft.Build.CommandLine.MSBuildApp">MSBuildApp

public static int Main() ==>public static ExitType Execute(string commandLine)

==>private static bool BuildProject(string projectFile, string[] targets, BuildPropertyGroup propertyBag, ILogger[] loggers, bool needToValidateProject, string schemaFile)

==>Microsoft.Build.Engine.dll : class Engine internal bool BuildProject(Project project, string[] targetNames, IDictionary targetOutputs, BuildSettings buildFlags, bool fireProjectStartedFinishedEvents)

==>Microsoft.Build.Engine.dll : class Project internal bool DoBuild(string[] targetNamesToBuild, IDictionary targetOutputs, bool fireProjectStartedFinishedEvents)

==>Microsoft.Build.Engine.dll : class Target in Microsoft.Build.Framework.dll internal bool Build(IDictionary targetOutputs) private bool ExecuteAllTasks() private bool ExecuteAllTasks(DependencyAnalysisResult howToBuild, Hashtable changedTargetInputs, Hashtable upToDateTargetInputs, ItemBucket bucket, ArrayList taskOutputItems, BuildPropertyGroup taskOutputProperties)

==>Microsoft.Build.Engine.dll : class TaskEngine internal bool ExecuteTask(ExecutionMode howToExecuteTask, Hashtable projectItemsAvailableToTask, BuildPropertyGroup projectPropertiesAvailableToTask, out bool taskClassWasFound)

==>private bool InstantiateTask(AppDomain taskAppDomain, out ITask task)

==>Microsoft.Build.Tasks.dll : class Exec protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { int num; try { num = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } finally { File.Delete(this.batchFile); File.Delete(this.tempFile); } return num; }

==>Microsoft.Build.Utilities.dll : class ToolTask protected virtual int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { this.LogPathToTool(this.ToolName, pathToTool); string path = null; Process proc = null; this.standardErrorData = new Queue(); this.standardOutputData = new Queue(); this.standardErrorDataAvailable = new ManualResetEvent(false); this.standardOutputDataAvailable = new ManualResetEvent(false); this.toolExited = new ManualResetEvent(false); this.toolTimeoutExpired = new ManualResetEvent(false); try { string responseFileSwitch; path = this.GetTemporaryResponseFile(responseFileCommands, out responseFileSwitch); proc = new Process(); proc.StartInfo = this.GetProcessStartInfo(pathToTool, commandLineCommands, responseFileSwitch); proc.EnableRaisingEvents = true; proc.Exited += new EventHandler(this.ReceiveExitNotification); proc.ErrorDataReceived += new DataReceivedEventHandler(this.ReceiveStandardErrorData); proc.OutputDataReceived += new DataReceivedEventHandler(this.ReceiveStandardOutputData); this.exitCode = 1; proc.Start(); proc.StandardInput.Close(); proc.BeginErrorReadLine(); proc.BeginOutputReadLine(); this.toolTimer = new Timer(new TimerCallback(this.ReceiveTimeoutNotification)); this.toolTimer.Change(this.Timeout, 1); this.HandleToolNotifications(proc); } finally { … … } return this.exitCode; } }

So now it is pretty clear by now, when MSBuild is executed and the project to be built is a web project, the aspnet_compiler.exe was invoked via a exec and the returning code of aspnet_compiler.exe is then checked to see whether aspnet_compiler.exe had succeeded or not. If error code is not 0, this exit code gets bubbled all the way up toToolTask.Execute() and returns the mysterious MSB6006: "aspnet_compiler.exe" error.

Since this error is emitted as an exit code within the command prompt environment, when running the aspnet_compiler.exe by itself will not generate the error (the error is an exit code of the program and is not displayed). We can, however, test to see whether the aspnet_compiler.exe indeed succeeded or not by checking the %ERRORLEVEL% environment variable after we execute the aspnet_compiler.exe

The following batch file illustrates this (test.bat):

@echo off

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_compiler.exe -v /Website -p "{project directory}" -u -f -d -fixednames "{destination directory}”

echo %ERRORLEVEL%

Now, try compiling a webpage without the matching </form> tag, you will get an ERRORLEVEL of 1. When compiling against a well formatted webpage an ERRORLEVEL of 0 is returned.

Anyway, the MSB6006 error message is clearly too generic to be helpful. In this specific scenario, I guess it is more appropriate to give a warning (since aspnet_compiler.exe compiles fine) then an error.

Be Sociable, Share!