Skip to content

Commit 70bb861

Browse files
authored
[release/8.0.1xx] Add Dev Device ID (#43357)
2 parents 1868cec + aab610f commit 70bb861

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Win32;
5+
6+
namespace Microsoft.DotNet.Cli.Telemetry
7+
{
8+
internal static class DeviceIdGetter
9+
{
10+
public static string GetDeviceId()
11+
{
12+
string deviceId = GetCachedDeviceId();
13+
14+
// Check if the device Id is already cached
15+
if (string.IsNullOrEmpty(deviceId))
16+
{
17+
// Generate a new guid
18+
deviceId = Guid.NewGuid().ToString("D").ToLowerInvariant();
19+
20+
// Cache the new device Id
21+
try
22+
{
23+
CacheDeviceId(deviceId);
24+
}
25+
catch
26+
{
27+
// If caching fails, return empty string to avoid sending a non-stored id
28+
deviceId = "";
29+
}
30+
}
31+
32+
return deviceId;
33+
}
34+
35+
private static string GetCachedDeviceId()
36+
{
37+
string deviceId = null;
38+
39+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
40+
{
41+
// Get device Id from Windows registry matching the OS architecture
42+
using (var key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64).OpenSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
43+
{
44+
deviceId = key?.GetValue("deviceid") as string;
45+
}
46+
}
47+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
48+
{
49+
// Get device Id from Linux cache file
50+
string cacheFilePath;
51+
string xdgCacheHome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
52+
if (!string.IsNullOrEmpty(xdgCacheHome))
53+
{
54+
cacheFilePath = Path.Combine(xdgCacheHome, "Microsoft", "DeveloperTools", "deviceid");
55+
}
56+
else
57+
{
58+
cacheFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache", "deviceid");
59+
}
60+
61+
if (File.Exists(cacheFilePath))
62+
{
63+
deviceId = File.ReadAllText(cacheFilePath);
64+
}
65+
}
66+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
67+
{
68+
// Get device Id from macOS cache file
69+
string cacheFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Microsoft", "DeveloperTools", "deviceid");
70+
if (File.Exists(cacheFilePath))
71+
{
72+
deviceId = File.ReadAllText(cacheFilePath);
73+
}
74+
}
75+
76+
return deviceId;
77+
}
78+
79+
private static void CacheDeviceId(string deviceId)
80+
{
81+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
82+
{
83+
// Cache device Id in Windows registry matching the OS architecture
84+
using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64))
85+
{
86+
using(var key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools"))
87+
{
88+
if (key != null)
89+
{
90+
key.SetValue("deviceid", deviceId);
91+
}
92+
}
93+
}
94+
}
95+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
96+
{
97+
// Cache device Id in Linux cache file
98+
string cacheFilePath;
99+
string xdgCacheHome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");
100+
if (!string.IsNullOrEmpty(xdgCacheHome))
101+
{
102+
cacheFilePath = Path.Combine(xdgCacheHome, "Microsoft", "DeveloperTools", "deviceId");
103+
}
104+
else
105+
{
106+
cacheFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache", "deviceid");
107+
}
108+
109+
CreateDirectoryAndWriteToFile(cacheFilePath, deviceId);
110+
}
111+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
112+
{
113+
// Cache device Id in macOS cache file
114+
string cacheFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Microsoft", "DeveloperTools", "deviceid");
115+
116+
CreateDirectoryAndWriteToFile(cacheFilePath, deviceId);
117+
}
118+
}
119+
120+
private static void CreateDirectoryAndWriteToFile(string filePath, string content)
121+
{
122+
string directory = Path.GetDirectoryName(filePath);
123+
if (!Directory.Exists(directory))
124+
{
125+
Directory.CreateDirectory(directory);
126+
}
127+
File.WriteAllText(filePath, content);
128+
}
129+
}
130+
}

