diff --git a/Directory.Build.targets b/Directory.Build.targets
index e4c7e1d21a574e..394442f57fbde7 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -22,4 +22,20 @@
$(MajorVersion).$(MinorVersion)
+
+
+
+
+ <_expectedVersion>$(ProductVersion)-$(VersionSuffix)
+ <_expectedVersion Condition="'$(DotNetFinalVersionKind)' == 'release'">$(ProductVersion)
+
+
+
+
+
+
diff --git a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
index 1665f9ac692502..d31cac422d1f7b 100644
--- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -8,6 +8,9 @@
false
$(NetCoreAppCurrent)
+
+ true
+
Portable
true
diff --git a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj
index af48a96262b628..151f2fb2eeee72 100644
--- a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -10,6 +10,9 @@
false
$(NetCoreAppCurrent)
+
+ true
+
Portable
true
diff --git a/tools-local/tasks/installer.tasks/ValidateAssemblyInformationalVersion.cs b/tools-local/tasks/installer.tasks/ValidateAssemblyInformationalVersion.cs
new file mode 100644
index 00000000000000..b28f71a83e7762
--- /dev/null
+++ b/tools-local/tasks/installer.tasks/ValidateAssemblyInformationalVersion.cs
@@ -0,0 +1,144 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.DotNet.Build.Tasks
+{
+ ///
+ /// Validates that the AssemblyInformationalVersion matches the product version
+ ///
+ public class ValidateAssemblyInformationalVersion : Task
+ {
+ [Required]
+ public string AssemblyPath { get; set; }
+
+ [Required]
+ public string ProductVersion { get; set; }
+
+ public override bool Execute()
+ {
+ using FileStream stream = File.OpenRead(AssemblyPath);
+ MetadataReader metadataReader = new PEReader(stream).GetMetadataReader();
+ AssemblyDefinition assemblyDefinition = metadataReader.GetAssemblyDefinition();
+
+ CustomAttribute customAttribute = default;
+ bool foundAttribute = false;
+ foreach (CustomAttributeHandle customAttributeHandle in assemblyDefinition.GetCustomAttributes())
+ {
+ if (customAttributeHandle.IsAssemblyInformationalVersionAttribute(metadataReader, out customAttribute))
+ {
+ foundAttribute = true;
+ break;
+ }
+ }
+
+ string versionString = null;
+ if (foundAttribute)
+ {
+ CustomAttributeValue attributeValue = customAttribute.DecodeValue(new CustomAttributeTypeProvider());
+ if (attributeValue.FixedArguments.Length > 0)
+ {
+ versionString = (string)attributeValue.FixedArguments[0].Value;
+ if (versionString != null)
+ {
+ int plusIndex = versionString.IndexOf('+');
+ if (plusIndex != -1)
+ {
+ versionString = versionString.Substring(0, plusIndex);
+ }
+ }
+ }
+ }
+
+ if (string.IsNullOrEmpty(versionString))
+ {
+ Log.LogError($"Couldn't find a valid version on AssemblyInformationalVersionAttribute in: {AssemblyPath}");
+ return false;
+ }
+
+ if (!versionString.Equals(ProductVersion, StringComparison.Ordinal))
+ {
+ Log.LogError($"AssemblyInformationalVersion {versionString} doesn't match expected version {ProductVersion} in: {AssemblyPath}");
+ return false;
+ }
+
+ Log.LogMessage(MessageImportance.Normal, $"AssemblyInformationalVersion {versionString} matched expected version {ProductVersion} in: {AssemblyPath}");
+
+ return true;
+ }
+ }
+
+ internal class CustomAttributeTypeProvider : ICustomAttributeTypeProvider
+ {
+ public string GetSystemType()
+ {
+ return "[System.Runtime]System.Type";
+ }
+
+ public bool IsSystemType(string type) => false;
+
+ public string GetTypeFromSerializedName(string name)
+ {
+ return name;
+ }
+
+ public string GetPrimitiveType(PrimitiveTypeCode typeCode)
+ {
+ return typeCode switch
+ {
+ PrimitiveTypeCode.String => "string",
+ _ => throw new ArgumentOutOfRangeException(nameof(typeCode)),
+ };
+ }
+
+ public string GetSZArrayType(string elementType) => null;
+ public PrimitiveTypeCode GetUnderlyingEnumType(string type) => default;
+ public string GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind = 0) => null;
+ public string GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind = 0) => null;
+ }
+
+ internal static class MetadataReaderExtensions
+ {
+ private static bool Equals(this StringHandle handle, string other, MetadataReader reader) =>
+ reader.GetString(handle).Equals(other, StringComparison.Ordinal);
+
+ private static bool TypeMatchesNameAndNamespace(this EntityHandle handle, string @namespace, string name, MetadataReader reader)
+ {
+ switch (handle.Kind)
+ {
+ case HandleKind.TypeDefinition:
+ TypeDefinition td = reader.GetTypeDefinition((TypeDefinitionHandle)handle);
+ return !td.Namespace.IsNil && td.Namespace.Equals(@namespace, reader) && td.Name.Equals(name, reader);
+ case HandleKind.TypeReference:
+ TypeReference tr = reader.GetTypeReference((TypeReferenceHandle)handle);
+ return !tr.Namespace.IsNil && tr.Namespace.Equals(@namespace, reader) && tr.Name.Equals(name, reader);
+ default:
+ return false;
+ }
+ }
+
+ public static bool IsAssemblyInformationalVersionAttribute(this CustomAttributeHandle attributeHandle, MetadataReader reader, out CustomAttribute attribute)
+ {
+ const string @namespace = "System.Reflection";
+ const string name = "AssemblyInformationalVersionAttribute";
+ attribute = reader.GetCustomAttribute(attributeHandle);
+ EntityHandle ctorHandle = attribute.Constructor;
+ switch (ctorHandle.Kind)
+ {
+ case HandleKind.MemberReference:
+ return reader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent.TypeMatchesNameAndNamespace(@namespace, name, reader);
+ case HandleKind.MethodDefinition:
+ EntityHandle handle = reader.GetMethodDefinition((MethodDefinitionHandle)ctorHandle).GetDeclaringType();
+ return handle.TypeMatchesNameAndNamespace(@namespace, name, reader);
+ default:
+ return false;
+ }
+ }
+ }
+}