Как запустить команду MsBUILD из консольного приложения С#?

Я пытаюсь запустить msbuild /t:scmclean из моего консольного приложения C#, но не могу. Это полностью работает, если я запускаю его из командной строки Visual Studio 2017, но терпит неудачу при выполнении из моего консольного приложения C#.

Моя целевая папка, в которой я запускаю эту команду: D:/Git/abc/Build/Tools, эта команда внутренне вызывает файл csproj.

Что я пробовал:

var result = await Cli.Wrap("C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\MSBuild\\15.0\\Bin\\MSBuild.exe")
                    .WithArguments("/t:scmclean")
                    .WithWorkingDirectory("D:\\git\\abc\\Build\\Tools")
                    .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutCO))
                   .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrCO))
                    .ExecuteBufferedAsync();
var stdOut1 = stdOutCO.ToString();
var stdErr1 = stdErrCO.ToString();

Console.WriteLine("Build Info:");
Console.WriteLine(stdOut1);
Console.WriteLine(stdErr1);

Это дает отказ в доступе к ошибке, однако, когда я проверяю пользователя с помощью команды

string userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;

он отображается как супер пользователь. Еще одна вещь, которую я пробовал, запускается после установки местоположения в мой текущий каталог:

Process.Start("C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\MSBuild\\15.0\\Bin\\MSBuild.exe", "/t:scmclean");

это также кажется неудачным. Также мне нужно знать, как записать вывод процесса в консоль.

MSBUILD: ошибка MSB1003: укажите проект или файл решения. текущий рабочий каталог не содержит файла проекта или решения.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
92
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я не уверен, что такое scmclean, потому что я не мог найти никакой информации об этом. Тем не менее, попробуйте следующее:

Вариант 1 (КлиОбертывание)

Загрузите/установите пакеты NuGet:

  • Microsoft.Build.Locator
  • CliWrap

Добавьте следующие директивы using (например, Form1.cs)

  • using System.Diagnostics;
  • using System.IO;
  • using Microsoft.Build.Locator;
  • using CliWrap;
  • using CliWrap.Buffered;

Выберите один из приведенных ниже вариантов (например, вариант A, вариант B, вариант C, вариант D или вариант E):

Вариант A — ExecuteBufferedAsync:

private async Task RunMSBuild(string solutionFilename, string msBuildPath, string arguments = null)
{
    //get tools path for newest Visual Studio version
    string msBuildFilename = Path.Combine(msBuildPath, "MSBuild.exe");

    if (!File.Exists(msBuildFilename))
        throw new Exception($"Error: MSBuild.exe not found ({msBuildFilename})");

    var result = await Cli.Wrap(msBuildFilename)
            .WithArguments(arguments)
            .WithWorkingDirectory(Path.GetDirectoryName(solutionFilename))
            .ExecuteBufferedAsync();

    Console.WriteLine(result.StandardOutput);
    Console.WriteLine(result.StandardError);
}

Вариант Б — ExecuteAsync:

private async Task RunMSBuild(string solutionFilename, string msBuildPath, string arguments = null)
{
    var stdOutBuffer = new StringBuilder();
    var stdErrBuffer = new StringBuilder();

    //get tools path for newest Visual Studio version
    string msBuildFilename = Path.Combine(msBuildPath, "MSBuild.exe");

    if (!File.Exists(msBuildFilename))
        throw new Exception($"Error: MSBuild.exe not found ({msBuildFilename})");

    var result = await Cli.Wrap(msBuildFilename)
            .WithArguments(arguments)
            .WithWorkingDirectory(Path.GetDirectoryName(solutionFilename))
            .WithStandardOutputPipe(PipeTarget.ToStringBuilder(stdOutBuffer))
            .WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer))
            .ExecuteAsync();

    // Access stdout & stderr buffered in-memory as strings
    var stdOut = stdOutBuffer.ToString();
    var stdErr = stdErrBuffer.ToString();

    Console.WriteLine(stdOut);
    Console.WriteLine(stdErr);
}

Вариант C — ExecuteAsync (делегат):

Примечание. Этот параметр отображает выходные данные в реальном времени.

