diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index dbbbf7286a..15e8bfe6a9 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -619,7 +619,7 @@ namespace Microsoft.PowerShell.Commands // avoid double reporting for WinCompat modules that go through CommandDiscovery\AutoloadSpecifiedModule if (!foundModule.IsWindowsPowerShellCompatModule) { - ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name, foundModule.Version?.ToString()); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule); #if LEGACYTELEMETRY TelemetryAPI.ReportModuleLoad(foundModule); #endif @@ -893,7 +893,7 @@ namespace Microsoft.PowerShell.Commands if (foundModule != null) { - ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name, foundModule.Version?.ToString()); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule); SetModuleBaseForEngineModules(foundModule.Name, this.Context); } @@ -935,7 +935,7 @@ namespace Microsoft.PowerShell.Commands // Send telemetry on the imported modules foreach (PSModuleInfo moduleInfo in remotelyImportedModules) { - ApplicationInsightsTelemetry.SendModuleTelemetryMetric(usingWinCompat ? TelemetryType.WinCompatModuleLoad : TelemetryType.ModuleLoad, moduleInfo.Name, moduleInfo.Version?.ToString()); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(usingWinCompat ? TelemetryType.WinCompatModuleLoad : TelemetryType.ModuleLoad, moduleInfo); } return remotelyImportedModules; @@ -1866,7 +1866,7 @@ namespace Microsoft.PowerShell.Commands // of doing Get-Module -list foreach (PSModuleInfo module in ModuleInfo) { - ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, module.Name, module.Version?.ToString()); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, module); RemoteDiscoveryHelper.DispatchModuleInfoProcessing( module, localAction: () => @@ -1926,7 +1926,7 @@ namespace Microsoft.PowerShell.Commands ImportModule_RemotelyViaPsrpSession(importModuleOptions, null, FullyQualifiedName, this.PSSession); foreach (ModuleSpecification modulespec in FullyQualifiedName) { - ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name, modulespec.Version?.ToString()); + ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name); } } else if (this.ParameterSetName.Equals(ParameterSet_ViaWinCompat, StringComparison.OrdinalIgnoreCase) diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index a1c3d324b5..5588cb103a 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -5,11 +5,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Management.Automation; using System.Runtime.InteropServices; +using System.Security.AccessControl; using System.Threading; using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Metrics; using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; @@ -124,6 +127,9 @@ namespace Microsoft.PowerShell.Telemetry // Use '0.0' as the string for an anonymous module version private const string AnonymousVersion = "0.0"; + // Use 'n/a' as the string when there's no tag to report + private const string NoTag = "n/a"; + // the telemetry failure string private const string _telemetryFailure = "TELEMETRY_FAILURE"; @@ -140,9 +146,11 @@ namespace Microsoft.PowerShell.Telemetry private static int s_startupEventSent = 0; /// Use a hashset for quick lookups. - /// We send telemetry only a known set of modules. - /// If it's not in the list (initialized in the static constructor), then we report anonymous. + /// We send telemetry only a known set of modules and tags. + /// If it's not in the list (initialized in the static constructor), then we report anonymous + /// or don't report anything (in the case of tags). private static readonly HashSet s_knownModules; + private static readonly HashSet s_knownModuleTags; /// Gets a value indicating whether telemetry can be sent. public static bool CanSendTelemetry { get; private set; } = false; @@ -601,6 +609,12 @@ namespace Microsoft.PowerShell.Telemetry "xWindowsUpdate", }; + // use a hashset when looking for module names, it should be quicker than a string comparison + s_knownModuleTags = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "CrescendoBuilt", + }; + s_uniqueUserIdentifier = GetUniqueIdentifier().ToString(); } } @@ -671,6 +685,41 @@ namespace Microsoft.PowerShell.Telemetry return defaultValue; } + /// + /// Send module load telemetry as a metric. + /// For modules we send the module name (if allowed), and the version. + /// Some modules (CIM) will continue use the string alternative method. + /// + /// The type of telemetry that we'll be sending. + /// The module to report. If it is not allowed, then it is set to 'anonymous'. + internal static void SendModuleTelemetryMetric(TelemetryType telemetryType, PSModuleInfo moduleInfo) + { + if (!CanSendTelemetry) + { + return; + } + + // Package up the module name, version, and known tags as a metric. + // Note that the allowed tags will be a comma separated list which will need to + // be handled in the telemetry query. + try + { + string allowedModuleName = GetModuleName(moduleInfo.Name); + string allowedModuleVersion = allowedModuleName == Anonymous ? AnonymousVersion : moduleInfo.Version?.ToString(); + var allowedModuleTags = moduleInfo.Tags.Where(t => s_knownModuleTags.Contains(t)).Distinct(); + string allowedModuleTagString = allowedModuleTags.Any() ? string.Join(',', allowedModuleTags) : NoTag; + + s_telemetryClient. + GetMetric(new MetricIdentifier(string.Empty, telemetryType.ToString(), "uuid", "SessionId", "ModuleName", "Version", "Tag")). + TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, allowedModuleName, allowedModuleVersion, allowedModuleTagString); + } + catch + { + // Ignore errors. + } + + } + /// /// Send module load telemetry as a metric. /// For modules we send the module name (if allowed), and the version. @@ -678,8 +727,7 @@ namespace Microsoft.PowerShell.Telemetry /// /// The type of telemetry that we'll be sending. /// The module name to report. If it is not allowed, then it is set to 'anonymous'. - /// The module version to report. The default value is the anonymous version '0.0.0.0'. - internal static void SendModuleTelemetryMetric(TelemetryType telemetryType, string moduleName, string moduleVersion = AnonymousVersion) + internal static void SendModuleTelemetryMetric(TelemetryType telemetryType, string moduleName) { if (!CanSendTelemetry) { @@ -689,8 +737,7 @@ namespace Microsoft.PowerShell.Telemetry try { string allowedModuleName = GetModuleName(moduleName); - string allowedModuleVersion = allowedModuleName == Anonymous ? AnonymousVersion : moduleVersion; - s_telemetryClient.GetMetric(telemetryType.ToString(), "uuid", "SessionId", "ModuleName", "Version").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, allowedModuleName, allowedModuleVersion); + s_telemetryClient.GetMetric(telemetryType.ToString(), "uuid", "SessionId", "ModuleName", "Version").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, allowedModuleName, AnonymousVersion); } catch {