microsoft/MSBuildLocator

Trying to load a project with Sdk.WindowDesktop fails.

vpenades opened this issue · 3 comments

I've been trying to load a csproj using new Microsoft.Build.Evaluation.Project("project.csproj", null, null"); and it throws this exception:

The specified SDK "Microsoft.NET.Sdk.WindowsDesktop" could not be found.
Source: Microsoft.Build
Error: MSB4236
MSBuild.CouldNotResolveSdk

The loaded csproj looks like this:

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

  <PropertyGroup>
    <TargetFramework>net471</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

And the host application registers the MSBuild location with:

Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults();

I've noticed different behavior depending on whether the host application is compiled against Net472 or against NetCoreApp31:

On Net472

  • The Microsoft.NET.Sdk.WindowsDesktop projects succeeds to load.
  • MSBuildPath of Locator Instance points to: "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin"

On NetCoreApp3.1

  • The Microsoft.NET.Sdk.WindowsDesktop projects throw an exception on load.
  • MSBuildPath of Locator instance points to: "C:\Program Files\dotnet\sdk\2.2.300"
    On Net4xx:

So I believe this is the reason because it works on Net4 and not on NetCore.

Is there a way to solve this issue and make the "Microsoft.NET.Sdk.WindowsDesktop" projects to load while the host app is NetCoreApp?

This is not a particularly nice workaround because it makes the library pretty pointless, as you have to tell the "locator" library exactly where to look, but you can do this from a .NET Core app (on Windows), and it will be able to load both .NET Core and .NET Framework projects:

MSBuildLocator.RegisterMSBuildPath(@"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin");

Seems that DiscoveryType.DeveloperConsole and DiscoveryType.VisualStudioSetup only work on the .NET Framework version of this package, and DiscoveryType.DotNetSdk only works on the .NET Core version of this package, so this returns completely different results depending on the target framework of the project that is referencing the package:

foreach (var instance in MSBuildLocator.QueryVisualStudioInstances(
    new VisualStudioInstanceQueryOptions
    {
        DiscoveryTypes = DiscoveryType.DeveloperConsole | DiscoveryType.DotNetSdk | DiscoveryType.VisualStudioSetup
    }))
{
    Console.WriteLine($"{instance.DiscoveryType} {instance.MSBuildPath}");
}

But if you take the paths from either of them and just pass them to MSBuildLocator.RegisterMSBuildPath, they work in the other, so it's a bit annoying that it doesn't just find all the same paths regardless of target framework. You can specify DiscoveryType but it's mostly pointless, because it ignores it when finding them, and then only uses it to throw away results afterwards.

The problem with that approach is that our project runs on different environments, and it's not guaranteed the path to pass to RegisterMSBuildPath is the same on all machines (specially those with different versions of VS)

Yes, it's not ideal, and I'm not making any excuses for this library. I'm just a consumer of this library, and I'm sharing my workaround.

Exactly how you get the path to the directory containing msbuild will depend on your environments. For my usage, I can just pass it in as an argument. You might be able to search your path environment variables, or use where.exe msbuild to find it, or save it to an environment variable on the machine, or you might be able to do something like this:

var msbuildDirectories = new[]
    {
        @"C:\Program Files (x86)\Microsoft Visual Studio",
        @"C:\Program Files\Microsoft Visual Studio"
    }
    .Where(Directory.Exists)
    .SelectMany(path => Directory.EnumerateFiles(path, "msbuild.exe", SearchOption.AllDirectories))
    .Select(Path.GetDirectoryName);

Two overloads exist:

  • MSBuildLocator.RegisterMSBuildPath(string)
  • MSBuildLocator.RegisterMSBuildPath(string[])

So you could either .ToArray() this or .First() it or .Last() it etc.