private async Task RunMSBuild(string solutionFilename, string msBuildPath, string arguments = null)
{
    //get tools path for newest Visual Studio version
    string msBuildFilename = Path.Combine(msBuildPath, "MSBuild.exe");

    if (!File.Exists(msBuildFilename))
        throw new Exception($"Error: MSBuild.exe not found ({msBuildFilename})");

    var result = await Cli.Wrap(msBuildFilename)
           .WithArguments(arguments)
           .WithWorkingDirectory(Path.GetDirectoryName(solutionFilename))
           .WithStandardOutputPipe(PipeTarget.ToDelegate(delegate (string msg)
           {
               Console.WriteLine(msg);
           }))
           .WithStandardErrorPipe(PipeTarget.ToDelegate(delegate (string msg)
           {
               Console.WriteLine(msg);
           }))
           .ExecuteAsync();
}

Вариант D — ExecuteAsync (действие/делегат):

Примечание. Этот параметр отображает выходные данные в реальном времени.

private async Task RunMSBuild(string solutionFilename, string msBuildPath, string arguments = null)
{
    //get tools path for newest Visual Studio version
    string msBuildFilename = Path.Combine(msBuildPath, "MSBuild.exe");

    if (!File.Exists(msBuildFilename))
        throw new Exception($"Error: MSBuild.exe not found ({msBuildFilename})");

    Action<string> handleStdOut = delegate (string msg)
    {
        Console.WriteLine(msg);
    };

    Action<string> handleStdErr = delegate (string msg)
    {
        Console.WriteLine(msg);
    };

    var result = await Cli.Wrap(msBuildFilename)
           .WithArguments(arguments)
           .WithWorkingDirectory(Path.GetDirectoryName(solutionFilename))
           .WithStandardOutputPipe(PipeTarget.ToDelegate(handleStdOut))
           .WithStandardErrorPipe(PipeTarget.ToDelegate(handleStdErr))
           .ExecuteAsync();
}

Вариант E — ExecuteAsync (EventStream):

Примечание. Этот параметр отображает выходные данные в реальном времени.

Загрузите/установите пакет NuGet: System.Reactive.Linq

Добавьте использование директив:

  • using CliWrap.EventStream;
  • using System.Reactive.Linq;
private async Task RunMSBuild(string solutionFilename, string msBuildPath, string arguments = null)
{
    //get tools path for newest Visual Studio version
    string msBuildFilename = Path.Combine(msBuildPath, "MSBuild.exe");

    if (!File.Exists(msBuildFilename))
        throw new Exception($"Error: MSBuild.exe not found ({msBuildFilename})");

    Command cmd = Cli.Wrap(msBuildFilename)
            .WithArguments(arguments)
            .WithWorkingDirectory(Path.GetDirectoryName(solutionFilename));
    
    await cmd.Observe().ForEachAsync(cmdEvent =>
    {
        switch (cmdEvent)
        {
            case StartedCommandEvent started:
                Console.WriteLine($"Process started; ID: {started.ProcessId}");
                break;
            case StandardOutputCommandEvent stdOut:
                Console.WriteLine($"{stdOut.Text}");
                break;
            case StandardErrorCommandEvent stdErr:
                Console.WriteLine($"{stdErr.Text}");
                break;
            case ExitedCommandEvent exited:
                Console.WriteLine($"Process exited; Code: {exited.ExitCode}");
                break;
        }
    });
}

(Необязательно) Добавьте следующие перегрузки методов:

public enum VSVersionType
{
    Latest,
    Oldest
}

private async Task RunMSBuild(string solutionFilename, int vsVersionYear, string arguments = null)
{
    //get Visual Studio instances
    List<VisualStudioInstance> vsInstances = MSBuildLocator.QueryVisualStudioInstances().OrderBy(x => x.Version).ToList();

    if (vsInstances != null && vsInstances.Count > 0)
    {
        //get MSBuild path
        var msBuildPath = vsInstances.Where(x => x.Name.EndsWith(vsVersionYear.ToString())).Select(x => x.MSBuildPath).FirstOrDefault();

        await RunMSBuild(solutionFilename, msBuildPath, arguments);
    }
}

