I am writing up a checkout, build and deployment application in C#, and need to know the best way to detect whether my call to msbuild.exe
has succeeded or not. I have tried to use the error code from the process, but I am not sure whether this is always accurate.
Is there a way (through the code below) that I can tell whether msbuild.exe
completed successfully?
try { Process msbProcess = new Process(); msbProcess.StartInfo.FileName = this.MSBuildPath; msbProcess.StartInfo.Arguments = msbArguments; msbProcess.Start(); msbProcess.WaitForExit(); if (msbProcess.ExitCode != 0) { // } else { // } msbProcess.Close(); } catch (Exception ex) { // }
To run MSBuild at a command prompt, pass a project file to MSBuild.exe, together with the appropriate command-line options. Command-line options let you set properties, execute specific targets, and set other options that control the build process.
If you have Visual Studio, then you already have MSBuild installed. With Visual Studio 2022, it's installed under the Visual Studio installation folder. For a typical default installation on Windows 10, MSBuild.exe is under the installation folder in MSBuild\Current\Bin.
Click on System and Security and then on System. In the left pane, click on Advanced system settings. At the very bottom of the pop up, click on Environment Variables. Edit the Path variable and append the folder's path that contains the MSBuild.exe to it (e.g., ;C:\Windows\Microsoft.NET\Framework64\v4.
To build a specific target of a specific project in a solution. At the command line, type MSBuild.exe <SolutionName>. sln , where <SolutionName> corresponds to the file name of the solution that contains the target that you want to execute.
As far as I've been able to determine, MSBuild returns an exit code greater then zero when it encounters an error. If it doesn't encounter any errors, it returns exit code 0. I've never seen it exit with code lower than 0.
I use it in a batch file:
msbuild <args> if errorlevel 1 goto errorDone
In four years of using it this way, I've never had reason to question the correctness of this approach.
Several questions on the MSDN forums ask the same thing.
The standard response is, in effect, "if errorlevel is 0, then there was no error".
Sorry if I'm a little bit too late for the party... but nearly 7 years after the question was posted I wanted to see a complete answer for it. I did some tests using the code below, and here are the conclusions:
Analysis
msbuild.exe
returns 1
when at least one build error occurs, and returns 0
when the build is successfully completed. At present, the program does not take warnings into account, which means a successful build with warnings causes msbuild.exe
to still return 0
.
Other errors like: trying to build a project that does not exist, or providing an incorrect argument (like /myInvalidArgument
), will also cause msbuild.exe
to return 1
.
Source Code
The following C# code is a complete implementation for building your favorite projects by firing msbuild.exe
from a command line. Don't forget to setup any necessary environment settings before compiling your projects.
Your BuildControl class:
using System; namespace Example { public sealed class BuildControl { // ... public bool BuildStuff() { MsBuilder builder = new MsBuilder(@"C:\...\project.csproj", "Release", "x86") { Target = "Rebuild", // for rebuilding instead of just building }; bool success = builder.Build(out string buildOutput); Console.WriteLine(buildOutput); return success; } // ... } }
MsBuilder class: Builds stuff by calling MsBuild.exe from command line:
using System; using System.Collections.Generic; using System.IO; using System.Text; namespace Example { public sealed class MsBuilder { public string ProjectPath { get; } public string LogPath { get; set; } public string Configuration { get; } public string Platform { get; } public int MaxCpuCount { get; set; } = 1; public string Target { get; set; } = "Build"; public string MsBuildPath { get; set; } = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MsBuild.exe"; public string BuildOutput { get; private set; } public MsBuilder(string projectPath, string configuration, string platform) { ProjectPath = !string.IsNullOrWhiteSpace(projectPath) ? projectPath : throw new ArgumentNullException(nameof(projectPath)); if (!File.Exists(ProjectPath)) throw new FileNotFoundException(projectPath); Configuration = !string.IsNullOrWhiteSpace(configuration) ? configuration : throw new ArgumentNullException(nameof(configuration)); Platform = !string.IsNullOrWhiteSpace(platform) ? platform : throw new ArgumentNullException(nameof(platform)); LogPath = Path.Combine(Path.GetDirectoryName(ProjectPath), $"{Path.GetFileName(ProjectPath)}.{Configuration}-{Platform}.msbuild.log"); } public bool Build(out string buildOutput) { List<string> arguments = new List<string>() { $"/nologo", $"\"{ProjectPath}\"", $"/p:Configuration={Configuration}", $"/p:Platform={Platform}", $"/t:{Target}", $"/maxcpucount:{(MaxCpuCount > 0 ? MaxCpuCount : 1)}", $"/fileLoggerParameters:LogFile=\"{LogPath}\";Append;Verbosity=diagnostic;Encoding=UTF-8", }; using (CommandLineProcess cmd = new CommandLineProcess(MsBuildPath, string.Join(" ", arguments))) { StringBuilder sb = new StringBuilder(); sb.AppendLine($"Build started: Project: '{ProjectPath}', Configuration: {Configuration}, Platform: {Platform}"); // Call MsBuild: int exitCode = cmd.Run(out string processOutput, out string processError); // Check result: sb.AppendLine(processOutput); if (exitCode == 0) { sb.AppendLine("Build completed successfully!"); buildOutput = sb.ToString(); return true; } else { if (!string.IsNullOrWhiteSpace(processError)) sb.AppendLine($"MSBUILD PROCESS ERROR: {processError}"); sb.AppendLine("Build failed!"); buildOutput = sb.ToString(); return false; } } } } }
CommandLineProcess class - Starts a command line process and waits until it finishes. All standard output/error is captured, and no separate window is started for the process:
using System; using System.Diagnostics; using System.IO; namespace Example { public sealed class CommandLineProcess : IDisposable { public string Path { get; } public string Arguments { get; } public bool IsRunning { get; private set; } public int? ExitCode { get; private set; } private Process Process; private readonly object Locker = new object(); public CommandLineProcess(string path, string arguments) { Path = path ?? throw new ArgumentNullException(nameof(path)); if (!File.Exists(path)) throw new ArgumentException($"Executable not found: {path}"); Arguments = arguments; } public int Run(out string output, out string err) { lock (Locker) { if (IsRunning) throw new Exception("The process is already running"); Process = new Process() { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo() { FileName = Path, Arguments = Arguments, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, }, }; if (!Process.Start()) throw new Exception("Process could not be started"); output = Process.StandardOutput.ReadToEnd(); err = Process.StandardError.ReadToEnd(); Process.WaitForExit(); try { Process.Refresh(); } catch { } return (ExitCode = Process.ExitCode).Value; } } public void Kill() { lock (Locker) { try { Process?.Kill(); } catch { } IsRunning = false; Process = null; } } public void Dispose() { try { Process?.Dispose(); } catch { } } } }
PS: I'm using Visual Studio 2017 / .NET 4.7.2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With