File: AllowEmptyTelemetry.cs
Web Access
Project: ..\..\..\src\Tasks\Microsoft.NET.Build.Tasks\Microsoft.NET.Build.Tasks.csproj (Microsoft.NET.Build.Tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using Microsoft.Build.Framework;
using Microsoft.NET.Build.Tasks;
 
namespace Microsoft.Build.Tasks
{
    /// <summary>
    /// Task that logs telemetry.
    /// </summary>
    public sealed class AllowEmptyTelemetry : TaskBase
    {
        public AllowEmptyTelemetry()
        {
            EventData = Array.Empty<ITaskItem>();
            EventName = string.Empty;
        }
 
        /// <summary>
        /// Gets or sets an array of ITaskItems. The ItemSpec of the item will be the 'key' of the telemetry event property. The 'Value' metadata will be the value.
        /// If the 'Hash' metadata is present and set to 'true', the value will be hashed before being logged.
        /// </summary>
        public ITaskItem[] EventData { get; set; }
 
        [Required] public string EventName { get; set; }
 
        protected override void ExecuteCore()
        {
            if (EventData == null)
            {
                (BuildEngine as IBuildEngine5)?.LogTelemetry(EventName, null);
            }
            else
            {
                IDictionary<string, string> properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                foreach (var item in EventData)
                {
                    var key = item.ItemSpec;
                    var availableNames = item.MetadataNames.Cast<string>();
                    var hasValue = availableNames.Contains("Value");
                    var value = hasValue ? item.GetMetadata("Value") : null;
                    var hasHash = availableNames.Contains("Hash");
                    var hash = hasHash ? bool.Parse(item.GetMetadata("Hash")) : false;
                    if (hash && value != null)
                    {
                        value = HashWithNormalizedCasing(value);
                    }
                    if (string.IsNullOrEmpty(value))
                    {
                        properties[key] = "null";
                    }
                    else
                    {
                        properties[key] = value;
                    }
                }
                (BuildEngine as IBuildEngine5)?.LogTelemetry(EventName, properties);
            }
        }
 
        // A local copy of Sha256Hasher.HashWithNormalizedCasing from the Microsoft.DotNet.Cli.Utils project.
        // We don't want to introduce a project<->project dependency, and the logic is straightforward enough.
        private static string HashWithNormalizedCasing(string text)
        {
            var utf8UpperBytes = Encoding.UTF8.GetBytes(text.ToUpperInvariant());
#if NETFRAMEWORK
            var crypt = System.Security.Cryptography.SHA256.Create();
            var hash = new System.Text.StringBuilder();
            byte[] crypto = crypt.ComputeHash(utf8UpperBytes);
            foreach (byte theByte in crypto)
            {
                hash.Append(theByte.ToString("x2"));
            }
            return hash.ToString().ToLowerInvariant();
#endif
#if NET
            byte[] hash = System.Security.Cryptography.SHA256.HashData(utf8UpperBytes);
            return Convert.ToHexStringLower(hash);
#endif
        }
    }
}