diff --git a/src/dotnet/Telemetry/ExternalTelemetryProperties.cs b/src/dotnet/Telemetry/ExternalTelemetryProperties.cs
new file mode 100644
index 000000000..42a2ad9fc
--- /dev/null
+++ b/src/dotnet/Telemetry/ExternalTelemetryProperties.cs
@@ -0,0 +1,134 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Security;
+using Microsoft.DotNet.PlatformAbstractions;
+using Microsoft.Win32;
+using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+
+namespace Microsoft.DotNet.Cli.Telemetry
+{
+ // Some properties we need for telemetry, that don't yet have suitable
+ // public API
+ internal static class ExternalTelemetryProperties
+ {
+ ///
+ /// For Windows, returns the OS installation type, eg. "Nano Server", "Server Core", "Server", or "Client".
+ /// For Unix, or on error, currently returns empty string.
+ ///
+ internal static string GetInstallationType()
+ {
+ if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Windows)
+ {
+ return "";
+ }
+
+ const string Key = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion";
+ const string ValueName = @"InstallationType";
+
+ try
+ {
+ return (string)Registry.GetValue(Key, ValueName, defaultValue: "");
+ }
+ // Catch everything: this is for telemetry only.
+ catch (Exception e)
+ {
+ Debug.Assert(e is ArgumentException | e is SecurityException | e is InvalidCastException);
+ return "";
+ }
+ }
+
+ [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = false)]
+ private static extern bool GetProductInfo(uint dwOSMajorVersion, uint dwOSMinorVersion, uint dwSpMajorVersion, uint dwSpMinorVersion, out uint pdwReturnedProductType);
+
+ ///
+ /// For Windows, returns the product type, loosely the SKU, as encoded by GetProductInfo().
+ /// For example, Enterprise is "4" (0x4) and Professional is "48" (0x30)
+ /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724358(v=vs.85).aspx for the full list.
+ /// We're not attempting to decode the value on the client side as new Windows releases may add new values.
+ /// For Unix, or on error, returns an empty string.
+ ///
+ internal static string GetProductType()
+ {
+ if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Windows)
+ {
+ return "";
+ }
+
+ try
+ {
+ if (GetProductInfo((uint)Environment.OSVersion.Version.Major, (uint)Environment.OSVersion.Version.Minor, 0, 0, out uint productType))
+ {
+ return productType.ToString("D", CultureInfo.InvariantCulture);
+ }
+ }
+ // Catch everything: this is for telemetry only
+ catch (Exception e)
+ {
+ Debug.Assert(false, $"Unexpected exception from GetProductInfo: ${e.GetType().Name}: ${e.Message}");
+ }
+
+ return "";
+ }
+
+ [DllImport("libc", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr gnu_get_libc_release();
+
+ [DllImport("libc", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr gnu_get_libc_version();
+
+ ///
+ /// If gnulibc is available, returns the release, such as "stable".
+ /// If the libc is musl, currently returns empty string.
+ /// Otherwise returns empty string.
+ ///
+ internal static string GetLibcRelease()
+ {
+ if (RuntimeEnvironment.OperatingSystemPlatform == Platform.Windows)
+ {
+ return "";
+ }
+
+ try
+ {
+ return Marshal.PtrToStringUTF8(gnu_get_libc_release());
+ }
+ // Catch everything: this is for telemetry only
+ catch (Exception e)
+ {
+ Debug.Assert(e is DllNotFoundException || e is EntryPointNotFoundException);
+ return "";
+ }
+ }
+
+ ///
+ /// If gnulibc is available, returns the version, such as "2.22".
+ /// If the libc is musl, currently returns empty string. (In future could run "ldd -version".)
+ /// Otherwise returns empty string.
+ ///
+ internal static string GetLibcVersion()
+ {
+ if (RuntimeEnvironment.OperatingSystemPlatform == Platform.Windows)
+ {
+ return "";
+ }
+
+ try
+ {
+ return Marshal.PtrToStringUTF8(gnu_get_libc_version());
+ }
+ // Catch everything: this is for telemetry only
+ catch (Exception e)
+ {
+ Debug.Assert(e is DllNotFoundException || e is EntryPointNotFoundException);
+ return "";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/dotnet/Telemetry/TelemetryCommonProperties.cs b/src/dotnet/Telemetry/TelemetryCommonProperties.cs
index 6cbf02b47..a37cd70ef 100644
--- a/src/dotnet/Telemetry/TelemetryCommonProperties.cs
+++ b/src/dotnet/Telemetry/TelemetryCommonProperties.cs
@@ -6,10 +6,14 @@ using System.Collections.Generic;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.PlatformAbstractions;
using System.IO;
+using System.Security;
using Microsoft.DotNet.Configurer;
+using Microsoft.Win32;
using System.Linq;
using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
using RuntimeInformation = System.Runtime.InteropServices.RuntimeInformation;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
namespace Microsoft.DotNet.Cli.Telemetry
{
@@ -43,6 +47,10 @@ namespace Microsoft.DotNet.Cli.Telemetry
private const string MachineId = "Machine ID";
private const string DockerContainer = "Docker Container";
private const string KernelVersion = "Kernel Version";
+ private const string InstallationType = "Installation Type";
+ private const string ProductType = "Product Type";
+ private const string LibcRelease = "Libc Release";
+ private const string LibcVersion = "Libc Version";
private const string TelemetryProfileEnvironmentVariable = "DOTNET_CLI_TELEMETRY_PROFILE";
private const string CannotFindMacAddress = "Unknown";
@@ -62,7 +70,11 @@ namespace Microsoft.DotNet.Cli.Telemetry
{DockerContainer, _userLevelCacheWriter.RunWithCache(IsDockerContainerCacheKey, () => _dockerContainerDetector.IsDockerContainer().ToString("G") )},
{CurrentPathHash, _hasher(_getCurrentDirectory())},
{MachineId, _userLevelCacheWriter.RunWithCache(MachineIdCacheKey, GetMachineId)},
- {KernelVersion, GetKernelVersion()}
+ {KernelVersion, GetKernelVersion()},
+ {InstallationType, ExternalTelemetryProperties.GetInstallationType()},
+ {ProductType, ExternalTelemetryProperties.GetProductType()},
+ {LibcRelease, ExternalTelemetryProperties.GetLibcRelease()},
+ {LibcVersion, ExternalTelemetryProperties.GetLibcVersion()}
};
}
diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/UnixOnlyFactAttribute.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/UnixOnlyFactAttribute.cs
new file mode 100644
index 000000000..78be9c856
--- /dev/null
+++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/UnixOnlyFactAttribute.cs
@@ -0,0 +1,19 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.DotNet.PlatformAbstractions;
+using Xunit;
+
+namespace Microsoft.DotNet.Tools.Test.Utilities
+{
+ public class UnixOnlyFactAttribute : FactAttribute
+ {
+ public UnixOnlyFactAttribute()
+ {
+ if (RuntimeEnvironment.OperatingSystemPlatform == Platform.Windows)
+ {
+ this.Skip = "This test requires Unix to run";
+ }
+ }
+ }
+}
diff --git a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
index 7c6c5484d..6404a2b58 100644
--- a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
+++ b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs
@@ -52,6 +52,61 @@ namespace Microsoft.DotNet.Tests
unitUnderTest.GetTelemetryCommonProperties()["Kernel Version"].Should().Be(RuntimeInformation.OSDescription);
}
+ [WindowsOnlyFact]
+ public void TelemetryCommonPropertiesShouldContainWindowsInstallType()
+ {
+ var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
+ unitUnderTest.GetTelemetryCommonProperties()["Installation Type"].Should().NotBeEmpty();
+ }
+
+ [UnixOnlyFact]
+ public void TelemetryCommonPropertiesShouldContainEmptyWindowsInstallType()
+ {
+ var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
+ unitUnderTest.GetTelemetryCommonProperties()["Installation Type"].Should().BeEmpty();
+ }
+
+ [WindowsOnlyFact]
+ public void TelemetryCommonPropertiesShouldContainWindowsProductType()
+ {
+ var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
+ unitUnderTest.GetTelemetryCommonProperties()["Product Type"].Should().NotBeEmpty();
+ }
+
+ [UnixOnlyFact]
+ public void TelemetryCommonPropertiesShouldContainEmptyWindowsProductType()
+ {
+ var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
+ unitUnderTest.GetTelemetryCommonProperties()["Product Type"].Should().BeEmpty();
+ }
+
+ [WindowsOnlyFact]
+ public void TelemetryCommonPropertiesShouldContainEmptyLibcReleaseAndVersion()
+ {
+ var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
+ unitUnderTest.GetTelemetryCommonProperties()["Libc Release"].Should().BeEmpty();
+ unitUnderTest.GetTelemetryCommonProperties()["Libc Version"].Should().BeEmpty();
+ }
+
+ [MacOsOnlyFact]
+ public void TelemetryCommonPropertiesShouldContainEmptyLibcReleaseAndVersion2()
+ {
+ var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
+ unitUnderTest.GetTelemetryCommonProperties()["Libc Release"].Should().BeEmpty();
+ unitUnderTest.GetTelemetryCommonProperties()["Libc Version"].Should().BeEmpty();
+ }
+
+ [LinuxOnlyFact]
+ public void TelemetryCommonPropertiesShouldContainLibcReleaseAndVersion()
+ {
+ if (!RuntimeEnvironment.OperatingSystem.Contains("Alpine", StringComparison.OrdinalIgnoreCase))
+ {
+ var unitUnderTest = new TelemetryCommonProperties(getMACAddress: () => null, userLevelCacheWriter: new NothingCache());
+ unitUnderTest.GetTelemetryCommonProperties()["Libc Release"].Should().NotBeEmpty();
+ unitUnderTest.GetTelemetryCommonProperties()["Libc Version"].Should().NotBeEmpty();
+ }
+ }
+
private class NothingCache : IUserLevelCacheWriter
{
public string RunWithCache(string cacheKey, Func getValueToCache)