File: Telemetry\DeviceIdHelper.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.AI.Evaluation.Console\Microsoft.Extensions.AI.Evaluation.Console.csproj (Microsoft.Extensions.AI.Evaluation.Console)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
 
namespace Microsoft.Extensions.AI.Evaluation.Console.Telemetry;
 
// Note: The below code is based on the code in the following file in the dotnet CLI:
// https://github.com/dotnet/sdk/blob/main/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs.
//
// The logic below should be kept in sync with the code linked above to ensure that the device ID remains consistent
// across tools.
 
internal sealed class DeviceIdHelper
{
    private const string RegistryKeyPath = @"SOFTWARE\Microsoft\DeveloperTools";
    private const string RegistryValueName = "deviceid";
    private const string CacheFileName = "deviceid";
 
    private static string? _deviceId;
 
    private readonly ILogger _logger;
 
    internal DeviceIdHelper(ILogger logger)
    {
        _logger = logger;
    }
 
    internal string GetDeviceId()
    {
        string? deviceId = GetCachedDeviceId();
 
        if (string.IsNullOrWhiteSpace(deviceId))
        {
#pragma warning disable CA1308 // Normalize strings to uppercase.
            // The DevDeviceId must follow the format specified below.
            // 1. The value is a randomly generated Guid/ UUID.
            // 2. The value follows the 8-4-4-4-12 format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
            // 3. The value shall be all lowercase and only contain hyphens. No braces or brackets.
            deviceId = Guid.NewGuid().ToString("D").ToLowerInvariant();
#pragma warning restore CA1308
 
            try
            {
                CacheDeviceId(deviceId);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to cache device ID.");
 
                // If caching fails, return empty string to avoid reporting a non-cached id.
                deviceId = string.Empty;
            }
        }
 
        return deviceId;
    }
 
    private static string? GetCachedDeviceId()
    {
        if (_deviceId is not null)
        {
            return _deviceId;
        }
 
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            using RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
            using RegistryKey? key = baseKey.OpenSubKey(RegistryKeyPath);
            _deviceId = key?.GetValue(RegistryValueName) as string;
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            string cacheFileDirectoryPath = GetCacheFileDirectoryPathForLinux();
            ReadCacheFile(cacheFileDirectoryPath);
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            string cacheFileDirectoryPath = GetCacheFileDirectoryPathForMacOS();
            ReadCacheFile(cacheFileDirectoryPath);
        }
 
        return _deviceId;
 
        static void ReadCacheFile(string cacheFileDirectoryPath)
        {
            string cacheFilePath = Path.Combine(cacheFileDirectoryPath, CacheFileName);
            if (File.Exists(cacheFilePath))
            {
                _deviceId = File.ReadAllText(cacheFilePath);
            }
        }
    }
 
    private static void CacheDeviceId(string deviceId)
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            using RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
            using RegistryKey key = baseKey.CreateSubKey(RegistryKeyPath);
            key.SetValue(RegistryValueName, deviceId);
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            string cacheFileDirectoryPath = GetCacheFileDirectoryPathForLinux();
            WriteCacheFile(cacheFileDirectoryPath, deviceId);
        }
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            string cacheFileDirectoryPath = GetCacheFileDirectoryPathForMacOS();
            WriteCacheFile(cacheFileDirectoryPath, deviceId);
        }
 
        _deviceId = deviceId;
 
        static void WriteCacheFile(string cacheFileDirectoryPath, string deviceId)
        {
            _ = Directory.CreateDirectory(cacheFileDirectoryPath);
            string cacheFilePath = Path.Combine(cacheFileDirectoryPath, CacheFileName);
            File.WriteAllText(cacheFilePath, deviceId);
        }
    }
 
    private static string GetCacheFileDirectoryPathForLinux()
    {
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            throw new InvalidOperationException();
        }
 
        string cacheFileDirectoryPath;
        string? xdgCacheHome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
 
        if (string.IsNullOrWhiteSpace(xdgCacheHome))
        {
            string userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
            cacheFileDirectoryPath = Path.Combine(userProfilePath, ".cache");
        }
        else
        {
            cacheFileDirectoryPath = Path.Combine(xdgCacheHome, "Microsoft", "DeveloperTools");
        }
 
        return cacheFileDirectoryPath;
    }
 
    private static string GetCacheFileDirectoryPathForMacOS()
    {
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            throw new InvalidOperationException();
        }
 
        string userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
 
        string cacheFileDirectoryPath =
            Path.Combine(userProfilePath, "Library", "Application Support", "Microsoft", "DeveloperTools");
 
        return cacheFileDirectoryPath;
    }
}