Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
make the MSbuild runtime assembly check more correct
  • Loading branch information
baronfel committed Nov 14, 2025
commit f43ae4a423c687d1303f841be0147cf402991ebf
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ That additional build logic is distributed with Visual Studio, with Visual Studi

Loading MSBuild from Visual Studio also ensures that your application gets the same view of projects as `MSBuild.exe`, `dotnet build`, or Visual Studio, including bug fixes, feature additions, and performance improvements that may come from a newer MSBuild release.

## How Locator searches for .NET SDK?
## How does Locator search for the .NET SDK?

MSBuild.Locator searches for the locally installed SDK based on the following priority:

Expand Down
14 changes: 6 additions & 8 deletions src/MSBuildLocator/Microsoft.Build.Locator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Title>MSBuild Locator</Title>
<Description>Package that assists in locating and using a copy of MSBuild installed as part of Visual Studio 2017 or higher or .NET Core SDK 2.1 or higher.</Description>
<PackageTags>msbuildlocator;locator;buildlocator</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>1.6.1</PackageValidationBaselineVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand All @@ -25,14 +26,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="MicroBuild.Core" Version="0.3.0" PrivateAssets="all" />
<Content Include="build\Microsoft.Build.Locator.props">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<PackagePath>build\</PackagePath>
</Content>
<Content Include="build\Microsoft.Build.Locator.targets">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<PackagePath>build\</PackagePath>
</Content>
<None Include="README.md" Pack="true" PackagePath="\" />
<None Include="build\Microsoft.Build.Locator.props" Pack="true" PackagePath="build\" />
<None Include="build\Microsoft.Build.Locator.targets" Pack="true" PackagePath="build\" />
<None Include="build\Microsoft.Build.Locator.props" Pack="true" PackagePath="buildTransitive/" />
<None Include="build\Microsoft.Build.Locator.targets" Pack="true" PackagePath="buildTransitive/" />
</ItemGroup>
<ItemGroup>
<FilesToSign Include="$(OutDir)\Microsoft.Build.Locator.dll">
Expand Down
57 changes: 57 additions & 0 deletions src/MSBuildLocator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Microsoft.Build.Locator

Microsoft.Build.Locator helps you locate and register MSBuild assemblies provided with Visual Studio or the .NET SDK. This is essential when you need to use MSBuild APIs in your application to evaluate or build projects.

## Why do I need this?

When using MSBuild's .NET API to load and build projects, you need access to the SDKs and build logic distributed with Visual Studio or the .NET SDK, not just the MSBuild APIs. MSBuildLocator helps you find these installations and set up your application to use them, ensuring your code gets the same view of projects as `MSBuild.exe`, `dotnet build`, or Visual Studio.

## Quick Start

Before using any MSBuild APIs, register MSBuild assemblies:

```csharp
using Microsoft.Build.Locator;
using Microsoft.Build.Evaluation;

// Register defaults before using any MSBuild types
MSBuildLocator.RegisterDefaults();

// Now you can safely use MSBuild APIs.
// NOTE: due the the way that the CLR loads assemblies, you MUST
// register MSBuild through Locator before any types from
// the MSBuild assemblies are used in your application.
// The safest way to ensure this is to put any MSBuild API
// access into a separate method.
LoadProject();

void LoadProject()
{
var project = new Project("MyProject.csproj");
...
}
```

For more control over which MSBuild instance to use:

```csharp
using Microsoft.Build.Locator;

// Query available MSBuild instances
var instances = MSBuildLocator.QueryVisualStudioInstances().ToArray();

// Register a specific instance
var instance = instances.OrderByDescending(i => i.Version).First();
MSBuildLocator.RegisterInstance(instance);
```

## Documentation

