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; } } }