private async Task RunMSBuild(string solutionFilename, VSVersionType vsVersion, string arguments = null)
{
    //get Visual Studio instances
    List<VisualStudioInstance> vsInstances = MSBuildLocator.QueryVisualStudioInstances().OrderBy(x => x.Version).ToList();

    if (vsInstances != null && vsInstances.Count > 0)
    {
        string msBuildPath = string.Empty;
        if (vsVersion == VSVersionType.Latest)
            msBuildPath = vsInstances[vsInstances.Count - 1].MSBuildPath;
        else if (vsVersion == VSVersionType.Oldest)
            msBuildPath = vsInstances[0].MSBuildPath;

        await RunMSBuild(solutionFilename, msBuildPath, arguments);
    }
}

Использование 1:

string solutionFilename  = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
await RunMSBuild(solutionFilename, 2017);

Использование 2:

string solutionFilename  = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
await RunMSBuild(solutionFilename, 2017, "-t:Clean;Compile");

Использование 3:

string solutionFilename = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
await RunMSBuild(solutionFilename, VSVersionType.Oldest);

Использование 4:

string solutionFilename = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
await RunMSBuild(solutionFilename, VSVersionType.Oldest, "-t:Clean;Compile");

Использование 5:

string msBuildPath = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.ProgramFilesX86)), @"Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin");
string solutionFilename = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
await RunMSBuild(solutionFilename, msBuildPath);

Использование 6:

string msBuildPath = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.ProgramFilesX86)), @"Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin");
string solutionFilename = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
await RunMSBuild(solutionFilename, msBuildPath, "-t:Clean;Compile"););

Вариант 2 (Система.Диагностика.Процесс)

Загрузите/установите пакет NuGet: Microsoft.Build.Locator

Добавьте следующие директивы using (например, Form1.cs)

  • using System.Diagnostics;
  • using System.IO;
  • using Microsoft.Build.Locator;
public enum VSVersionType
{
    Latest,
    Oldest
}

private void RunMSBuild(string solutionFilename, int vsVersionYear, string arguments = null)
{
    //get Visual Studio instances
    List<VisualStudioInstance> vsInstances = MSBuildLocator.QueryVisualStudioInstances().OrderBy(x => x.Version).ToList();

    if (vsInstances != null && vsInstances.Count > 0)
    { 
        //get MSBuild path
        var msBuildPath = vsInstances.Where(x => x.Name.EndsWith(vsVersionYear.ToString())).Select(x => x.MSBuildPath).FirstOrDefault();

        RunMSBuild(solutionFilename, msBuildPath, arguments);
    }
}

private void RunMSBuild(string solutionFilename, VSVersionType vsVersion, string arguments = null)
{
    //get Visual Studio instances
    List<VisualStudioInstance> vsInstances = MSBuildLocator.QueryVisualStudioInstances().OrderBy(x => x.Version).ToList();

    if (vsInstances != null && vsInstances.Count > 0)
    {
        string msBuildPath = string.Empty;
        if (vsVersion == VSVersionType.Latest)
            msBuildPath = vsInstances[vsInstances.Count - 1].MSBuildPath;
        else if (vsVersion == VSVersionType.Oldest)
            msBuildPath = vsInstances[0].MSBuildPath;

        RunMSBuild(solutionFilename, msBuildPath, arguments);
    }
}

private void RunMSBuild(string solutionFilename, string msBuildPath, string arguments = null)
{
    //get tools path for newest Visual Studio version
    string msBuildFilename = Path.Combine(msBuildPath, "MSBuild.exe");

    if (!File.Exists(msBuildFilename))
        throw new Exception($"Error: MSBuild.exe not found ({msBuildFilename})");

    ProcessStartInfo startInfo = new ProcessStartInfo(msBuildFilename)
    {
        CreateNoWindow = true,
        RedirectStandardError = true,
        RedirectStandardOutput = true,
        UseShellExecute = false,
        WindowStyle = ProcessWindowStyle.Hidden
    };

    //set value
    startInfo.Arguments = arguments;

    if (!String.IsNullOrEmpty(arguments))
    {
        if (!arguments.Contains(Path.GetFileNameWithoutExtension(solutionFilename)))
        {
            arguments += $" \"{solutionFilename}\"";
        }
    }
    else
    {
        arguments = $" \"{solutionFilename}\"";
    }

    Debug.WriteLine($"arguments: {arguments}");

    //set value
    startInfo.WorkingDirectory = Path.GetDirectoryName(solutionFilename);

    using (Process p = new Process() { StartInfo = startInfo, EnableRaisingEvents = true })
    {
        //subscribe to event and add event handler code
        p.ErrorDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code 
                Debug.WriteLine("Error: " + e.Data);
            }
        };

        //subscribe to event and add event handler code
        p.OutputDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code
                Debug.WriteLine("Output: " + e.Data);
            }
        };

        //start
        p.Start();

        p.BeginErrorReadLine(); //begin async reading for standard error
        p.BeginOutputReadLine(); //begin async reading for standard output

        //waits until the process is finished before continuing
        p.WaitForExit();

        p.CancelErrorRead(); //cancel async reading for standard error
        p.CancelOutputRead(); //cancel async reading for standard output
    }
}

