How to detect the status of msbuild from command line or C# Application

前端 未结 2 1615
粉色の甜心
粉色の甜心 2020-12-15 17:01

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.

2条回答
  •  半阙折子戏
    2020-12-15 17:51

    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 arguments = new List()
                {
                    $"/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

提交回复
热议问题