src/Cli/dotnet/Telemetry/TelemetryCommonProperties.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ public TelemetryCommonProperties(
1414
Func<string> getCurrentDirectory = null,
1515
Func<string, string> hasher = null,
1616
Func<string> getMACAddress = null,
17+
Func<string> getDeviceId = null,
1718
IDockerContainerDetector dockerContainerDetector = null,
1819
IUserLevelCacheWriter userLevelCacheWriter = null,
1920
ICIEnvironmentDetector ciEnvironmentDetector = null)
2021
{
2122
_getCurrentDirectory = getCurrentDirectory ?? Directory.GetCurrentDirectory;
2223
_hasher = hasher ?? Sha256Hasher.Hash;
2324
_getMACAddress = getMACAddress ?? MacAddressGetter.GetMacAddress;
25+
_getDeviceId = getDeviceId ?? DeviceIdGetter.GetDeviceId;
2426
_dockerContainerDetector = dockerContainerDetector ?? new DockerContainerDetectorForTelemetry();
2527
_userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter();
2628
_ciEnvironmentDetector = ciEnvironmentDetector ?? new CIEnvironmentDetectorForTelemetry();
@@ -31,6 +33,7 @@ public TelemetryCommonProperties(
3133
private Func<string> _getCurrentDirectory;
3234
private Func<string, string> _hasher;
3335
private Func<string> _getMACAddress;
36+
private Func<string> _getDeviceId;
3437
private IUserLevelCacheWriter _userLevelCacheWriter;
3538
private const string OSVersion = "OS Version";
3639
private const string OSPlatform = "OS Platform";
@@ -40,6 +43,7 @@ public TelemetryCommonProperties(
4043
private const string ProductVersion = "Product Version";
4144
private const string TelemetryProfile = "Telemetry Profile";
4245
private const string CurrentPathHash = "Current Path Hash";
46+
private const string DeviceId = "devdeviceid";
4347
private const string MachineId = "Machine ID";
4448
private const string MachineIdOld = "Machine ID Old";
4549
private const string DockerContainer = "Docker Container";
@@ -81,6 +85,7 @@ public Dictionary<string, string> GetTelemetryCommonProperties()
8185
CliFolderPathCalculator.DotnetUserProfileFolderPath,
8286
$"{MachineIdCacheKey}.v1.dotnetUserLevelCache"),
8387
GetMachineId)},
88+
{DeviceId, _getDeviceId()},
8489
{KernelVersion, GetKernelVersion()},
8590
{InstallationType, ExternalTelemetryProperties.GetInstallationType()},
8691
{ProductType, ExternalTelemetryProperties.GetProductType()},

src/Tests/dotnet.Tests/TelemetryCommonPropertiesTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ public void TelemetryCommonPropertiesShouldReturnHashedMachineId()
3333
unitUnderTest.GetTelemetryCommonProperties()["Machine ID"].Should().NotBe("plaintext");
3434
}
3535

36+
[Fact]
37+
public void TelemetryCommonPropertiesShouldReturnDevDeviceId()
38+
{
39+
var unitUnderTest = new TelemetryCommonProperties(getDeviceId: () => "plaintext", userLevelCacheWriter: new NothingCache());
40+
unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"].Should().Be("plaintext");
41+
}
42+
3643
[Fact]
3744
public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotGetMacAddress()
3845
{
@@ -42,6 +49,19 @@ public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotGetMacAddress(
4249
Guid.TryParse(assignedMachineId, out var _).Should().BeTrue("it should be a guid");
4350
}
4451

52+
[Fact]
53+
public void TelemetryCommonPropertiesShouldEnsureDevDeviceIDIsCached()
54+
{
55+
var unitUnderTest = new TelemetryCommonProperties(userLevelCacheWriter: new NothingCache());
56+
var assignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"];
57+
58+
Guid.TryParse(assignedMachineId, out var _).Should().BeTrue("it should be a guid");
59+
var secondAssignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"];
60+
61+
Guid.TryParse(secondAssignedMachineId, out var _).Should().BeTrue("it should be a guid");
62+
secondAssignedMachineId.Should().Be(assignedMachineId, "it should match the previously assigned guid");
63+
}
64+
4565
[Fact]
4666
public void TelemetryCommonPropertiesShouldReturnHashedMachineIdOld()
4767
{

0 commit comments

Comments
 (0)