For complete documentation, see [Use Microsoft.Build.Locator](https://learn.microsoft.com/visualstudio/msbuild/updating-an-existing-application#use-microsoftbuildlocator) on Microsoft Learn.

## Samples

See the [BuilderApp](https://github.com/microsoft/MSBuildLocator/blob/a349ee7ffd889cd7634d3fd8b413bf9f29244b50/samples/BuilderApp) sample for a full
exploration of the MSBuildLocator library and capabilities.


37 changes: 18 additions & 19 deletions src/MSBuildLocator/build/Microsoft.Build.Locator.targets
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="EnsureMSBuildAssembliesNotCopied" AfterTargets="Build" Condition="'$(DisableMSBuildAssemblyCopyCheck)' != 'true'">
<Target Name="EnsureMSBuildAssembliesNotCopied" AfterTargets="ResolvePackageAssets" Condition="'$(DisableMSBuildAssemblyCopyCheck)' != 'true'">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResolvePackageAssets creates RuntimeCopyLocalItems for all dependencies, so we have to filter those down to just the ones that came from the MSbuild-related dependencies we want to flag.

<ItemGroup>
<MSBuildPackagesWithoutPrivateAssets
Include="@(PackageReference)"
Condition="!$([MSBuild]::ValueOrDefault('%(PackageReference.ExcludeAssets)', '').ToLower().Contains('runtime')) and
(
'%(PackageReference.Identity)' == 'Microsoft.Build' or
'%(PackageReference.Identity)' == 'Microsoft.Build.Framework' or
'%(PackageReference.Identity)' == 'Microsoft.Build.Utilities.Core' or
'%(PackageReference.Identity)' == 'Microsoft.Build.Tasks.Core' or
'%(PackageReference.Identity)' == 'Microsoft.Build.Engine' or
'%(PackageReference.Identity)' == 'Microsoft.Build.Conversion.Core' or
'%(PackageReference.Identity)' == 'Microsoft.Build.Runtime' or
'%(PackageReference.Identity)' == 'Microsoft.Build.Localization' or
'%(PackageReference.Identity)' == 'Microsoft.Build.Engine' or
'%(PackageReference.Identity)' == 'Microsoft.NET.StringTools' or
'%(PackageReference.Identity)' == 'NuGet.Frameworks'
)"/>
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Framework'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Utilities.Core'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Tasks.Core'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Engine'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Conversion.Core'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Runtime'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Localization'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.Build.Engine'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'Microsoft.NET.StringTools'))" />
<_MSBuildAssembliesCopyLocalItems Include="@(RuntimeCopyLocalItems->WithMetadataValue('NuGetPackageId', 'NuGet.Frameworks'))" />
<MSBuildAssembliesCopyLocalItems Include="@(_MSBuildAssembliesCopyLocalItems->WithMetadataValue('CopyLocal', 'true')->WithMetadataValue('AssetType', 'runtime'))" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have the candidate set, we can find just the ones that are runtime assets and were copylocal'd.

<_DistinctMSBuildPackagesReferencedPoorly Include="@(MSBuildAssembliesCopyLocalItems->Metadata('NuGetPackageId')->Distinct())" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

such assets will have the nuget package id and version metadata on them, so we can distinct the IDs easily and make nice error messages.

<_DistinctPackagesWithVersions Include="@(_DistinctMSBuildPackagesReferencedPoorly->'%(NuGetPackageId)@%(NuGetPackageVersion)')" />
</ItemGroup>

<Error
Condition="'@(MSBuildPackagesWithoutPrivateAssets)' != ''"
Text="A PackageReference to Microsoft.Build.* without ExcludeAssets=&quot;runtime&quot; exists in your project. This will cause MSBuild assemblies to be copied to your output directory, causing your application to load them at runtime. To use the copy of MSBuild registered by MSBuildLocator, set ExcludeAssets=&quot;runtime&quot; on the MSBuild PackageReferences. To disable this check, set the property DisableMSBuildAssemblyCopyCheck=true in your project file (not recommended as you must distributed all of MSBuild + associated toolset). Package(s) referenced: @(MSBuildPackagesWithoutPrivateAssets)" />
Condition="@(_DistinctPackagesWithVersions->Count()) > 0"
Text="A PackageReference to an MSBuild run-time package without ExcludeAssets=&quot;runtime&quot; exists in your project. This will cause MSBuild run-time assemblies to be copied to your output directory, causing your application to load them at runtime. To use the copy of MSBuild registered by MSBuildLocator, set ExcludeAssets=&quot;runtime&quot; on the MSBuild PackageReferences. To disable this check, set the property DisableMSBuildAssemblyCopyCheck=true in your project file (not recommended as you must distributed all of MSBuild + associated toolset). Package(s) referenced: @(_DistinctPackagesWithVersions, ', ')" />
</Target>
</Project>
Loading