MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Diagnostics;7using System.IO;8using System.Linq;9using System.Management.Automation;10using System.Runtime.InteropServices;11using System.Threading;12 13using Microsoft.ApplicationInsights;14using Microsoft.ApplicationInsights.Metrics;15using Microsoft.ApplicationInsights.Channel;16using Microsoft.ApplicationInsights.Extensibility;17using Microsoft.ApplicationInsights.Extensibility.Implementation;18 19namespace Microsoft.PowerShell.Telemetry20{21 /// <summary>22 /// The category of telemetry.23 /// </summary>24 internal enum TelemetryType25 {26 /// <summary>27 /// Telemetry of the application type (cmdlet, script, etc).28 /// </summary>29 ApplicationType,30 31 /// <summary>32 /// Send telemetry when we load a module, only module names in the s_knownModules list33 /// will be reported, otherwise it will be "anonymous".34 /// </summary>35 ModuleLoad,36 37 /// <summary>38 /// Send telemetry when we load a module using Windows compatibility feature, only module names in the s_knownModules list39 /// will be reported, otherwise it will be "anonymous".40 /// </summary>41 WinCompatModuleLoad,42 43 /// <summary>44 /// Send telemetry for experimental module feature deactivation.45 /// All experimental engine features will be have telemetry.46 /// </summary>47 ExperimentalEngineFeatureDeactivation,48 49 /// <summary>50 /// Send telemetry for experimental module feature activation.51 /// All experimental engine features will be have telemetry.52 /// </summary>53 ExperimentalEngineFeatureActivation,54 55 /// <summary>56 /// Send telemetry for an experimental feature when use.57 /// </summary>58 ExperimentalFeatureUse,59 60 /// <summary>61 /// Send telemetry for experimental module feature deactivation.62 /// Experimental module features will send telemetry based on the module it is in.63 /// If we send telemetry for the module, we will also do so for any experimental feature64 /// in that module.65 /// </summary>66 ExperimentalModuleFeatureDeactivation,67 68 /// <summary>69 /// Send telemetry for experimental module feature activation.70 /// Experimental module features will send telemetry based on the module it is in.71 /// If we send telemetry for the module, we will also do so for any experimental feature72 /// in that module.73 /// </summary>74 ExperimentalModuleFeatureActivation,75 76 /// <summary>77 /// Send telemetry for each PowerShell.Create API.78 /// </summary>79 PowerShellCreate,80 81 /// <summary>82 /// Remote session creation.83 /// </summary>84 RemoteSessionOpen,85 86 /// <summary>87 /// Send telemetry for a stable feature when used.88 /// By making a distinction between this and experimental feature use, it will make89 /// queries much easier.90 /// </summary>91 FeatureUse,92 }93 94 /// <summary>95 /// Set up the telemetry initializer to mask the platform specific names.96 /// </summary>97 internal class NameObscurerTelemetryInitializer : ITelemetryInitializer98 {99 // Report the platform name information as "na".100 private const string _notavailable = "na";101 102 /// <summary>103 /// Initialize properties we are obscuring to "na".104 /// </summary>105 /// <param name="telemetry">The instance of our telemetry.</param>106 public void Initialize(ITelemetry telemetry)107 {108 telemetry.Context.Cloud.RoleName = _notavailable;109 telemetry.Context.GetInternalContext().NodeName = _notavailable;110 telemetry.Context.Cloud.RoleInstance = _notavailable;111 }112 }113 114 /// <summary>115 /// Send up telemetry for startup.116 /// </summary>117 public static class ApplicationInsightsTelemetry118 {119 // The string for SubsystermRegistration120 internal const string s_subsystemRegistration = "Subsystem.Registration";121 122 // If this env var is true, yes, or 1, telemetry will NOT be sent.123 private const string _telemetryOptoutEnvVar = "POWERSHELL_TELEMETRY_OPTOUT";124 125 // PSCoreInsight2 telemetry key126 // private const string _psCoreTelemetryKey = "ee4b2115-d347-47b0-adb6-b19c2c763808"; // Production127 private const string _psCoreTelemetryKey = "d26a5ef4-d608-452c-a6b8-a4a55935f70d"; // V7 Preview 3128 129 // In the event there is a problem in creating the node identifier file, use the default identifier.130 // This can happen if we are running in a system which has a read-only filesystem.131 private static readonly Guid _defaultNodeIdentifier = new Guid("2f998828-3f4a-4741-bf50-d11c6be42f50");132 133 // Use "anonymous" as the string to return when you can't report a name134 private const string Anonymous = "anonymous";135 136 // Use '0.0' as the string for an anonymous module version137 private const string AnonymousVersion = "0.0";138 139 // Use 'n/a' as the string when there's no tag to report140 private const string NoTag = "n/a";141 142 // the telemetry failure string143 private const string _telemetryFailure = "TELEMETRY_FAILURE";144 145 // Telemetry client to be reused when we start sending more telemetry146 private static TelemetryClient s_telemetryClient { get; }147 148 // the unique identifier for the user, when we start we149 private static string s_uniqueUserIdentifier { get; }150 151 // the session identifier152 private static string s_sessionId { get; }153 154 // private semaphore to determine whether we sent the startup telemetry event155 private static int s_startupEventSent = 0;156 157 /// Use a hashset for quick lookups.158 /// We send telemetry only a known set of modules and tags.159 /// If it's not in the list (initialized in the static constructor), then we report anonymous160 /// or don't report anything (in the case of tags).161 private static readonly HashSet<string> s_knownModules;162 private static readonly HashSet<string> s_knownModuleTags;163 164 private static readonly HashSet<string> s_knownSubsystemNames;165 166 private static readonly string s_uuidPath;167 168 /// <summary>Gets a value indicating whether telemetry can be sent.</summary>169 public static bool CanSendTelemetry { get; private set; }170 171 /// <summary>172 /// Initializes static members of the <see cref="ApplicationInsightsTelemetry"/> class.173 /// Static constructor determines whether telemetry is to be sent, and then174 /// sets the telemetry key and set the telemetry delivery mode.175 /// Creates the session ID and initializes the HashSet of known module names.176 /// Gets or constructs the unique identifier.177 /// </summary>178 static ApplicationInsightsTelemetry()179 {180 // If we can't send telemetry, there's no reason to do any of this181 CanSendTelemetry = !Utils.GetEnvironmentVariableAsBool(name: _telemetryOptoutEnvVar, defaultValue: false)182 && Platform.TryDeriveFromCache("telemetry.uuid", out s_uuidPath);183 184 if (!CanSendTelemetry)185 {186 return;187 }188 189 s_sessionId = Guid.NewGuid().ToString();190 TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault();191 configuration.ConnectionString = "InstrumentationKey=" + _psCoreTelemetryKey;192 193 // Set this to true to reduce latency during development194 configuration.TelemetryChannel.DeveloperMode = false;195 196 // Be sure to obscure any information about the client node name.197 configuration.TelemetryInitializers.Add(new NameObscurerTelemetryInitializer());198 199 s_telemetryClient = new TelemetryClient(configuration);200 201 // use a hashset when looking for module names, it should be quicker than a string comparison202 s_knownModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase)203 {204 "AADRM",205 "activedirectory",206 "adcsadministration",207 "adcsdeployment",208 "addsadministration",209 "addsdeployment",210 "adfs",211 "adrms",212 "adrmsadmin",213 "agpm",214 "AIShell",215 "appbackgroundtask",216 "applocker",217 "appv",218 "appvclient",219 "appvsequencer",220 "appvserver",221 "appx",222 "assignedaccess",223 "Az",224 "Az.Accounts",225 "Az.Advisor",226 "Az.Aks",227 "Az.AlertsManagement",228 "Az.AnalysisServices",229 "Az.ApiManagement",230 "Az.ApplicationInsights",231 "Az.Attestation",232 "Az.Automation",233 "Az.Batch",234 "Az.Billing",235 "Az.Blueprint",236 "Az.Cdn",237 "Az.CognitiveServices",238 "Az.Compute",239 "Az.ContainerInstance",240 "Az.ContainerRegistry",241 "Az.DataBox",242 "Az.DataFactory",243 "Az.DataLakeAnalytics",244 "Az.DataLakeStore",245 "Az.DataMigration",246 "Az.DataShare",247 "Az.DeploymentManager",248 "Az.DeviceProvisioningServices",249 "Az.DevSpaces",250 "Az.DevTestLabs",251 "Az.Dns",252 "Az.EventGrid",253 "Az.EventHub",254 "Az.FrontDoor",255 "Az.GuestConfiguration",256 "Az.HDInsight",257 "Az.HealthcareApis",258 "Az.IotCentral",259 "Az.IotHub",260 "Az.KeyVault",261 "Az.Kusto",262 "Az.LogicApp",263 "Az.MachineLearning",264 "Az.ManagedServiceIdentity",265 "Az.ManagedServices",266 "Az.ManagementPartner",267 "Az.Maps",268 "Az.MarketplaceOrdering",269 "Az.Media",270 "Az.MixedReality",271 "Az.Monitor",272 "Az.NetAppFiles",273 "Az.Network",274 "Az.NotificationHubs",275 "Az.OperationalInsights",276 "Az.Peering",277 "Az.PolicyInsights",278 "Az.PowerBIEmbedded",279 "Az.PrivateDns",280 "Az.RecoveryServices",281 "Az.RedisCache",282 "Az.Relay",283 "Az.Reservations",284 "Az.ResourceGraph",285 "Az.Resources",286 "Az.Search",287 "Az.Security",288 "Az.ServiceBus",289 "Az.ServiceFabric",290 "Az.SignalR",291 "Az.Sql",292 "Az.Storage",293 "Az.StorageSync",294 "Az.StorageTable",295 "Az.StreamAnalytics",296 "Az.Subscription",297 "Az.Tools.Predictor",298 "Az.TrafficManager",299 "Az.Websites",300 "Azs.Azurebridge.Admin",301 "Azs.Backup.Admin",302 "Azs.Commerce.Admin",303 "Azs.Compute.Admin",304 "Azs.Fabric.Admin",305 "Azs.Gallery.Admin",306 "Azs.Infrastructureinsights.Admin",307 "Azs.Keyvault.Admin",308 "Azs.Network.Admin",309 "Azs.Storage.Admin",310 "Azs.Subscriptions",311 "Azs.Subscriptions.Admin",312 "Azs.Update.Admin",313 "AzStorageTable",314 "Azure",315 "Azure.AnalysisServices",316 "Azure.Storage",317 "AzureAD",318 "AzureInformationProtection",319 "AzureRM.Aks",320 "AzureRM.AnalysisServices",321 "AzureRM.ApiManagement",322 "AzureRM.ApplicationInsights",323 "AzureRM.Automation",324 "AzureRM.Backup",325 "AzureRM.Batch",326 "AzureRM.Billing",327 "AzureRM.Cdn",328 "AzureRM.CognitiveServices",329 "AzureRm.Compute",330 "AzureRM.Compute.ManagedService",331 "AzureRM.Consumption",332 "AzureRM.ContainerInstance",333 "AzureRM.ContainerRegistry",334 "AzureRM.DataFactories",335 "AzureRM.DataFactoryV2",336 "AzureRM.DataLakeAnalytics",337 "AzureRM.DataLakeStore",338 "AzureRM.DataMigration",339 "AzureRM.DeploymentManager",340 "AzureRM.DeviceProvisioningServices",341 "AzureRM.DevSpaces",342 "AzureRM.DevTestLabs",343 "AzureRm.Dns",344 "AzureRM.EventGrid",345 "AzureRM.EventHub",346 "AzureRM.FrontDoor",347 "AzureRM.HDInsight",348 "AzureRm.Insights",349 "AzureRM.IotCentral",350 "AzureRM.IotHub",351 "AzureRm.Keyvault",352 "AzureRM.LocationBasedServices",353 "AzureRM.LogicApp",354 "AzureRM.MachineLearning",355 "AzureRM.MachineLearningCompute",356 "AzureRM.ManagedServiceIdentity",357 "AzureRM.ManagementPartner",358 "AzureRM.Maps",359 "AzureRM.MarketplaceOrdering",360 "AzureRM.Media",361 "AzureRM.Network",362 "AzureRM.NotificationHubs",363 "AzureRM.OperationalInsights",364 "AzureRM.PolicyInsights",365 "AzureRM.PowerBIEmbedded",366 "AzureRM.Profile",367 "AzureRM.RecoveryServices",368 "AzureRM.RecoveryServices.Backup",369 "AzureRM.RecoveryServices.SiteRecovery",370 "AzureRM.RedisCache",371 "AzureRM.Relay",372 "AzureRM.Reservations",373 "AzureRM.ResourceGraph",374 "AzureRM.Resources",375 "AzureRM.Scheduler",376 "AzureRM.Search",377 "AzureRM.Security",378 "AzureRM.ServerManagement",379 "AzureRM.ServiceBus",380 "AzureRM.ServiceFabric",381 "AzureRM.SignalR",382 "AzureRM.SiteRecovery",383 "AzureRM.Sql",384 "AzureRm.Storage",385 "AzureRM.StorageSync",386 "AzureRM.StreamAnalytics",387 "AzureRM.Subscription",388 "AzureRM.Subscription.Preview",389 "AzureRM.Tags",390 "AzureRM.TrafficManager",391 "AzureRm.UsageAggregates",392 "AzureRm.Websites",393 "AzureRmStorageTable",394 "bestpractices",395 "bitlocker",396 "bitstransfer",397 "booteventcollector",398 "branchcache",399 "CimCmdlets",400 "clusterawareupdating",401 "CompatPowerShellGet",402 "configci",403 "ConfigurationManager",404 "CompletionPredictor",405 "DataProtectionManager",406 "dcbqos",407 "deduplication",408 "defender",409 "devicehealthattestation",410 "dfsn",411 "dfsr",412 "dhcpserver",413 "directaccessclient",414 "directaccessclientcomponent",415 "directaccessclientcomponents",416 "dism",417 "dnsclient",418 "dnsserver",419 "ElasticDatabaseJobs",420 "EventTracingManagement",421 "failoverclusters",422 "fileserverresourcemanager",423 "FIMAutomation",424 "GPRegistryPolicy",425 "grouppolicy",426 "hardwarecertification",427 "hcs",428 "hgsattestation",429 "hgsclient",430 "hgsdiagnostics",431 "hgskeyprotection",432 "hgsserver",433 "hnvdiagnostics",434 "hostcomputeservice",435 "hpc",436 "HPC.ACM",437 "HPC.ACM.API.PS",438 "HPCPack2016",439 "hyper-v",440 "IISAdministration",441 "international",442 "ipamserver",443 "iscsi",444 "iscsitarget",445 "ISE",446 "kds",447 "Microsoft.MBAM",448 "Microsoft.MEDV",449 "MgmtSvcAdmin",450 "MgmtSvcConfig",451 "MgmtSvcMySql",452 "MgmtSvcSqlServer",453 "Microsoft.AzureStack.ReadinessChecker",454 "Microsoft.Crm.PowerShell",455 "Microsoft.DiagnosticDataViewer",456 "Microsoft.DirectoryServices.MetadirectoryServices.Config",457 "Microsoft.Dynamics.Nav.Apps.Management",458 "Microsoft.Dynamics.Nav.Apps.Tools",459 "Microsoft.Dynamics.Nav.Ide",460 "Microsoft.Dynamics.Nav.Management",461 "Microsoft.Dynamics.Nav.Model.Tools",462 "Microsoft.Dynamics.Nav.Model.Tools.Crm",463 "Microsoft.EnterpriseManagement.Warehouse.Cmdlets",464 "Microsoft.Medv.Administration.Commands.WorkspacePackager",465 "Microsoft.PowerApps.Checker.PowerShell",466 "Microsoft.PowerShell.Archive",467 "Microsoft.PowerShell.ConsoleGuiTools",468 "Microsoft.PowerShell.Core",469 "Microsoft.PowerShell.Crescendo",470 "Microsoft.PowerShell.Diagnostics",471 "Microsoft.PowerShell.Host",472 "Microsoft.PowerShell.LocalAccounts",473 "Microsoft.PowerShell.Management",474 "Microsoft.PowerShell.ODataUtils",475 "Microsoft.PowerShell.Operation.Validation",476 "Microsoft.PowerShell.PSAdapter",477 "Microsoft.PowerShell.PSResourceGet",478 "Microsoft.PowerShell.RemotingTools",479 "Microsoft.PowerShell.SecretManagement",480 "Microsoft.PowerShell.SecretStore",481 "Microsoft.PowerShell.Security",482 "Microsoft.PowerShell.TextUtility",483 "Microsoft.PowerShell.Utility",484 "Microsoft.SharePoint.Powershell",485 "Microsoft.SystemCenter.ServiceManagementAutomation",486 "Microsoft.Windows.ServerManager.Migration",487 "Microsoft.WSMan.Management",488 "Microsoft.Xrm.OnlineManagementAPI",489 "Microsoft.Xrm.Tooling.CrmConnector.PowerShell",490 "Microsoft.Xrm.Tooling.PackageDeployment",491 "Microsoft.Xrm.Tooling.PackageDeployment.Powershell",492 "Microsoft.Xrm.Tooling.Testing",493 "MicrosoftPowerBIMgmt",494 "MicrosoftPowerBIMgmt.Data",495 "MicrosoftPowerBIMgmt.Profile",496 "MicrosoftPowerBIMgmt.Reports",497 "MicrosoftPowerBIMgmt.Workspaces",498 "MicrosoftStaffHub",499 "MicrosoftTeams",500 "MIMPAM",501 "mlSqlPs",502 "MMAgent",503 "MPIO",504 "MsDtc",505 "MSMQ",506 "MSOnline",507 "MSOnlineBackup",508 "WmsCmdlets",509 "WmsCmdlets3",510 "NanoServerImageGenerator",511 "NAVWebClientManagement",512 "NetAdapter",513 "NetConnection",514 "NetEventPacketCapture",515 "Netlbfo",516 "Netldpagent",517 "NetNat",518 "Netqos",519 "NetSecurity",520 "NetSwitchtTeam",521 "Nettcpip",522 "Netwnv",523 "NetworkConnectivity",524 "NetworkConnectivityStatus",525 "NetworkController",526 "NetworkControllerDiagnostics",527 "NetworkloadBalancingClusters",528 "NetworkSwitchManager",529 "NetworkTransition",530 "NFS",531 "NPS",532 "OfficeWebapps",533 "OperationsManager",534 "PackageManagement",535 "PartnerCenter",536 "pcsvdevice",537 "pef",538 "Pester",539 "pkiclient",540 "platformidentifier",541 "pnpdevice",542 "PowerShellEditorServices",543 "PowerShellGet",544 "powershellwebaccess",545 "printmanagement",546 "ProcessMitigations",547 "provisioning",548 "PSDesiredStateConfiguration",549 "PSDiagnostics",550 "PSReadLine",551 "PSScheduledJob",552 "PSScriptAnalyzer",553 "PSWorkflow",554 "PSWorkflowUtility",555 "RemoteAccess",556 "RemoteDesktop",557 "RemoteDesktopServices",558 "ScheduledTasks",559 "Secureboot",560 "ServerCore",561 "ServerManager",562 "ServerManagerTasks",563 "ServerMigrationcmdlets",564 "ServiceFabric",565 "Microsoft.Online.SharePoint.PowerShell",566 "shieldedvmdatafile",567 "shieldedvmprovisioning",568 "shieldedvmtemplate",569 "SkypeOnlineConnector",570 "SkypeForBusinessHybridHealth",571 "smbshare",572 "smbwitness",573 "smisconfig",574 "softwareinventorylogging",575 "SPFAdmin",576 "Microsoft.SharePoint.MigrationTool.PowerShell",577 "sqlps",578 "SqlServer",579 "StartLayout",580 "StartScreen",581 "Storage",582 "StorageDsc",583 "storageqos",584 "Storagereplica",585 "Storagespaces",586 "Syncshare",587 "System.Center.Service.Manager",588 "TLS",589 "TroubleshootingPack",590 "TrustedPlatformModule",591 "UEV",592 "UpdateServices",593 "UserAccessLogging",594 "vamt",595 "VirtualMachineManager",596 "vpnclient",597 "WasPSExt",598 "WDAC",599 "WDS",600 "WebAdministration",601 "WebAdministrationDsc",602 "WebApplicationProxy",603 "WebSites",604 "Whea",605 "WhiteboardAdmin",606 "WindowsDefender",607 "WindowsDefenderDsc",608 "WindowsDeveloperLicense",609 "WindowsDiagnosticData",610 "WindowsErrorReporting",611 "WindowServerRackup",612 "WindowsSearch",613 "WindowsServerBackup",614 "WindowsUpdate",615 "WinGetCommandNotFound",616 "wsscmdlets",617 "wsssetup",618 "wsus",619 "xActiveDirectory",620 "xBitLocker",621 "xDefender",622 "xDhcpServer",623 "xDismFeature",624 "xDnsServer",625 "xHyper-V",626 "xHyper-VBackup",627 "xPSDesiredStateConfiguration",628 "xSmbShare",629 "xSqlPs",630 "xStorage",631 "xWebAdministration",632 "xWindowsUpdate",633 };634 635 // use a hashset when looking for module names, it should be quicker than a string comparison636 s_knownModuleTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase)637 {638 "CrescendoBuilt",639 };640 641 s_uniqueUserIdentifier = GetUniqueIdentifier().ToString();642 s_knownSubsystemNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)643 {644 "Completion",645 "General Feedback",646 "Windows Package Manager - WinGet",647 "Az Predictor"648 };649 }650 651 /// <summary>652 /// Send module load telemetry as a metric.653 /// For modules we send the module name (if allowed), and the version.654 /// Some modules (CIM) will continue use the string alternative method.655 /// </summary>656 /// <param name="telemetryType">The type of telemetry that we'll be sending.</param>657 /// <param name="moduleInfo">The module to report. If it is not allowed, then it is set to 'anonymous'.</param>658 internal static void SendModuleTelemetryMetric(TelemetryType telemetryType, PSModuleInfo moduleInfo)659 {660 if (!CanSendTelemetry)661 {662 return;663 }664 665 // Package up the module name, version, and known tags as a metric.666 // Note that the allowed tags will be a comma separated list which will need to667 // be handled in the telemetry query.668 try669 {670 string allowedModuleName = GetModuleName(moduleInfo.Name);671 string allowedModuleVersion = allowedModuleName == Anonymous ? AnonymousVersion : moduleInfo.Version?.ToString();672 var allowedModuleTags = moduleInfo.Tags.Where(t => s_knownModuleTags.Contains(t)).Distinct();673 string allowedModuleTagString = allowedModuleTags.Any() ? string.Join(',', allowedModuleTags) : NoTag;674 675 s_telemetryClient.676 GetMetric(new MetricIdentifier(string.Empty, telemetryType.ToString(), "uuid", "SessionId", "ModuleName", "Version", "Tag")).677 TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, allowedModuleName, allowedModuleVersion, allowedModuleTagString);678 }679 catch680 {681 // Ignore errors.682 }683 684 }685 686 /// <summary>687 /// Send module load telemetry as a metric.688 /// For modules we send the module name (if allowed), and the version.689 /// Some modules (CIM) will continue use the string alternative method.690 /// </summary>691 /// <param name="telemetryType">The type of telemetry that we'll be sending.</param>692 /// <param name="moduleName">The module name to report. If it is not allowed, then it is set to 'anonymous'.</param>693 internal static void SendModuleTelemetryMetric(TelemetryType telemetryType, string moduleName)694 {695 if (!CanSendTelemetry)696 {697 return;698 }699 700 try701 {702 string allowedModuleName = GetModuleName(moduleName);703 s_telemetryClient.GetMetric(telemetryType.ToString(), "uuid", "SessionId", "ModuleName", "Version").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, allowedModuleName, AnonymousVersion);704 }705 catch706 {707 // Ignore errors.708 }709 }710 711 /// <summary>712 /// Send telemetry as a metric.713 /// </summary>714 /// <param name="metricId">The type of telemetry that we'll be sending.</param>715 /// <param name="data">The specific details about the telemetry.</param>716 /// <param name="value">The count of instances for the telemetry payload.</param>717 internal static void SendTelemetryMetric(TelemetryType metricId, string data, double value = 1.0)718 {719 if (!CanSendTelemetry)720 {721 return;722 }723 724 // These should be handled by SendModuleTelemetryMetric.725 Debug.Assert(metricId != TelemetryType.ModuleLoad, "ModuleLoad should be handled by SendModuleTelemetryMetric.");726 Debug.Assert(metricId != TelemetryType.WinCompatModuleLoad, "WinCompatModuleLoad should be handled by SendModuleTelemetryMetric.");727 728 string metricName = metricId.ToString();729 try730 {731 switch (metricId)732 {733 case TelemetryType.ApplicationType:734 case TelemetryType.PowerShellCreate:735 case TelemetryType.RemoteSessionOpen:736 case TelemetryType.ExperimentalEngineFeatureActivation:737 case TelemetryType.ExperimentalEngineFeatureDeactivation:738 case TelemetryType.ExperimentalFeatureUse:739 case TelemetryType.FeatureUse:740 s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: value, s_uniqueUserIdentifier, s_sessionId, data);741 break;742 case TelemetryType.ExperimentalModuleFeatureActivation:743 case TelemetryType.ExperimentalModuleFeatureDeactivation:744 string experimentalFeatureName = GetExperimentalFeatureName(data);745 s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: value, s_uniqueUserIdentifier, s_sessionId, experimentalFeatureName);746 break;747 }748 }749 catch750 {751 // do nothing, telemetry can't be sent752 // don't send the panic telemetry as if we have failed above, it will likely fail here.753 }754 }755 756 /// <summary>757 /// Send additional information about an feature as it is used.758 /// </summary>759 /// <param name="featureName">The name of the feature.</param>760 /// <param name="detail">The details about the feature use.</param>761 /// <param name="value">The value to report when sending the payload.</param>762 internal static void SendUseTelemetry(string featureName, string detail, double value = 1.0)763 {764 if (!CanSendTelemetry)765 {766 return;767 }768 769 // keep payload small770 if (featureName is null || detail is null || featureName.Length > 33 || detail.Length > 33)771 {772 return;773 }774 775 if (string.Compare(featureName, s_subsystemRegistration, true) == 0)776 {777 SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, GetSubsystemName(detail)), value);778 }779 else780 {781 SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, detail), value);782 }783 }784 785 /// <summary>786 /// Send additional information about an experimental feature as it is used.787 /// </summary>788 /// <param name="featureName">The name of the experimental feature.</param>789 /// <param name="detail">The details about the experimental feature use.</param>790 internal static void SendExperimentalUseData(string featureName, string detail)791 {792 if (!CanSendTelemetry)793 {794 return;795 }796 797 SendTelemetryMetric(TelemetryType.ExperimentalFeatureUse, string.Join(":", featureName, detail));798 }799 800 // Get the experimental feature name. If we can report it, we'll return the name of the feature, otherwise, we'll return "anonymous"801 private static string GetExperimentalFeatureName(string featureNameToValidate)802 {803 // An experimental feature in a module is guaranteed to start with the module name804 // we can strip out the text past the last '.' as the text before that will be the ModuleName805 int lastDotIndex = featureNameToValidate.LastIndexOf('.');806 string moduleName = featureNameToValidate.Substring(0, lastDotIndex);807 if (s_knownModules.Contains(moduleName))808 {809 return featureNameToValidate;810 }811 812 return Anonymous;813 }814 815 // Get the module name. If we can report it, we'll return the name, otherwise, we'll return the string "anonymous"816 private static string GetSubsystemName(string subsystemNameToValidate)817 {818 if (s_knownSubsystemNames.Contains(subsystemNameToValidate))819 {820 return subsystemNameToValidate;821 }822 823 return Anonymous;824 }825 826 // Get the module name. If we can report it, we'll return the name, otherwise, we'll return anonymous.827 private static string GetModuleName(string moduleNameToValidate)828 {829 if (s_knownModules.Contains(moduleNameToValidate))830 {831 return moduleNameToValidate;832 }833 834 return Anonymous;835 }836 837 /// <summary>838 /// Create the startup payload and send it up.839 /// This is done only once during for the console host.840 /// </summary>841 /// <param name="mode">The "mode" of the startup.</param>842 /// <param name="parametersUsed">The parameter bitmap used when starting.</param>843 internal static void SendPSCoreStartupTelemetry(string mode, double parametersUsed)844 {845 // Check if we already sent startup telemetry846 if (Interlocked.CompareExchange(ref s_startupEventSent, 1, 0) == 1)847 {848 return;849 }850 851 if (!CanSendTelemetry)852 {853 return;854 }855 856 // This is the payload which reports the startup information of OS and shell details.857 var properties = new Dictionary<string, string>();858 859 // This is the payload for the parameter data which is sent as a metric.860 var parameters = new Dictionary<string, double>();861 862 // The variable POWERSHELL_DISTRIBUTION_CHANNEL is set in our docker images and863 // by various other environments. This allows us to track the actual docker OS as864 // OSDescription provides only "linuxkit" which has limited usefulness.865 var channel = Environment.GetEnvironmentVariable("POWERSHELL_DISTRIBUTION_CHANNEL");866 867 // Construct the payload for the OS and shell details.868 properties.Add("SessionId", s_sessionId);869 properties.Add("UUID", s_uniqueUserIdentifier);870 properties.Add("GitCommitID", PSVersionInfo.GitCommitId);871 properties.Add("OSDescription", RuntimeInformation.OSDescription);872 properties.Add("RuntimeIdentifier", RuntimeInformation.RuntimeIdentifier);873 properties.Add("OSChannel", string.IsNullOrEmpty(channel) ? "unknown" : channel);874 properties.Add("StartMode", string.IsNullOrEmpty(mode) ? "unknown" : mode);875 876 // Construct the payload for the parameters used.877 parameters.Add("Param", parametersUsed);878 try879 {880 s_telemetryClient.TrackEvent("ConsoleHostStartup", properties, parameters);881 }882 catch883 {884 // do nothing, telemetry cannot be sent885 }886 }887 888 /// <summary>889 /// Try to read the file and collect the guid.890 /// </summary>891 /// <param name="telemetryFilePath">The path to the telemetry file.</param>892 /// <param name="id">The newly created id.</param>893 /// <returns>894 /// The method returns a bool indicating success or failure of creating the id.895 /// </returns>896 private static bool TryGetIdentifier(string telemetryFilePath, out Guid id)897 {898 if (File.Exists(telemetryFilePath))899 {900 // attempt to read the persisted identifier901 const int GuidSize = 16;902 byte[] buffer = new byte[GuidSize];903 try904 {905 using (FileStream fs = new FileStream(telemetryFilePath, FileMode.Open, FileAccess.Read))906 {907 // if the read is invalid, or wrong size, we return it908 int n = fs.Read(buffer, 0, GuidSize);909 if (n == GuidSize)910 {911 // it's possible this could through912 id = new Guid(buffer);913 if (id != Guid.Empty)914 {915 return true;916 }917 }918 }919 }920 catch921 {922 // something went wrong, the file may not exist or not have enough bytes, so return false923 }924 }925 926 id = Guid.Empty;927 return false;928 }929 930 /// <summary>931 /// Try to create a unique identifier and persist it to the telemetry.uuid file.932 /// </summary>933 /// <param name="telemetryFilePath">The path to the persisted telemetry.uuid file.</param>934 /// <returns>935 /// The method node id.936 /// </returns>937 private static Guid CreateUniqueIdentifierAndFile(string telemetryFilePath)938 {939 // one last attempt to retrieve before creating incase we have a lot of simultaneous entry into the mutex.940 Guid id = Guid.Empty;941 if (TryGetIdentifier(telemetryFilePath, out id))942 {943 return id;944 }945 946 // The directory may not exist, so attempt to create it947 // CreateDirectory will simply return the directory if exists948 bool attemptFileCreation = true;949 try950 {951 Directory.CreateDirectory(Path.GetDirectoryName(telemetryFilePath));952 }953 catch954 {955 // There was a problem in creating the directory for the file, do not attempt to create the file.956 // We don't send telemetry here because there are valid reasons for the directory to not exist957 // and not be able to be created.958 attemptFileCreation = false;959 }960 961 // If we were able to create the directory, try to create the file,962 // if this fails we will send telemetry to indicate this and then use the default identifier.963 if (attemptFileCreation)964 {965 try966 {967 id = Guid.NewGuid();968 File.WriteAllBytes(telemetryFilePath, id.ToByteArray());969 return id;970 }971 catch972 {973 // another bit of telemetry to notify us about a problem with saving the unique id.974 s_telemetryClient.GetMetric(_telemetryFailure, "Detail").TrackValue(1, "saveuuid");975 }976 }977 978 // all attempts to create an identifier have failed, so use the default node id.979 id = _defaultNodeIdentifier;980 return id;981 }982 983 /// <summary>984 /// Retrieve the unique identifier from the persisted file, if it doesn't exist create it.985 /// Generate a guid which will be used as the UUID.986 /// </summary>987 /// <returns>A guid which represents the unique identifier.</returns>988 private static Guid GetUniqueIdentifier()989 {990 // Try to get the unique id. If this returns false, we'll991 // create/recreate the telemetry.uuid file to persist for next startup.992 Guid id = Guid.Empty;993 if (TryGetIdentifier(s_uuidPath, out id))994 {995 return id;996 }997 998 // Multiple processes may start simultaneously so we need a system wide999 // way to control access to the file in the case (although remote) when we have1000 // simultaneous shell starts without the persisted file which attempt to create the file.1001 try1002 {1003 // CreateUniqueIdentifierAndFile shouldn't throw, but the mutex might1004 using var m = new Mutex(true, "CreateUniqueUserId");1005 m.WaitOne();1006 try1007 {1008 return CreateUniqueIdentifierAndFile(s_uuidPath);1009 }1010 finally1011 {1012 m.ReleaseMutex();1013 }1014 }1015 catch (Exception)1016 {1017 // Any problem in generating a uuid will result in no telemetry being sent.1018 // Try to send the failure in telemetry, but it will have no unique id.1019 s_telemetryClient.GetMetric(_telemetryFailure, "Detail").TrackValue(1, "mutex");1020 }1021 1022 // something bad happened, turn off telemetry since the unique id wasn't set.1023 CanSendTelemetry = false;1024 return id;1025 }1026 }1027}1028 