"Internal MSBuild Error" from nupkg "Microsoft.Build"

55 views Asked by At

I am trying to use the Microsoft.Build nuget package to:

  • open a solution
  • enumerate projects in it
  • discover the DLLs that are created.

<rant>Failure modes seem to be super-abundant, error messages are uninformative, documentation seems to be scarce and misdirected, and examples are outdated. After hours upon hours of troubleshooting in the dark, and solving issue after issue that popped up, I finally arrived at an "Internal MSBuild Error", which brings me to Stack Overflow.</rant>

My "scratch" solution contains just one net8.0 project, as follows:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Build" Version="17.9.5" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.9.5" />
  </ItemGroup>

</Project>

This project contains just one source file, as follows:

    namespace ConsoleApp1;

    using System;
    using System.IO;
    using System.Collections.Generic;
    using Microsoft.Build.Construction;
    using Microsoft.Build.Definition;
    using Microsoft.Build.Evaluation;
    using Microsoft.Build.Evaluation.Context;

    class Program
    {
        static void Main( string[] args )
        {
            Directory.SetCurrentDirectory( @"C:\" ); //ensure that the error we encounter further down is not due to the current directory
            msBuildApiTest( @"D:\Personal\MyVisualStudioSolution\Solution.sln" ); // <-- this fails
            msBuildApiTest( @"D:\Personal\scratch\scratch.sln" ); // <-- this would also fail
        }

        static void msBuildApiTest( string solutionFilePath )
        {
            string msBuildExtensionsPath = @"C:\Program Files\dotnet\sdk\8.0.102";
            string msBuildSdksPath = Path.Combine( msBuildExtensionsPath, "Sdks" );

            Environment.SetEnvironmentVariable( "MSBuildSDKsPath", msBuildSdksPath ); //Prevents InvalidProjectFileException: The SDK 'Microsoft.NET.Sdk' specified could not be found.
            Environment.SetEnvironmentVariable( "MSBuildEnableWorkloadResolver", "false" ); //Prevents InvalidProjectFileException: The SDK 'Microsoft.NET.SDK.WorkloadAutoImportPropsLocator' specified could not be found.

            ProjectOptions projectOptions = new();
            projectOptions.EvaluationContext = EvaluationContext.Create( EvaluationContext.SharingPolicy.Shared );
            projectOptions.LoadSettings = ProjectLoadSettings.DoNotEvaluateElementsWithFalseCondition;
            projectOptions.GlobalProperties = new Dictionary<string, string>();
            projectOptions.GlobalProperties.Add( "SolutionDir", Path.GetDirectoryName( solutionFilePath ) + "\\" ); //The trailing backslash is OF PARAMOUNT IMPORTANCE.
            projectOptions.GlobalProperties.Add( "MSBuildExtensionsPath", msBuildExtensionsPath ); //Prevents InvalidProjectFileException: The imported project "D:\Personal\scratch\ConsoleApp1\bin\Debug\net8.0\Current\Microsoft.Common.props" was not found.
            ProjectCollection projectCollection = new( ToolsetDefinitionLocations.Default );
            
            SolutionFile solutionFile = SolutionFile.Parse( solutionFilePath );
            foreach( ProjectInSolution projectInSolution in solutionFile.ProjectsInOrder )
            {
                if( projectInSolution.ProjectType is SolutionProjectType.SolutionFolder or SolutionProjectType.SharedProject )
                    continue;
                Console.WriteLine( $"{projectInSolution.ProjectType}\t{projectInSolution.ProjectName}\t{projectInSolution.RelativePath}" );

                Project project1 = Project.FromFile( projectInSolution.AbsolutePath, projectOptions ); // <-- this fails
                Project project2 = new( projectInSolution.AbsolutePath, projectOptions.GlobalProperties, "Current", projectCollection ); // <-- this would also fail
            }
        }
    }

Either of the last two statements fails.

The exception is a Microsoft.Build.Exceptions.InvalidProjectFileException.

The message of the exception is as follows:

The expression "[MSBuild]::GetTargetFrameworkIdentifier(net8.0)" cannot be evaluated. MSB0001: Internal MSBuild Error: A required NuGet assembly was not found. Expected Path: D:\Personal\scratch\ConsoleApp1\bin\Debug\net8.0 C:\Program Files\dotnet\sdk\8.0.102\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.TargetFrameworkInference.targets

Notes:

  • The "Expected Path" (whatever that means) points to the output directory of my scratch solution, which makes no sense, because that's not the solution I am trying to parse, and I have not supplied Microsoft.Build with any path to my scratch solution. As a matter of fact, I even switch the current directory to C:\ so as to make sure that the error is not due to that.

  • The @"C:\Program Files\dotnet\sdk\8.0.102" path works on my machine, you might have to change it for your machine. Automatic discovery would be nice, but it is beyond the scope of this scratch app.

  • The solution at "D:\Personal\MyVisualStudioSolution\Solution.sln" is the solution that I am trying to parse, consisting of many net8.0 C# projects and one net472 project. However, it does not matter, because I get the exact same failure when I point this simple scratch app to try to parse itself.

The question is: Why is this failing, and what must I do to make it work?

1

There are 1 answers

0
Mike Nakis On

As it turns out, most of the magical incantations that I was doing before SolutionFile.Parse() were wrong.

The "SolutionDir" part was right, but all of the other stuff had to be removed, and replaced with the following magical incantation:

  • Reference nuget package Microsoft.Build.Locator
  • Before accessing any MSBuild types, and in a separate function,*1 invoke MSBuildLocator.RegisterDefaults();

This magical incantation magically makes all the other magic right.


*1 If you invoke RegisterDefaults() and then proceed to refer to MSBuild types in the same function, you will get an amusing error message, which, luckily, is descriptive enough to tell you what you need to do to fix it. Amazing stuff.