1

I am trying to run msbuild /t:scmclean from my C# console application but failing to. This completely works fine if i run it from my Visual studio command prompt 2017 but fails while executing from my C# console application.

My Target folder where I am running this command is : D:/Git/abc/Build/Tools, this command internally calls csproj file.

What I have tried:

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);

This gives an error access denied, however when I check the user using the command

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

it shows as super user. Another thing that I tried is running after setting the location to my current directory is

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

this also seems to be failing. Also I need to know how to write the output of a process in console.

MSBUILD : error MSB1003: Specify a project or solution file. The current working directory does not contain a project or solution file.

0

1 Answer 1

2

I'm not sure what scmclean is because I couldn't find any information about it. Nevertheless, try the following:

Option 1 (CliWrap)

Download/install NuGet packages:

  • Microsoft.Build.Locator
  • CliWrap

Add the following using directives (ex: Form1.cs)

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

Choose one of the options below (ie: Option A, Option B, Option C, Option D, or Option E):

Option 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);
}

Option B - 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);
}

Option C - ExecuteAsync (Delegate):

Note: This option displays real-time output.

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();
}

Option D - ExecuteAsync (Action/Delegate):

Note: This option displays real-time output.

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();
}

Option E - ExecuteAsync (EventStream):

Note: This option displays real-time output.

Download/install NuGet package: System.Reactive.Linq

Add using directives:

  • 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;
        }
    });
}

(Optional) Add the following method overloads:

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);
    }
}

Usage 1:

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

Usage 2:

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

Usage 3:

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

Usage 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");

Usage 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);

Usage 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"););

Option 2 (System.Diagnostics.Process)

Download/install NuGet package: Microsoft.Build.Locator

Add the following using directives (ex: 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
    }
}

Usage 1:

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

Usage 2:

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

Usage 3:

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

Usage 4:

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

Usage 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);

Usage 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"););

Note: When using the Developer Command Prompt, a batch file ("VsDevCmd.bat") is used to set environment variables. One can see this by:

  • Click Windows Start menu
  • Right-click Developer Command Prompt...
  • Select More
  • Select Open file location

In the folder that opens:

  • Right-click Developer Command Prompt... link.
  • Select Properties
  • Select Shortcut tab

Look at value of Target which shows the batch file that is used to set the environment variables.


Resources:

Additional Resources:


Sign up to request clarification or add additional context in comments.

6 Comments

I would strongly advise using the Microsoft.Build.Locator package even if you plan to spawn a new process and not embed the build engine.
@JonathanDodds: I wasn't familiar with Microsoft.Build.Locator. After reviewing the URL provided, I updated the code.
What i am trying to do is run t:scmclean inside the folder, D:/Git/abc/build/tools with the pass of msbuid.exe , which i should be able to get from the system path set for msbuil.exe from the c# console application. May be i am failing to explain but all i need to do is go to the directory and run t:scmclean , which works fine from command line but not through my c# code
@madhurima The /t (or /target) switch specifies a set of targets to run in the specified project file. When no project file is specified (as in your command line), MSBuild searches the current working directory for a file with a file extension that ends in proj (e.g. C# projects use .csproj). Is D:/Git/abc/Build/Tools guaranteed to contain a *.*proj file? Are you getting an error because the file doesn't exist?
@user09938 /t:scmclean is running a custom written target named scmclean (which I assume is 'scm clean').
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.