Skip to content

Commit

Permalink
[release/8.0.4xx] Add Dev Device ID (#43362)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcpopMSFT authored Oct 10, 2024
2 parents ab60184 + 9434154 commit 71c1540
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 0 deletions.
130 changes: 130 additions & 0 deletions src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Win32;

namespace Microsoft.DotNet.Cli.Telemetry
{
internal static class DeviceIdGetter
{
public static string GetDeviceId()
{
string deviceId = GetCachedDeviceId();

// Check if the device Id is already cached
if (string.IsNullOrEmpty(deviceId))
{
// Generate a new guid
deviceId = Guid.NewGuid().ToString("D").ToLowerInvariant();

// Cache the new device Id
try
{
CacheDeviceId(deviceId);
}
catch
{
// If caching fails, return empty string to avoid sending a non-stored id
deviceId = "";
}
}

return deviceId;
}

private static string GetCachedDeviceId()
{
string deviceId = null;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Get device Id from Windows registry matching the OS architecture
using (var key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64).OpenSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
{
deviceId = key?.GetValue("deviceid") as string;
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// Get device Id from Linux cache file
string cacheFilePath;
string xdgCacheHome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
if (!string.IsNullOrEmpty(xdgCacheHome))
{
cacheFilePath = Path.Combine(xdgCacheHome, "Microsoft", "DeveloperTools", "deviceid");
}
else
{
cacheFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache", "deviceid");
}

if (File.Exists(cacheFilePath))
{
deviceId = File.ReadAllText(cacheFilePath);
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// Get device Id from macOS cache file
string cacheFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Microsoft", "DeveloperTools", "deviceid");
if (File.Exists(cacheFilePath))
{
deviceId = File.ReadAllText(cacheFilePath);
}
}

return deviceId;
}

private static void CacheDeviceId(string deviceId)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Cache device Id in Windows registry matching the OS architecture
using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64))
{
using(var key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
{
if (key != null)
{
key.SetValue("deviceid", deviceId);
}
}
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// Cache device Id in Linux cache file
string cacheFilePath;
string xdgCacheHome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
if (!string.IsNullOrEmpty(xdgCacheHome))
{
cacheFilePath = Path.Combine(xdgCacheHome, "Microsoft", "DeveloperTools", "deviceId");
}
else
{
cacheFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache", "deviceid");
}

CreateDirectoryAndWriteToFile(cacheFilePath, deviceId);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// Cache device Id in macOS cache file
string cacheFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Microsoft", "DeveloperTools", "deviceid");

CreateDirectoryAndWriteToFile(cacheFilePath, deviceId);
}
}

private static void CreateDirectoryAndWriteToFile(string filePath, string content)
{
string directory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
File.WriteAllText(filePath, content);
}
}
}
5 changes: 5 additions & 0 deletions src/Cli/dotnet/Telemetry/TelemetryCommonProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ public TelemetryCommonProperties(
Func<string> getCurrentDirectory = null,
Func<string, string> hasher = null,
Func<string> getMACAddress = null,
Func<string> getDeviceId = null,
IDockerContainerDetector dockerContainerDetector = null,
IUserLevelCacheWriter userLevelCacheWriter = null,
ICIEnvironmentDetector ciEnvironmentDetector = null)
{
_getCurrentDirectory = getCurrentDirectory ?? Directory.GetCurrentDirectory;
_hasher = hasher ?? Sha256Hasher.Hash;
_getMACAddress = getMACAddress ?? MacAddressGetter.GetMacAddress;
_getDeviceId = getDeviceId ?? DeviceIdGetter.GetDeviceId;
_dockerContainerDetector = dockerContainerDetector ?? new DockerContainerDetectorForTelemetry();
_userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter();
_ciEnvironmentDetector = ciEnvironmentDetector ?? new CIEnvironmentDetectorForTelemetry();
Expand All @@ -31,6 +33,7 @@ public TelemetryCommonProperties(
private Func<string> _getCurrentDirectory;
private Func<string, string> _hasher;
private Func<string> _getMACAddress;
private Func<string> _getDeviceId;
private IUserLevelCacheWriter _userLevelCacheWriter;
private const string OSVersion = "OS Version";
private const string OSPlatform = "OS Platform";
Expand All @@ -40,6 +43,7 @@ public TelemetryCommonProperties(
private const string ProductVersion = "Product Version";
private const string TelemetryProfile = "Telemetry Profile";
private const string CurrentPathHash = "Current Path Hash";
private const string DeviceId = "devdeviceid";
private const string MachineId = "Machine ID";
private const string MachineIdOld = "Machine ID Old";
private const string DockerContainer = "Docker Container";
Expand Down Expand Up @@ -81,6 +85,7 @@ public Dictionary<string, string> GetTelemetryCommonProperties()
CliFolderPathCalculator.DotnetUserProfileFolderPath,
$"{MachineIdCacheKey}.v1.dotnetUserLevelCache"),
GetMachineId)},
{DeviceId, _getDeviceId()},
{KernelVersion, GetKernelVersion()},
{InstallationType, ExternalTelemetryProperties.GetInstallationType()},
{ProductType, ExternalTelemetryProperties.GetProductType()},
Expand Down
20 changes: 20 additions & 0 deletions src/Tests/dotnet.Tests/TelemetryCommonPropertiesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ public void TelemetryCommonPropertiesShouldReturnHashedMachineId()
unitUnderTest.GetTelemetryCommonProperties()["Machine ID"].Should().NotBe("plaintext");
}

[Fact]
public void TelemetryCommonPropertiesShouldReturnDevDeviceId()
{
var unitUnderTest = new TelemetryCommonProperties(getDeviceId: () => "plaintext", userLevelCacheWriter: new NothingCache());
unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"].Should().Be("plaintext");
}

[Fact]
public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotGetMacAddress()
{
Expand All @@ -42,6 +49,19 @@ public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotGetMacAddress(
Guid.TryParse(assignedMachineId, out var _).Should().BeTrue("it should be a guid");
}

[Fact]
public void TelemetryCommonPropertiesShouldEnsureDevDeviceIDIsCached()
{
var unitUnderTest = new TelemetryCommonProperties(userLevelCacheWriter: new NothingCache());
var assignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"];

Guid.TryParse(assignedMachineId, out var _).Should().BeTrue("it should be a guid");
var secondAssignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"];

Guid.TryParse(secondAssignedMachineId, out var _).Should().BeTrue("it should be a guid");
secondAssignedMachineId.Should().Be(assignedMachineId, "it should match the previously assigned guid");
}

[Fact]
public void TelemetryCommonPropertiesShouldReturnHashedMachineIdOld()
{
Expand Down

0 comments on commit 71c1540

Please sign in to comment.