File: WebConfigTelemetry.cs
Web Access
Project: ..\..\..\src\WebSdk\Publish\Tasks\Microsoft.NET.Sdk.Publish.Tasks.csproj (Microsoft.NET.Sdk.Publish.Tasks)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.RegularExpressions;
using Microsoft.DotNet.Cli;
 
namespace Microsoft.NET.Sdk.Publish.Tasks
{
    public class WebConfigTelemetry
    {
        // An example of a project line looks like this:
        //  Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{05A5AD00-71B5-4612-AF2F-9EA9121C4111}"
        private static readonly Lazy<Regex> s_crackProjectLine = new(
            () => new Regex
                (
                @"^" // Beginning of line
                + @"Project\s*\(\""[^""]*?\""\)"
                + @"\s*=\s*" // Any amount of whitespace plus "=" plus any amount of whitespace
                + @"""[^""]*?"""
                + @"\s*,\s*" // Any amount of whitespace plus "," plus any amount of whitespace
                + @"\""(?<RELATIVEPATH>[^""]*?)\"""
                + @"\s*,\s*" // Any amount of whitespace plus "," plus any amount of whitespace
                + @"\""(?<PROJECTGUID>[^""]*?)\"""
                + @"\s*$", // End-of-line
                RegexOptions.Compiled
                )
            );
 
        public static XDocument? AddTelemetry(XDocument? webConfig, string? projectGuid, bool ignoreProjectGuid, string? solutionFileFullPath, string? projectFileFullPath)
        {
            try
            {
                bool isCLIOptOutEnabled = EnvironmentHelper.GetEnvironmentVariableAsBool(EnvironmentVariableNames.TELEMETRY_OPTOUT, defaultValue: CompileOptions.TelemetryOptOutDefault);
                if (string.IsNullOrEmpty(projectGuid) && !ignoreProjectGuid && !isCLIOptOutEnabled)
                {
                    projectGuid = GetProjectGuidFromSolutionFile(solutionFileFullPath, projectFileFullPath);
                }
 
                // Add the projectGuid to web.config if it is not present. Remove ProjectGuid from web.config if opted out.
                webConfig = WebConfigTransform.AddProjectGuidToWebConfig(webConfig, projectGuid, ignoreProjectGuid);
            }
            catch
            {
                // Telemetry
            }
 
            return webConfig;
        }
 
        public static string? GetProjectGuidFromSolutionFile(string? solutionFileFullPath, string? projectFileFullPath)
        {
            try
            {
                if (!string.IsNullOrEmpty(solutionFileFullPath) && File.Exists(solutionFileFullPath))
                {
                    return GetProjectGuid(solutionFileFullPath, projectFileFullPath);
                }
 
                int parentLevelsToSearch = 5;
                string? solutionDirectory = Path.GetDirectoryName(projectFileFullPath);
 
                while (parentLevelsToSearch-- > 0)
                {
                    if (string.IsNullOrEmpty(solutionDirectory) || !Directory.Exists(solutionDirectory))
                    {
                        return null;
                    }
 
                    IEnumerable<string> solutionFiles = [
                        ..Directory.EnumerateFiles(solutionDirectory, "*.sln", SearchOption.TopDirectoryOnly),
                        ..Directory.EnumerateFiles(solutionDirectory, "*.slnx", SearchOption.TopDirectoryOnly)
                    ];
                    foreach (string solutionFile in solutionFiles)
                    {
                        string? projectGuid = GetProjectGuid(solutionFile, projectFileFullPath);
                        if (!string.IsNullOrEmpty(projectGuid))
                        {
                            return projectGuid;
                        }
                    }
 
                    solutionDirectory = Directory.GetParent(solutionDirectory)?.FullName;
                }
            }
            catch
            {
                // This code path is only used for telemetry.
            }
 
            return null;
        }
 
        private static string? GetProjectGuid(string? solutionFileFullPath, string? projectFileFullPath)
        {
            if (!string.IsNullOrEmpty(solutionFileFullPath) && File.Exists(solutionFileFullPath))
            {
                string[] solutionFileLines = File.ReadAllLines(solutionFileFullPath);
                foreach (string solutionFileLine in solutionFileLines)
                {
                    Match match = s_crackProjectLine.Value.Match(solutionFileLine);
                    if (match.Success)
                    {
                        string projectRelativePath = match.Groups["RELATIVEPATH"].Value.Trim();
                        string? solutionFileDirectory = Path.GetDirectoryName(solutionFileFullPath);
                        if (solutionFileDirectory is not null)
                        {
                            string projectFullPathConstructed = Path.Combine(solutionFileDirectory, projectRelativePath);
                            projectFullPathConstructed = Path.GetFullPath((new Uri(projectFullPathConstructed)).LocalPath);
                            if (string.Equals(projectFileFullPath, projectFullPathConstructed, StringComparison.OrdinalIgnoreCase))
                            {
                                string projectGuid = match.Groups["PROJECTGUID"].Value.Trim();
                                return projectGuid;
                            }
                        }
                    }
                }
            }
 
            return null;
        }
    }
}