Использование 1:

string solutionFilename  = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
RunMSBuild(solutionFilename, 2017);

Использование 2:

string solutionFilename  = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
RunMSBuild(solutionFilename, 2017, "-t:Clean;Compile");

Использование 3:

string solutionFilename = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
RunMSBuild(solutionFilename, VSVersionType.Oldest);

Использование 4:

string solutionFilename = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
RunMSBuild(solutionFilename, VSVersionType.Oldest, "-t:Clean;Compile");

Использование 5:

string msBuildPath = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.ProgramFilesX86)), @"Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin");
string solutionFilename = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
RunMSBuild(solutionFilename, msBuildPath);

Использование 6:

string msBuildPath = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.ProgramFilesX86)), @"Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin");
string solutionFilename = Path.Combine(Path.Combine(GetFolderPath(SpecialFolder.MyDocuments), "Visual Studio 2017", "Projects", "MyAmazingApp", "MyAmazingApp.sln"));
RunMSBuild(solutionFilename, msBuildPath, "-t:Clean;Compile"););

Примечание. При использовании Developer Command Prompt для установки переменных среды используется пакетный файл («VsDevCmd.bat»). Это можно увидеть по:

  • Нажмите Windows Start menu
  • Щелкните правой кнопкой мыши Developer Command Prompt...
  • Выберите More
  • Выберите Open file location

В открывшейся папке:

  • Щелкните правой кнопкой мыши ссылку Developer Command Prompt....
  • Выберите Properties
  • Выберите вкладку Shortcut

Посмотрите на значение Target, которое показывает пакетный файл, который используется для установки переменных среды.


Ресурсы:

Дополнительные ресурсы:


Я настоятельно рекомендую использовать пакет Microsoft.Build.Locator, даже если вы планируете создать новый процесс и не встраивать механизм сборки.

Jonathan Dodds 10.02.2023 00:04

@JonathanDodds: я не был знаком с Microsoft.Build.Locator. После просмотра предоставленного URL-адреса я обновил код.

user09938 10.02.2023 03:46

То, что я пытаюсь сделать, это запустить t:scmclean внутри папки, D:/Git/abc/build/tools с пропуском msbuid.exe , который я должен получить из системного пути, установленного для msbuil.exe из консольное приложение С#. Может быть, я не могу объяснить, но все, что мне нужно сделать, это перейти в каталог и запустить t:scmclean , который отлично работает из командной строки, но не через мой код С#.

madhurima 10.02.2023 08:17

@madhurima Переключатель /t (или /target) указывает набор целей для запуска в указанном файле проекта. Если файл проекта не указан (как в командной строке), MSBuild ищет в текущем рабочем каталоге файл с расширением, которое заканчивается на proj (например, в проектах C# используется .csproj). Гарантированно ли D:/Git/abc/Build/Tools содержит файл *.*proj? Вы получаете сообщение об ошибке, потому что файл не существует?

Jonathan Dodds 10.02.2023 14:32

@madhurima: я не знаю, что такое t:scmclean — я не смог найти никакой информации об этом. Однако я обновил сообщение, включив в него использование с пакетом NuGet CliWrap. Весь код в посте проверен.

user09938 10.02.2023 17:15

@ user09938 /t:scmclean работает с пользовательской письменной целью с именем scmclean (я полагаю, это «scm clean»).

Jonathan Dodds 12.02.2023 17:56

Другие вопросы по теме