Skip to content
Denis Kuzmin (github/3F) edited this page May 1, 2024 · 1 revision

Although MvsSln provides native implementation for most of the features, however, part of the code still (we're considering future replacement, follow the news) relies on original MS implementation.

Unfortunately modern Microsoft.Build assemblies are much more closely integrated with Visual Studio and much more difficult to maintain independently (i.e. without VS/dotnet sdk dependencies).

Today you may note the following possible problems when using our Env features (flags: Env | LoadDefaultData; or Env | LoadMinimalDefaultData).

For example:

using(var sln = new Sln(path, SlnItems.Env | SlnItems.LoadMinimalDefaultData))
{
    //...
}

It may produce the following:

Microsoft.Build.Exceptions.InvalidProjectFileException:

The imported project "<...>" was not found. Confirm that the path in the 
<Import> declaration is correct, and that the file exists on disk.

Due to import sections when loading project files:

<Import Project="..." />

For something like this: Microsoft.CSharp.targets, Microsoft.Common.props, Microsoft.Cpp.Default.props, Microsoft.Cpp.targets, ...

Or it also may produce the following:

Microsoft.Build.Exceptions.InvalidProjectFileException: 
The SDK 'Microsoft.NET.Sdk' specified could not be found.

Due to SDK-based project type:

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

Solutions for MvsSln 2.x

Microsoft.Build.Locator

This package Microsoft.Build.Locator is official solution from MS. That was based on two things:

  1. Spoofing assembly from installed instance of Visual Studio.
    1. Either Microsoft.VisualStudio.Setup.Configuration.Interop API to query installed Visual Studio instances. [?src]
    2. Or through VS dev console. [?src]
  2. (.NET Core) Manual msbuild path through MSBUILD_EXE_PATH environment variable by using available dotnet Sdk paths.

Here's implementation: https://github.com/microsoft/MSBuildLocator/blob/87bebc8b2014f0b52110cf46500d805bba3e072c/src/MSBuildLocator/MSBuildLocator.cs#L114

The first method is the most problematic due to isolation and actually spoofing the assembly.

We will never provide something like this together with MvsSln! Because it will require patching on your side anyway, like below.

Thus, just add this:

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

Before using MvsSln, for example:

// your assembly

MSBuildLocator.RegisterDefaults();

// ... before MvsSln

using(var sln = new Sln(path, SlnItems.Env | SlnItems.LoadMinimalDefaultData))
{
    // your assembly will be fixed via the first or the second way in MSBuildLocator
}

MSBUILD_EXE_PATH environment variable

Microsoft.Build.Locator above already implements this way, however, you can try to use it manually. Just add this:

System.Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", "fullpath to msbuild dll with env");

Before using MvsSln, for example:

System.Environment.SetEnvironmentVariable(
    "MSBUILD_EXE_PATH", 
    @"C:\Program Files\dotnet\sdk\3.0.100\MSBuild.dll", 
    EnvironmentVariableTarget.Process
);

using(var sln = new Sln(@"D:\tmp\_Issues\MvsSln\PkgRef\ConsoleApp1\ConsoleApp1.sln", SlnItems.Env | SlnItems.LoadMinimalDefaultData)) {
    //...
}

Microsoft.NET.Sdk. Why does it work?

  1. MSBUILD_EXE_PATH feature is already implemented by MSBuild inside for work in other environment like dotnet sdk or Visual Studio.
  2. dotnet SDK conatins Sdk Resolver (Microsoft.Build.NuGetSdkResolver.dll) that will be used due to:
<!-- SdkResolvers\Microsoft.Build.NuGetSdkResolver\Microsoft.Build.NuGetSdkResolver.xml -->
<SdkResolver>
  <Path>..\..\Microsoft.Build.NuGetSdkResolver.dll</Path>
</SdkResolver>

This env finally helps to resolve SDK-based project type, and now you can process it:

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

And more because of other environment such as dotnet sdk etc.

Manual detecting full path to dotnet sdk

Here's official MS way: https://github.com/microsoft/MSBuildLocator/blob/87bebc8b2014f0b52110cf46500d805bba3e072c/src/MSBuildLocator/DotNetSdkLocationHelper.cs#L61

dotnet --list-sdks

1.0.4 [C:\Program Files\dotnet\sdk]
1.1.0 [C:\Program Files\dotnet\sdk]
2.0.2 [C:\Program Files\dotnet\sdk]
2.0.3 [C:\Program Files\dotnet\sdk]
2.1.2 [C:\Program Files\dotnet\sdk]
2.1.4 [C:\Program Files\dotnet\sdk]
...

MSBuildLocator: MSBuild assemblies were already loaded

System.InvalidOperationException: 
'Microsoft.Build.Locator.MSBuildLocator.RegisterInstance was called, but MSBuild assemblies were already loaded.

Firstly, ensure that RegisterDefaults is called before any MvsSln or MSBuild use:

MSBuildLocator.RegisterDefaults();

// ... vvvv before any use

new Sln(path, SlnItems.All)
new Project()

If not, try add reference to MSBuild assembly in your project (only at build time without copying to the output directory).

For PackageReference, use ExcludeAssets="runtime":

<PackageReference Include="MvsSln" Version="2.5.0" />
<PackageReference Include="Microsoft.Build" Version="16.3.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />

For Reference, use <Private>False</Private>:

<Reference Include="MvsSln, Version=2.5.0.0, Culture=neutral, PublicKeyToken=87f0bd8fb7f0a2c4, processorArchitecture=MSIL">
    <HintPath>..\packages\MvsSln.2.5.0\lib\net472\MvsSln.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
    <HintPath>..\packages\Microsoft.Build.16.3.0\lib\net472\Microsoft.Build.dll</HintPath>
    <Private>False</Private>
</Reference>
<Reference Include="Microsoft.Build.Locator, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9dff12846e04bfbd, processorArchitecture=MSIL">
    <HintPath>..\packages\Microsoft.Build.Locator.1.2.6\lib\net46\Microsoft.Build.Locator.dll</HintPath>
</Reference>

Or GAC if netfx:

<Reference Include="Microsoft.Build" />

Examples

<PackageReference Include="MvsSln" Version="2.5.0" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" />
<PackageReference Include="Microsoft.Build" Version="16.3.0" ExcludeAssets="runtime" />
Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults(); //must be invoked BEFORE use of MvsSln
// ...
using(var sln = new Sln(path, SlnItems.All)) { ... }

If you will see the error:

Microsoft.Build.Shared.InternalErrorException: 
'MSB0001: Internal MSBuild Error: Type information for Microsoft.Build.Utilities.ToolLocationHelper 
was present in the whitelist cache as Microsoft.Build.Utilities.ToolLocationHelper'

Try add also Microsoft.Build.Utilities.Core:

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

Please note: PackageReference samples are fully compatible to Reference type of items.

Custom implementation of project instances

MvsSln also provides related virtual methods for your convenience.

As the real example, the .NET DllExport project uses this way to avoid some problems when loading unsupported project types:

That is, you can easily try to override actual loading with any custom logic as you need. For example:

+Microsoft.Build.dll //  ~ it could be custom reference in your env

using net.r_eg.MvsSln;
using net.r_eg.MvsSln.Core;
...

public class MyEnv: IsolatedEnv, IEnvironment, IDisposable
{
    public MyEnv(ISlnResult data)
        : base(data)
    {

    }

    protected override Microsoft.Build.Evaluation.Project Load(string path, IDictionary<string, string> properties)
    {
        return base.Load(path, properties); //TODO: your awesome logic 
    }
    
    protected override void Dispose(bool disposing) => base.Dispose(disposing);
}
...

Then, for example:

using(var sln = new Sln(file, SlnItems.Projects
                                | SlnItems.SolutionConfPlatforms
                                | SlnItems.ProjectConfPlatforms))
{
    IEnvironment env = new MyEnv(sln.Result);
    env.LoadMinimalProjects();
    ...

    sln.Result.ProjectItems.Where(p => p.path == "special\\projectfile.csproj");
    ...
}

Note: 2.4 introduces XProjectEnv (splitted IsolatedEnv as the base but without IDisposable) [?]