diff --git a/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs b/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs
new file mode 100644
index 0000000000..f10f53a748
--- /dev/null
+++ b/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs
@@ -0,0 +1,135 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.Build.Tasks
+{
+ ///
+ /// Computes the checksum for a single file.
+ ///
+ public sealed class BlazorGetFileHash : Task
+ {
+ internal const string _defaultFileHashAlgorithm = "SHA256";
+ internal const string _hashEncodingBase64 = "base64";
+
+ ///
+ /// The files to be hashed.
+ ///
+ [Required]
+ public ITaskItem[] Files { get; set; }
+
+ ///
+ /// The algorithm. Allowed values: SHA256, SHA384, SHA512. Default = SHA256.
+ ///
+ public string Algorithm { get; set; } = _defaultFileHashAlgorithm;
+
+ ///
+ /// The metadata name where the hash is stored in each item. Defaults to "FileHash".
+ ///
+ public string MetadataName { get; set; } = "FileHash";
+
+ ///
+ /// The encoding to use for generated hashs. Defaults to "hex". Allowed values = "base64".
+ ///
+ public string HashEncoding { get; set; } = _hashEncodingBase64;
+
+ ///
+ /// The hash of the file. This is only set if there was one item group passed in.
+ ///
+ [Output]
+ public string Hash { get; set; }
+
+ ///
+ /// The input files with additional metadata set to include the file hash.
+ ///
+ [Output]
+ public ITaskItem[] Items { get; set; }
+
+ public override bool Execute()
+ {
+ if (!SupportsAlgorithm(Algorithm))
+ {
+ Log.LogError("Unrecognized HashAlgorithm {0}", Algorithm);
+ return false;
+ }
+
+ System.Threading.Tasks.Parallel.ForEach(Files, file =>
+ {
+ if (!File.Exists(file.ItemSpec))
+ {
+ Log.LogError("File not found '{0}", file.ItemSpec);
+ }
+ });
+
+ if (Log.HasLoggedErrors)
+ {
+ return false;
+ }
+
+ System.Threading.Tasks.Parallel.ForEach(Files, file =>
+ {
+ var hash = ComputeHash(Algorithm, file.ItemSpec);
+ file.SetMetadata("FileHashAlgorithm", Algorithm);
+ file.SetMetadata(MetadataName, EncodeHash(hash));
+ });
+
+ Items = Files;
+
+ if (Files.Length == 1)
+ {
+ Hash = Files[0].GetMetadata(MetadataName);
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ internal static string EncodeHash(byte[] hash)
+ {
+ return Convert.ToBase64String(hash);
+ }
+
+ internal static bool SupportsAlgorithm(string algorithmName)
+ => _supportedAlgorithms.Contains(algorithmName);
+
+ internal static byte[] ComputeHash(string algorithmName, string filePath)
+ {
+ using (var stream = File.OpenRead(filePath))
+ using (var algorithm = CreateAlgorithm(algorithmName))
+ {
+ return algorithm.ComputeHash(stream);
+ }
+ }
+
+ private static readonly HashSet _supportedAlgorithms
+ = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ "SHA256",
+ "SHA384",
+ "SHA512",
+ };
+
+ private static HashAlgorithm CreateAlgorithm(string algorithmName)
+ {
+ if (string.Equals(algorithmName, "SHA256", StringComparison.OrdinalIgnoreCase))
+ {
+ return SHA256.Create();
+ }
+ else if (string.Equals(algorithmName, "SHA384", StringComparison.OrdinalIgnoreCase))
+ {
+ return SHA384.Create();
+ }
+ else if (string.Equals(algorithmName, "SHA512", StringComparison.OrdinalIgnoreCase))
+ {
+ return SHA512.Create();
+ }
+
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets
index 8c16457068..1b7614bbae 100644
--- a/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets
+++ b/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets
@@ -1,4 +1,6 @@
+
+
true
false
@@ -153,9 +155,9 @@
<_ExistingBlazorOutputWithTargetPath Include="@(_BlazorOutputWithTargetPath)" Condition="Exists('%(FullPath)')" />
-
+
-
+
<_BlazorOutputWithIntegrity Include="@(_BlazorOutputWithHash)">