diff --git a/Directory.Build.props b/Directory.Build.props
index 5736fce3341c20..f971fa0a774fcc 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -70,6 +70,7 @@
$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmAppBuilder', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))
$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'WasmBuildTasks', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))
$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'MonoAOTCompiler', 'Debug', '$(NetCoreAppToolCurrent)'))
+ $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'RuntimeConfigParser', 'Debug', '$(NetCoreAppToolCurrent)', 'publish'))
$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', '$(NetCoreAppToolCurrent)', 'installer.tasks.dll'))
$([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'installer.tasks', 'Debug', 'net461', 'installer.tasks.dll'))
@@ -78,6 +79,7 @@
$([MSBuild]::NormalizePath('$(WasmAppBuilderDir)', 'WasmAppBuilder.dll'))
$([MSBuild]::NormalizePath('$(WasmBuildTasksDir)', 'WasmBuildTasks.dll'))
$([MSBuild]::NormalizePath('$(MonoAOTCompilerDir)', 'MonoAOTCompiler.dll'))
+ $([MSBuild]::NormalizePath('$(RuntimeConfigParserDir)', 'RuntimeConfigParser.dll'))
diff --git a/src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs b/src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs
new file mode 100644
index 00000000000000..12cbf59442f5f4
--- /dev/null
+++ b/src/tasks/RuntimeConfigParser/RuntimeConfigParser.cs
@@ -0,0 +1,113 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Reflection.Metadata;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+public class RuntimeConfigParserTask : Task
+{
+ ///
+ /// The path to runtimeconfig.json file.
+ ///
+ [Required]
+ public string RuntimeConfigFile { get; set; } = "";
+
+ ///
+ /// The path to the output binary file.
+ ///
+ [Required]
+ public string OutputFile { get; set; } = "";
+
+ ///
+ /// List of properties reserved for the host.
+ ///
+ public ITaskItem[] ReservedProperties { get; set; } = Array.Empty();
+
+ public override bool Execute()
+ {
+ if (string.IsNullOrEmpty(RuntimeConfigFile))
+ {
+ Log.LogError($"'{nameof(RuntimeConfigFile)}' is required.");
+ }
+
+ if (string.IsNullOrEmpty(OutputFile))
+ {
+ Log.LogError($"'{nameof(OutputFile)}' is required.");
+ }
+
+ Dictionary configProperties = ConvertInputToDictionary(RuntimeConfigFile);
+
+ if (ReservedProperties.Length != 0)
+ {
+ CheckDuplicateProperties(configProperties, ReservedProperties);
+ }
+
+ var blobBuilder = new BlobBuilder();
+ ConvertDictionaryToBlob(configProperties, blobBuilder);
+
+ using var stream = File.OpenWrite(OutputFile);
+ blobBuilder.WriteContentTo(stream);
+
+ return !Log.HasLoggedErrors;
+ }
+
+ /// Reads a json file from the given path and extracts the "configProperties" key (assumed to be a string to string dictionary)
+ private Dictionary ConvertInputToDictionary(string inputFilePath)
+ {
+ var options = new JsonSerializerOptions {
+ AllowTrailingCommas = true,
+ ReadCommentHandling = JsonCommentHandling.Skip,
+ };
+
+ var jsonString = File.ReadAllText(inputFilePath);
+ var parsedJson = JsonSerializer.Deserialize(jsonString, options);
+
+ if (parsedJson == null)
+ {
+ throw new ArgumentException("Wasn't able to parse the json file successfully.");
+ }
+
+ return parsedJson.ConfigProperties;
+ }
+
+ /// Just write the dictionary out to a blob as a count followed by
+ /// a length-prefixed UTF8 encoding of each key and value
+ private void ConvertDictionaryToBlob(IReadOnlyDictionary properties, BlobBuilder builder)
+ {
+ int count = properties.Count;
+
+ builder.WriteCompressedInteger(count);
+ foreach (var kvp in properties)
+ {
+ builder.WriteSerializedString(kvp.Key);
+ builder.WriteSerializedString(kvp.Value);
+ }
+ }
+
+ private void CheckDuplicateProperties(IReadOnlyDictionary properties, ITaskItem[] keys)
+ {
+ foreach (var key in keys)
+ {
+ if (properties.ContainsKey(key.ItemSpec))
+ {
+ throw new ArgumentException($"Property '{key}' can't be set by the user!");
+ }
+ }
+ }
+}
+
+public class Root
+{
+ // the configProperties key
+ [JsonPropertyName("configProperties")]
+ public Dictionary ConfigProperties { get; set; } = new Dictionary();
+ // everything other than configProperties
+ [JsonExtensionData]
+ public Dictionary ExtensionData { get; set; } = new Dictionary();
+}
diff --git a/src/tasks/RuntimeConfigParser/RuntimeConfigParser.csproj b/src/tasks/RuntimeConfigParser/RuntimeConfigParser.csproj
new file mode 100644
index 00000000000000..39204be9b0bbf5
--- /dev/null
+++ b/src/tasks/RuntimeConfigParser/RuntimeConfigParser.csproj
@@ -0,0 +1,27 @@
+
+
+ $(NetCoreAppToolCurrent)
+ Library
+ true
+ false
+ enable
+ $(NoWarn),CA1050
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+