diff --git a/src/Containers/Microsoft.NET.Build.Containers/VSHostObject.cs b/src/Common/VSHostObject.cs
similarity index 75%
rename from src/Containers/Microsoft.NET.Build.Containers/VSHostObject.cs
rename to src/Common/VSHostObject.cs
index 89ea5b16ceae..b7b343690312 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/VSHostObject.cs
+++ b/src/Common/VSHostObject.cs
@@ -6,8 +6,13 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
-namespace Microsoft.NET.Build.Containers.Tasks;
+namespace Microsoft.NET.Sdk.Common;
+///
+/// Extracts task items and credentials from a Visual Studio host object.
+/// Supports both the JSON-based QueryAllTaskItems protocol and the legacy
+/// interface.
+///
internal sealed class VSHostObject(ITaskHost? hostObject, TaskLoggingHelper log)
{
private const string CredentialItemSpecName = "MsDeployCredential";
@@ -51,7 +56,11 @@ internal sealed class VSHostObject(ITaskHost? hostObject, TaskLoggingHelper log)
return (username, password);
}
- private IEnumerable? GetTaskItems()
+ ///
+ /// Gets all task items from the host object.
+ ///
+ /// The task items if available, null otherwise.
+ public IEnumerable? GetTaskItems()
{
try
{
@@ -62,7 +71,6 @@ internal sealed class VSHostObject(ITaskHost? hostObject, TaskLoggingHelper log)
// - Returns a JSON array of objects with the shape:
// [{ "ItemSpec": "", "Metadata": { "": "", ... } }, ...]
// The JSON is deserialized into TaskItemDto records and converted to ITaskItem instances.
- // Only UserName and Password metadata are extracted to avoid conflicts with reserved MSBuild metadata.
string? rawTaskItems = (string?)_hostObject!.GetType().InvokeMember(
"QueryAllTaskItems",
BindingFlags.InvokeMethod,
@@ -101,14 +109,16 @@ static TaskItem ConvertToTaskItem(TaskItemDto dto)
TaskItem taskItem = new(dto.ItemSpec ?? string.Empty);
if (dto.Metadata is not null)
{
- if (dto.Metadata.TryGetValue(UserMetaDataName, out string? userName))
- {
- taskItem.SetMetadata(UserMetaDataName, userName);
- }
-
- if (dto.Metadata.TryGetValue(PasswordMetaDataName, out string? password))
+ foreach (KeyValuePair kvp in dto.Metadata)
{
- taskItem.SetMetadata(PasswordMetaDataName, password);
+ try
+ {
+ taskItem.SetMetadata(kvp.Key, kvp.Value);
+ }
+ catch (ArgumentException)
+ {
+ // Skip reserved/built-in MSBuild metadata names (e.g. FullPath, Identity).
+ }
}
}
@@ -116,6 +126,16 @@ static TaskItem ConvertToTaskItem(TaskItemDto dto)
}
}
- private readonly record struct TaskItemDto(string? ItemSpec, Dictionary? Metadata);
-}
+ private readonly struct TaskItemDto
+ {
+ public string? ItemSpec { get; }
+ public Dictionary? Metadata { get; }
+ [System.Text.Json.Serialization.JsonConstructor]
+ public TaskItemDto(string? itemSpec, Dictionary? metadata)
+ {
+ ItemSpec = itemSpec;
+ Metadata = metadata;
+ }
+ }
+}
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj b/src/Containers/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj
index 4ffaf57f4a43..d635d1fdfb86 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj
+++ b/src/Containers/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj
@@ -53,6 +53,10 @@
+
+
+
+
@@ -74,7 +78,6 @@
-
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
index 124ee9749f45..8dc590094f58 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs
@@ -5,6 +5,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.MSBuild;
using Microsoft.NET.Build.Containers.Resources;
+using Microsoft.NET.Sdk.Common;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Microsoft.NET.Build.Containers.Tasks;
diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs
index 8bcec281ff38..49477208b5c8 100644
--- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs
+++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs
@@ -5,6 +5,7 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.NET.Build.Containers.Resources;
+using Microsoft.NET.Sdk.Common;
namespace Microsoft.NET.Build.Containers.Tasks;
diff --git a/src/WebSdk/Publish/Tasks/Microsoft.NET.Sdk.Publish.Tasks.csproj b/src/WebSdk/Publish/Tasks/Microsoft.NET.Sdk.Publish.Tasks.csproj
index 28419ff24276..baca625dfd5c 100644
--- a/src/WebSdk/Publish/Tasks/Microsoft.NET.Sdk.Publish.Tasks.csproj
+++ b/src/WebSdk/Publish/Tasks/Microsoft.NET.Sdk.Publish.Tasks.csproj
@@ -48,6 +48,7 @@
+
true
diff --git a/src/WebSdk/Publish/Tasks/MsDeploy/VSHostObject.cs b/src/WebSdk/Publish/Tasks/MsDeploy/VSHostObject.cs
deleted file mode 100644
index 8b711417390c..000000000000
--- a/src/WebSdk/Publish/Tasks/MsDeploy/VSHostObject.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.Build.Framework;
-
-namespace Microsoft.NET.Sdk.Publish.Tasks.MsDeploy
-{
- internal class VSHostObject
- {
- IEnumerable? _hostObject;
- public VSHostObject(IEnumerable? hostObject)
- {
- _hostObject = hostObject;
- }
-
- public bool ExtractCredentials(out string username, out string password)
- {
- bool retVal = false;
- username = password = string.Empty;
- if (_hostObject != null)
- {
- ITaskItem? credentialItem = _hostObject.FirstOrDefault(p => p.ItemSpec == VSMsDeployTaskHostObject.CredentialItemSpecName);
- if (credentialItem != null)
- {
- retVal = true;
- username = credentialItem.GetMetadata(VSMsDeployTaskHostObject.UserMetaDataName);
- if (!string.IsNullOrEmpty(username))
- {
- password = credentialItem.GetMetadata(VSMsDeployTaskHostObject.PasswordMetaDataName);
- }
- }
- }
- return retVal;
- }
-
- public void GetFileSkips(out ITaskItem[]? srcSkips, out ITaskItem[]? destSkips)
- {
- srcSkips = null;
- destSkips = null;
- if (_hostObject != null)
- {
- IEnumerable items;
-
- items = from item in _hostObject
- where (item.ItemSpec == VSMsDeployTaskHostObject.SkipFileItemSpecName
- && (item.GetMetadata(VSMsDeployTaskHostObject.SkipApplyMetadataName) == VSMsDeployTaskHostObject.SourceDeployObject ||
- string.IsNullOrEmpty(item.GetMetadata(VSMsDeployTaskHostObject.SkipApplyMetadataName)))
- )
- select item;
- srcSkips = items.ToArray();
-
- items = from item in _hostObject
- where (item.ItemSpec == VSMsDeployTaskHostObject.SkipFileItemSpecName
- && (item.GetMetadata(VSMsDeployTaskHostObject.SkipApplyMetadataName) == VSMsDeployTaskHostObject.DestinationDeployObject ||
- string.IsNullOrEmpty(item.GetMetadata(VSMsDeployTaskHostObject.SkipApplyMetadataName)))
- )
- select item;
- destSkips = items.ToArray();
- }
- }
- }
-}
diff --git a/src/WebSdk/Publish/Tasks/Tasks/MsDeploy/VsMsdeploy.cs b/src/WebSdk/Publish/Tasks/Tasks/MsDeploy/VsMsdeploy.cs
index 27466df4a83e..ede99a2cbe10 100644
--- a/src/WebSdk/Publish/Tasks/Tasks/MsDeploy/VsMsdeploy.cs
+++ b/src/WebSdk/Publish/Tasks/Tasks/MsDeploy/VsMsdeploy.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Build.Framework;
+using Microsoft.NET.Sdk.Common;
using Microsoft.NET.Sdk.Publish.Tasks.Properties;
using Collections = System.Collections;
using Diagnostics = System.Diagnostics;
@@ -823,9 +824,8 @@ public override bool Execute()
else
{
dest = VSMSDeployObjectFactory.CreateVSMSDeployObject(Destination[0]);
- VSHostObject hostObj = new(HostObject as IEnumerable);
- string username, password;
- if (hostObj.ExtractCredentials(out username, out password))
+ VSHostObject hostObj = new(HostObject, Log);
+ if (hostObj.TryGetCredentials() is (string username, string password))
{
dest.UserName = username;
dest.Password = password;
@@ -937,11 +937,24 @@ void IVSMSDeployHost.UpdateDeploymentBaseOptions(VSMSDeployObject srcVsMsDeployo
List enableSkipDirectiveList = MSDeployUtility.ConvertStringIntoList(EnableSkipDirective);
List disableSkipDirectiveList = MSDeployUtility.ConvertStringIntoList(DisableSkipDirective);
- VSHostObject hostObject = new(HostObject as IEnumerable);
- ITaskItem[]? srcSkipItems, destSkipsItems;
+ VSHostObject hostObject = new(HostObject, Log);
+ IEnumerable? allItems = hostObject.GetTaskItems();
+ ITaskItem[]? srcSkipItems = null;
+ ITaskItem[]? destSkipsItems = null;
// Add FileSkip rules from Host Object
- hostObject.GetFileSkips(out srcSkipItems, out destSkipsItems);
+ if (allItems is not null)
+ {
+ srcSkipItems = allItems.Where(item =>
+ item.ItemSpec == VSMsDeployTaskHostObject.SkipFileItemSpecName
+ && (item.GetMetadata(VSMsDeployTaskHostObject.SkipApplyMetadataName) == VSMsDeployTaskHostObject.SourceDeployObject
+ || string.IsNullOrEmpty(item.GetMetadata(VSMsDeployTaskHostObject.SkipApplyMetadataName)))).ToArray();
+
+ destSkipsItems = allItems.Where(item =>
+ item.ItemSpec == VSMsDeployTaskHostObject.SkipFileItemSpecName
+ && (item.GetMetadata(VSMsDeployTaskHostObject.SkipApplyMetadataName) == VSMsDeployTaskHostObject.DestinationDeployObject
+ || string.IsNullOrEmpty(item.GetMetadata(VSMsDeployTaskHostObject.SkipApplyMetadataName)))).ToArray();
+ }
Utility.AddSkipDirectiveToBaseOptions(srcVsMsDeployobject.BaseOptions, srcSkipItems, enableSkipDirectiveList, disableSkipDirectiveList, Log);
Utility.AddSkipDirectiveToBaseOptions(destVsMsDeployobject.BaseOptions, destSkipsItems, enableSkipDirectiveList, disableSkipDirectiveList, Log);
diff --git a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.cs b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.cs
index cd9002b6c524..1770c478409c 100644
--- a/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.cs
+++ b/src/WebSdk/Publish/Tasks/Tasks/OneDeploy/OneDeploy.cs
@@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Build.Framework;
-using Microsoft.NET.Sdk.Publish.Tasks.MsDeploy;
+using Microsoft.NET.Sdk.Common;
using Microsoft.NET.Sdk.Publish.Tasks.Properties;
namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy;
@@ -161,9 +161,19 @@ internal async Task OneDeployAsync(
private bool GetCredentialsFromTask(out string user, out string password)
{
- VSHostObject hostObj = new(HostObject as IEnumerable);
+ VSHostObject hostObj = new(HostObject, Log);
+ if (hostObj.TryGetCredentials() is (string u, string p))
+ {
+ user = u;
+ password = p;
+
+ return true;
+ }
+
+ user = string.Empty;
+ password = string.Empty;
- return hostObj.ExtractCredentials(out user, out password);
+ return false;
}
private Task DeployAsync(
diff --git a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/ZipDeploy.cs b/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/ZipDeploy.cs
index 216c34b98a94..e8510953da3c 100644
--- a/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/ZipDeploy.cs
+++ b/src/WebSdk/Publish/Tasks/Tasks/ZipDeploy/ZipDeploy.cs
@@ -3,7 +3,7 @@
using System.Net;
using Microsoft.Build.Framework;
-using Microsoft.NET.Sdk.Publish.Tasks.MsDeploy;
+using Microsoft.NET.Sdk.Common;
using Microsoft.NET.Sdk.Publish.Tasks.Properties;
namespace Microsoft.NET.Sdk.Publish.Tasks.ZipDeploy
@@ -136,8 +136,19 @@ public async Task ZipDeployAsync(string? zipToPublishPath, string? userNam
private bool GetDestinationCredentials(out string user, out string password)
{
- VSHostObject hostObj = new(HostObject as IEnumerable);
- return hostObj.ExtractCredentials(out user, out password);
+ VSHostObject hostObj = new(HostObject, Log);
+ if (hostObj.TryGetCredentials() is (string u, string p))
+ {
+ user = u;
+ password = p;
+
+ return true;
+ }
+
+ user = string.Empty;
+ password = string.Empty;
+
+ return false;
}
}
}