I am trying to run MSBuild programmatically from a C# DLL (which will ultimately be loaded from PowerShell), and as a first step from a command-line application. I have used Microsoft.Build.Locator as recommended (or so I reckon) by installing its NuGet package to my project, and adding the following references to my test project:
<Reference Include="Microsoft.Build, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.Build.Framework, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<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>
The project targets .NET Framework 4.8, and the source code is as follows:
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Locator;
using System.Collections.Generic;
namespace nrm_testing
{
class Program
{
static void Main(string[] args)
{
MSBuildLocator.RegisterDefaults();
DoStuff();
}
static void DoStuff()
{
using (var projectCollection = new ProjectCollection())
{
var buildParameters = new BuildParameters
{
MaxNodeCount = 1 // https://stackoverflow.com/q/62658963/3233393
};
var buildRequestData = new BuildRequestData(
@"path\to\a\project.vcxproj",
new Dictionary<string, string>(),
null,
new string[0],
null
);
var result = BuildManager.DefaultBuildManager.Build(buildParameters, buildRequestData);
}
}
}
}
Upon entering the using
block, I receive the following exception:
System.IO.FileNotFoundException: 'Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.'
The Modules window shows that MSBL did successfully locate my VS2019 installation:
Microsoft.Build.dll 16.07.0.37604 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.dll
Microsoft.Build.Framework.dll 16.07.0.37604 C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.Framework.dll
Microsoft.Build.Locator.dll 1.02.6.49918 C:\dev\nrm3-tests\nrm\nrm-testing\.out\AnyCPU-Debug\Microsoft.Build.Locator.dll
System.Runtime.CompilerServices.Unsafe.dll
is indeed present besides the located MSBuild assemblies, in version 4.0.6.0 (according to DotPeek).
What could be causing this error, and how could I fix it?
My attempts so far:
I have found this question, but the linked GitHub issue is still open and I'm unsure whether it's the same problem.
I have managed to get a binding redirection working, but I don't think I can use them from within a DLL, so that's a dead end.
Adding the
System.Runtime.CompilerServices.Unsafe
NuGet package to the project (and verifying that it is indeed copied alongside the project's executable) does nothing (thanks magicandre1981 for the suggestion).Switching from packages.config to PackageReference (as suggested by Perry Qian), with no change in behaviour.
After a lot of fiddling with different ideas, I ended up writing this workaround based on manual assembly resolution.
RegisterMSBuildAssemblyPath
detects whenMicrosoft.Build.dll
gets loaded, and memorizes its directory. Upon subsequent assembly load failures,RedirectMSBuildAssemblies
checks if the missing assembly exists inside that path, and loads it if it does.I'm pretty sure there are (many) corner cases that will make this fail, but it will do for now.