File: VSHostObject.cs
Web Access
Project: src\src\sdk\src\Containers\Microsoft.NET.Build.Containers\Microsoft.NET.Build.Containers.csproj (Microsoft.NET.Build.Containers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using System.Text.Json;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.NET.Build.Containers.Tasks;

internal sealed class VSHostObject(ITaskHost? hostObject, TaskLoggingHelper log)
{
    private const string CredentialItemSpecName = "MsDeployCredential";
    private const string UserMetaDataName = "UserName";
    private const string PasswordMetaDataName = "Password";

    private readonly ITaskHost? _hostObject = hostObject;
    private readonly TaskLoggingHelper _log = log;

    /// <summary>
    /// Tries to extract credentials from the host object.
    /// </summary>
    /// <returns>A tuple of (username, password) if credentials were found with non-empty username, null otherwise.</returns>
    public (string username, string password)? TryGetCredentials()
    {
        if (_hostObject is null)
        {
            return null;
        }

        IEnumerable<ITaskItem>? taskItems = GetTaskItems();
        if (taskItems is null)
        {
            _log.LogMessage(MessageImportance.Low, "No task items found in host object.");
            return null;
        }

        ITaskItem? credentialItem = taskItems.FirstOrDefault(p => p.ItemSpec == CredentialItemSpecName);
        if (credentialItem is null)
        {
            return null;
        }

        string username = credentialItem.GetMetadata(UserMetaDataName);
        if (string.IsNullOrEmpty(username))
        {
            return null;
        }

        string password = credentialItem.GetMetadata(PasswordMetaDataName);
        return (username, password);
    }

    private IEnumerable<ITaskItem>? GetTaskItems()
    {
        try
        {
            // This call mirrors the behavior of Microsoft.WebTools.Publish.MSDeploy.VSMsDeployTaskHostObject.QueryAllTaskItems.
            // Expected contract:
            //   - Instance method on the host object named "QueryAllTaskItems".
            //   - Signature: string QueryAllTaskItems().
            //   - Returns a JSON array of objects with the shape:
            //       [{ "ItemSpec": "<string>", "Metadata": { "<key>": "<value>", ... } }, ...]
            // 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,
                null,
                _hostObject,
                null);

            if (!string.IsNullOrEmpty(rawTaskItems))
            {
                List<TaskItemDto>? dtos = JsonSerializer.Deserialize<List<TaskItemDto>>(rawTaskItems);
                if (dtos is not null && dtos.Count > 0)
                {
                    _log.LogMessage(MessageImportance.Low, "Successfully retrieved task items via QueryAllTaskItems.");
                    return dtos.Select(ConvertToTaskItem).ToList();
                }
            }

            _log.LogMessage(MessageImportance.Low, "QueryAllTaskItems returned null or empty result.");
        }
        catch (Exception ex)
        {
            _log.LogMessage(MessageImportance.Low, "Exception trying to call QueryAllTaskItems: {0}", ex.Message);
        }

        // Fallback: try to use the host object directly as IEnumerable<ITaskItem> (legacy behavior).
        if (_hostObject is IEnumerable<ITaskItem> enumerableHost)
        {
            _log.LogMessage(MessageImportance.Low, "Falling back to IEnumerable<ITaskItem> host object.");
            return enumerableHost;
        }

        return null;

        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))
                {
                    taskItem.SetMetadata(PasswordMetaDataName, password);
                }
            }

            return taskItem;
        }
    }

    private readonly record struct TaskItemDto(string? ItemSpec, Dictionary<string, string>? Metadata);
}