Reputation: 2979
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".
Is there programmatic way to invoke build using the latest build tools and C# 7 compiler?
Upvotes: 15
Views: 8699
Reputation: 2488
This is easier now, since Microsoft has released their own package.
Set up package references as described here. Note the ExcludeAssets="runtime"
on all packages except for the Locator package:
<PackageReference Include="Microsoft.Build.Locator" Version="1.4.1" />
<PackageReference Include="Microsoft.Build" Version="16.10.0" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="16.10.0" ExcludeAssets="runtime" />
public Init()
{
MSBuildLocator.RegisterDefaults();
var project = LoadProject(@"C:\Path\To\Project.csproj");
}
public Project LoadProject(string csprojPath)
{
var context = new ProjectCollection();
return context.LoadProject(csprojPath);
}
According to the documentation link above, the call to the Microsoft.Build assemblies needs to happen in a different method than the MSBuildLocator.RegisterDefaults()
.
Upvotes: 0
Reputation: 21
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
Upvotes: 2
Reputation: 2407
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.
Upvotes: 4
Reputation: 3231
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:
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,
// e.g. 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...
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.
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.
Upvotes: 4
Reputation: 1330
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:
Of the Project according to the Visual Studio version:
With Projects property containing all the project I need to build
of the MsBuild task with same value as for the Project
Found in the registry:
Registry.LocalMachine.OpenSubKey($@"SOFTWARE\Microsoft\MSBuild\ToolsVersions\{msBuildVersion}")
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);
And build the serialized Project using a custom XML Logger - you can use the one provided by MsBuildExtensionPack
Deserialize the result summary from Xml and use it to determine if build failed or not, which errors and warnings occurred etc.
Upvotes: 4