简体   繁体   中英

Building msbuild 15 project programmatically

I'm trying to build a simple C# 7 class library project created with VS2017.

MSBuild from framework assemblies is outdated, so I'm referencing Microsoft.Build , Microsoft.Build.Engine and Microsoft.Build.Framework from MSBuild folder within visual studio ( C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\MSBuild\\15.0\\Bin ).

Still, when I do this:

using (var collection = new ProjectCollection())
{
    var proj = collection.LoadProject(@"c:\projects\Sample\Sample.csproj"); // <-- exception
    proj.Build(new[] {new ConsoleLogger()});
}

I'm getting InvalidProjectFileException: The tools version "15.0" is unrecognized. Available tools versions are "4.0", "2.0". InvalidProjectFileException: The tools version "15.0" is unrecognized. Available tools versions are "4.0", "2.0".

Is there programmatic way to invoke build using the latest build tools and C# 7 compiler?

I had similar needs for my team and I wrote a Builder library for C# that supports several versions of Visual Studio. I could not make the Project.Build function work properly, so I went for executing MsBuild.exe directly.

How I built it:

Use Microsoft.Build.Framework from NuGet

Create a new Project object with 1 target called Build

Define the right ToolsVersion

Of the Project according to the Visual Studio version:

  • 2010, 2012 => 4.0
  • 2013 => 12.0
  • 2015 => 14.0
  • 2017 => 15.0

Add a new task of type MsBuild

With Projects property containing all the project I need to build

Define the ToolsVersion

of the MsBuild task with same value as for the Project

Serialize the Project into a temporary file

Find the right MsBuild.exe according to the ToolsVersion

4.0, 12.0, 14.0

Found in the registry:

Registry.LocalMachine.OpenSubKey($@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\{msBuildVersion}")

15.0

Not anymore in the registry, you need to use the Nuget package Microsoft.VisualStudio.Setup.Configuration.Interop

        var query = new SetupConfiguration();

        var query2 = (ISetupConfiguration2)query;

        var e = query2.EnumAllInstances();

        var helper = (ISetupHelper)query;

        int fetched;

        var instances = new ISetupInstance[1];

        do
        {
            e.Next(1, instances, out fetched);
            if (fetched > 0)
            {
                var instance = instances[0];

                var instance2 = (ISetupInstance2)instance;

                var state = instance2.GetState();

                // Skip non-complete instance, I guess?
                // Skip non-local instance, I guess?
                // Skip unregistered products?
                if (state != InstanceState.Complete
                    || (state & InstanceState.Local) != InstanceState.Local
                    || (state & InstanceState.Registered) != InstanceState.Registered)
                {
                    continue;
                }

                var msBuildComponent =
                    instance2.GetPackages()
                        .FirstOrDefault(
                            p =>
                                p.GetId()
                                    .Equals("Microsoft.Component.MSBuild",
                                        StringComparison.InvariantCultureIgnoreCase));

                if (msBuildComponent == null)
                {
                    continue;
                }

                var instanceRootDirectory = instance2.GetInstallationPath();

                var msbuildPathInInstance = Path.Combine(instanceRootDirectory, "MSBuild", msBuildVersion, "Bin", "msbuild.exe");

                if (File.Exists(msbuildPathInInstance))
                {
                    return msbuildPathInInstance;
                }
            }
        } while (fetched > 0);

Execute MsBuild.exe

And build the serialized Project using a custom XML Logger - you can use the one provided by MsBuildExtensionPack

Read the result summary

Deserialize the result summary from Xml and use it to determine if build failed or not, which errors and warnings occurred etc.

If you just need the path to the latest MSBuild.exe, use Microsoft.Build.Utilities.Core from Nuget and use the following code:

ToolLocationHelper.GetPathToBuildToolsFile("msbuild.exe", ToolLocationHelper.CurrentToolsVersion);

This works whether just Build Tools are installed or the full Visual Studio installation.

I had exactly the same issues in my project.
In my case I want to build an SSDT project programmatically but I tried other project types as well.

Interestingly, it worked perfectly fine in build 26228.04 of VS2017 (which was the Release build) and stopped working in build 26228.09.
Yesterday build 26228.10 was released, so I decided to give it another go.

Surprisingly the following worked for me:

  1. Update to the latest VS2017 build 26228.10 using the Visual Studio Installer.
  2. I was never able to get collection.LoadProject(...) to work without getting some weird error but you can use this code to do the build:

     BuildResult result = null; using (var pc = new ProjectCollection()) result = BuildManager.DefaultBuildManager.Build( new BuildParameters(pc) { Loggers = new[] { new ConsoleLogger() } }, // Change this path to your .sln file instead of the .csproj. // (It won't work with the .csproj.) new BuildRequestData(@"c:\\projects\\Sample.sln", // Change the parameters as you need them, // eg if you want to just Build the Debug (not Rebuild the Release). new Dictionary<string, string> { { "Configuration", "Release" }, { "Platform", "Any CPU" } }, null, new[] { "Rebuild" }, null)); if (result.OverallResult == BuildResultCode.Failure) // Something bad happened... 
  3. Ensure that you have copied all the MSBuild assembly binding redirections to your .config file.
    If you haven't already done this, open the file C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\MSBuild\\15.0\\Bin\\MSBuild.exe.config and copy the whole <runtime> element to the <configuration> element of your own .config file in the project that uses MSBuild programmatically.
    If you don't redirect the MSBuild assembly versions, you will get some pretty strange error messages from MSBuild which will vary based on your MSBuild version.
    But it's sure to say: It will definitely not work without the assembly binding redirections.

  4. One additional step if you want to build an SSDT project:
    Change the following two lines in your .sqlproj file from

     <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion> 

    to

     <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(SSDTExists)' == ''">15.0</VisualStudioVersion> 

I'm not 100% sure why, but these steps worked perfectly for me!
But because MSBuild is a bit "difficult" to use programmatically (in my opinion it's sometimes as stable as a weather forecast), chances are that my approach won't work in your case.

This is already solved in a PreRelease version ( 15.5.0-preview-000072-0942130 ) of msbuild, see MsBuild issue #2369: msbuild nuget package unable to open vs2017 csproj files . So in future no further hacks needed

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM