Update telemetry collecting with a few changes (#17304)

This commit is contained in:
James Truher [MSFT]
2022-05-16 17:10:51 -07:00
committed by GitHub
parent c40066d5f2
commit 65d8f11aeb
6 changed files with 233 additions and 76 deletions
@@ -1,6 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#nullable enable
using System;
@@ -195,6 +194,57 @@ namespace Microsoft.PowerShell
"workingdirectory"
};
/// <summary>
/// These represent the parameters that are used when starting pwsh.
/// We can query in our telemetry to determine how pwsh was invoked.
/// </summary>
[Flags]
internal enum ParameterBitmap : long
{
Command = 0x00000001, // -Command | -c
ConfigurationName = 0x00000002, // -ConfigurationName | -config
CustomPipeName = 0x00000004, // -CustomPipeName
EncodedCommand = 0x00000008, // -EncodedCommand | -e | -ec
EncodedArgument = 0x00000010, // -EncodedArgument
ExecutionPolicy = 0x00000020, // -ExecutionPolicy | -ex | -ep
File = 0x00000040, // -File | -f
Help = 0x00000080, // -Help, -?, /?
InputFormat = 0x00000100, // -InputFormat | -inp | -if
Interactive = 0x00000200, // -Interactive | -i
Login = 0x00000400, // -Login | -l
MTA = 0x00000800, // -MTA
NoExit = 0x00001000, // -NoExit | -noe
NoLogo = 0x00002000, // -NoLogo | -nol
NonInteractive = 0x00004000, // -NonInteractive | -noni
NoProfile = 0x00008000, // -NoProfile | -nop
OutputFormat = 0x00010000, // -OutputFormat | -o | -of
SettingsFile = 0x00020000, // -SettingsFile | -settings
SSHServerMode = 0x00040000, // -SSHServerMode | -sshs
SocketServerMode = 0x00080000, // -SocketServerMode | -sockets
ServerMode = 0x00100000, // -ServerMode | -server
NamedPipeServerMode = 0x00200000, // -NamedPipeServerMode | -namedpipes
STA = 0x00400000, // -STA
Version = 0x00800000, // -Version | -v
WindowStyle = 0x01000000, // -WindowStyle | -w
WorkingDirectory = 0x02000000, // -WorkingDirectory | -wd
// Enum values for specified ExecutionPolicy
EPUnrestricted = 0x0000000100000000, // ExecutionPolicy unrestricted
EPRemoteSigned = 0x0000000200000000, // ExecutionPolicy remote signed
EPAllSigned = 0x0000000400000000, // ExecutionPolicy all signed
EPRestricted = 0x0000000800000000, // ExecutionPolicy restricted
EPDefault = 0x0000001000000000, // ExecutionPolicy default
EPBypass = 0x0000002000000000, // ExecutionPolicy bypass
EPUndefined = 0x0000004000000000, // ExecutionPolicy undefined
EPIncorrect = 0x0000008000000000, // ExecutionPolicy incorrect
}
internal ParameterBitmap ParametersUsed = 0;
internal double ParametersUsedAsDouble
{
get { return (double)ParametersUsed; }
}
[Conditional("DEBUG")]
private void AssertArgumentsParsed()
{
@@ -641,6 +691,53 @@ namespace Microsoft.PowerShell
return Path.GetFullPath(path);
}
/// <summary>
/// Determine the execution policy based on the supplied string.
/// If the string doesn't match to any known execution policy, set it to incorrect.
/// </summary>
/// <param name="_executionPolicy">The value provided on the command line.</param>
/// <returns>The execution policy.</returns>
private static ParameterBitmap GetExecutionPolicy(string? _executionPolicy)
{
if (_executionPolicy is null)
{
return ParameterBitmap.EPUndefined;
}
ParameterBitmap executionPolicySetting = ParameterBitmap.EPIncorrect;
if (string.Equals(_executionPolicy, "default", StringComparison.OrdinalIgnoreCase))
{
executionPolicySetting = ParameterBitmap.EPDefault;
}
else if (string.Equals(_executionPolicy, "remotesigned", StringComparison.OrdinalIgnoreCase))
{
executionPolicySetting = ParameterBitmap.EPRemoteSigned;
}
else if (string.Equals(_executionPolicy, "bypass", StringComparison.OrdinalIgnoreCase))
{
executionPolicySetting = ParameterBitmap.EPBypass;
}
else if (string.Equals(_executionPolicy, "allsigned", StringComparison.OrdinalIgnoreCase))
{
executionPolicySetting = ParameterBitmap.EPAllSigned;
}
else if (string.Equals(_executionPolicy, "restricted", StringComparison.OrdinalIgnoreCase))
{
executionPolicySetting = ParameterBitmap.EPRestricted;
}
else if (string.Equals(_executionPolicy, "unrestricted", StringComparison.OrdinalIgnoreCase))
{
executionPolicySetting = ParameterBitmap.EPUnrestricted;
}
else if (string.Equals(_executionPolicy, "undefined", StringComparison.OrdinalIgnoreCase))
{
executionPolicySetting = ParameterBitmap.EPUndefined;
}
return executionPolicySetting;
}
private static bool MatchSwitch(string switchKey, string match, string smallestUnambiguousMatch)
{
Dbg.Assert(!string.IsNullOrEmpty(match), "need a value");
@@ -755,6 +852,7 @@ namespace Microsoft.PowerShell
_noInteractive = true;
_skipUserInit = true;
_noExit = false;
ParametersUsed |= ParameterBitmap.Version;
break;
}
@@ -763,48 +861,59 @@ namespace Microsoft.PowerShell
_showHelp = true;
_showExtendedHelp = true;
_abortStartup = true;
ParametersUsed |= ParameterBitmap.Help;
}
else if (MatchSwitch(switchKey, "login", "l"))
{
// On Windows, '-Login' does nothing.
// On *nix, '-Login' is already handled much earlier to improve startup performance, so we do nothing here.
ParametersUsed |= ParameterBitmap.Login;
}
else if (MatchSwitch(switchKey, "noexit", "noe"))
{
_noExit = true;
noexitSeen = true;
ParametersUsed |= ParameterBitmap.NoExit;
}
else if (MatchSwitch(switchKey, "noprofile", "nop"))
{
_skipUserInit = true;
ParametersUsed |= ParameterBitmap.NoProfile;
}
else if (MatchSwitch(switchKey, "nologo", "nol"))
{
_showBanner = false;
ParametersUsed |= ParameterBitmap.NoLogo;
}
else if (MatchSwitch(switchKey, "noninteractive", "noni"))
{
_noInteractive = true;
ParametersUsed |= ParameterBitmap.NonInteractive;
}
else if (MatchSwitch(switchKey, "socketservermode", "so"))
{
_socketServerMode = true;
ParametersUsed |= ParameterBitmap.SocketServerMode;
}
else if (MatchSwitch(switchKey, "servermode", "s"))
{
_serverMode = true;
ParametersUsed |= ParameterBitmap.ServerMode;
}
else if (MatchSwitch(switchKey, "namedpipeservermode", "nam"))
{
_namedPipeServerMode = true;
ParametersUsed |= ParameterBitmap.NamedPipeServerMode;
}
else if (MatchSwitch(switchKey, "sshservermode", "sshs"))
{
_sshServerMode = true;
ParametersUsed |= ParameterBitmap.SSHServerMode;
}
else if (MatchSwitch(switchKey, "interactive", "i"))
{
_noInteractive = false;
ParametersUsed |= ParameterBitmap.Interactive;
}
else if (MatchSwitch(switchKey, "configurationname", "config"))
{
@@ -817,6 +926,7 @@ namespace Microsoft.PowerShell
}
_configurationName = args[i];
ParametersUsed |= ParameterBitmap.ConfigurationName;
}
else if (MatchSwitch(switchKey, "custompipename", "cus"))
{
@@ -841,7 +951,9 @@ namespace Microsoft.PowerShell
break;
}
#endif
_customPipeName = args[i];
ParametersUsed |= ParameterBitmap.CustomPipeName;
}
else if (MatchSwitch(switchKey, "command", "c"))
{
@@ -849,6 +961,8 @@ namespace Microsoft.PowerShell
{
break;
}
ParametersUsed |= ParameterBitmap.Command;
}
else if (MatchSwitch(switchKey, "windowstyle", "w"))
{
@@ -875,6 +989,8 @@ namespace Microsoft.PowerShell
string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidWindowStyleArgument, args[i], e.Message));
break;
}
ParametersUsed |= ParameterBitmap.WindowStyle;
#endif
}
else if (MatchSwitch(switchKey, "file", "f"))
@@ -883,6 +999,8 @@ namespace Microsoft.PowerShell
{
break;
}
ParametersUsed |= ParameterBitmap.File;
}
#if DEBUG
else if (MatchSwitch(switchKey, "isswait", "isswait"))
@@ -894,14 +1012,18 @@ namespace Microsoft.PowerShell
{
ParseFormat(args, ref i, ref _outFormat, CommandLineParameterParserStrings.MissingOutputFormatParameter);
_outputFormatSpecified = true;
ParametersUsed |= ParameterBitmap.OutputFormat;
}
else if (MatchSwitch(switchKey, "inputformat", "inp") || MatchSwitch(switchKey, "if", "if"))
{
ParseFormat(args, ref i, ref _inFormat, CommandLineParameterParserStrings.MissingInputFormatParameter);
ParametersUsed |= ParameterBitmap.InputFormat;
}
else if (MatchSwitch(switchKey, "executionpolicy", "ex") || MatchSwitch(switchKey, "ep", "ep"))
{
ParseExecutionPolicy(args, ref i, ref _executionPolicy, CommandLineParameterParserStrings.MissingExecutionPolicyParameter);
ParametersUsed |= ParameterBitmap.ExecutionPolicy;
ParametersUsed |= GetExecutionPolicy(_executionPolicy);
}
else if (MatchSwitch(switchKey, "encodedcommand", "e") || MatchSwitch(switchKey, "ec", "e"))
{
@@ -910,6 +1032,8 @@ namespace Microsoft.PowerShell
{
break;
}
ParametersUsed |= ParameterBitmap.EncodedCommand;
}
else if (MatchSwitch(switchKey, "encodedarguments", "encodeda") || MatchSwitch(switchKey, "ea", "ea"))
{
@@ -917,6 +1041,8 @@ namespace Microsoft.PowerShell
{
break;
}
ParametersUsed |= ParameterBitmap.EncodedArgument;
}
else if (MatchSwitch(switchKey, "settingsfile", "settings"))
{
@@ -925,6 +1051,8 @@ namespace Microsoft.PowerShell
{
break;
}
ParametersUsed |= ParameterBitmap.SettingsFile;
}
else if (MatchSwitch(switchKey, "sta", "sta"))
{
@@ -944,6 +1072,7 @@ namespace Microsoft.PowerShell
}
_staMode = true;
ParametersUsed |= ParameterBitmap.STA;
}
else if (MatchSwitch(switchKey, "mta", "mta"))
{
@@ -963,6 +1092,7 @@ namespace Microsoft.PowerShell
}
_staMode = false;
ParametersUsed |= ParameterBitmap.MTA;
}
else if (MatchSwitch(switchKey, "workingdirectory", "wo") || MatchSwitch(switchKey, "wd", "wd"))
{
@@ -975,6 +1105,7 @@ namespace Microsoft.PowerShell
}
_workingDirectory = args[i];
ParametersUsed |= ParameterBitmap.WorkingDirectory;
}
#if !UNIX
else if (MatchSwitch(switchKey, "removeworkingdirectorytrailingcharacter", "removeworkingdirectorytrailingcharacter"))
@@ -990,6 +1121,9 @@ namespace Microsoft.PowerShell
{
break;
}
// default to filename being the next argument.
ParametersUsed |= ParameterBitmap.File;
}
}
@@ -192,7 +192,7 @@ namespace Microsoft.PowerShell
// First check for and handle PowerShell running in a server mode.
if (s_cpp.ServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode");
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode", s_cpp.ParametersUsedAsDouble);
ProfileOptimization.StartProfile("StartupProfileData-ServerMode");
StdIOProcessMediator.Run(
initialCommand: s_cpp.InitialCommand,
@@ -202,7 +202,7 @@ namespace Microsoft.PowerShell
}
else if (s_cpp.SSHServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SSHServer");
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SSHServer", s_cpp.ParametersUsedAsDouble);
ProfileOptimization.StartProfile("StartupProfileData-SSHServerMode");
StdIOProcessMediator.Run(
initialCommand: s_cpp.InitialCommand,
@@ -212,7 +212,7 @@ namespace Microsoft.PowerShell
}
else if (s_cpp.NamedPipeServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe");
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe", s_cpp.ParametersUsedAsDouble);
ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode");
RemoteSessionNamedPipeServer.RunServerMode(
configurationName: s_cpp.ConfigurationName);
@@ -220,7 +220,7 @@ namespace Microsoft.PowerShell
}
else if (s_cpp.SocketServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode");
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode", s_cpp.ParametersUsedAsDouble);
ProfileOptimization.StartProfile("StartupProfileData-SocketServerMode");
HyperVSocketMediator.Run(
initialCommand: s_cpp.InitialCommand,
@@ -255,7 +255,7 @@ namespace Microsoft.PowerShell
PSHost.IsStdOutputRedirected = Console.IsOutputRedirected;
// Send startup telemetry for ConsoleHost startup
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal");
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal", s_cpp.ParametersUsedAsDouble);
exitCode = s_theConsoleHost.Run(s_cpp, false);
}
@@ -619,7 +619,7 @@ namespace Microsoft.PowerShell.Commands
// avoid double reporting for WinCompat modules that go through CommandDiscovery\AutoloadSpecifiedModule
if (!foundModule.IsWindowsPowerShellCompatModule)
{
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name);
ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name, foundModule.Version?.ToString());
#if LEGACYTELEMETRY
TelemetryAPI.ReportModuleLoad(foundModule);
#endif
@@ -896,7 +896,7 @@ namespace Microsoft.PowerShell.Commands
if (foundModule != null)
{
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name);
ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name, foundModule.Version?.ToString());
SetModuleBaseForEngineModules(foundModule.Name, this.Context);
}
@@ -938,7 +938,7 @@ namespace Microsoft.PowerShell.Commands
// Send telemetry on the imported modules
foreach (PSModuleInfo moduleInfo in remotelyImportedModules)
{
ApplicationInsightsTelemetry.SendTelemetryMetric(usingWinCompat ? TelemetryType.WinCompatModuleLoad : TelemetryType.ModuleLoad, moduleInfo.Name);
ApplicationInsightsTelemetry.SendModuleTelemetryMetric(usingWinCompat ? TelemetryType.WinCompatModuleLoad : TelemetryType.ModuleLoad, moduleInfo.Name, moduleInfo.Version?.ToString());
}
return remotelyImportedModules;
@@ -1369,7 +1369,8 @@ namespace Microsoft.PowerShell.Commands
foreach (RemoteDiscoveryHelper.CimModule remoteCimModule in remotePsCimModules)
{
ImportModule_RemotelyViaCimModuleData(importModuleOptions, remoteCimModule, cimSession);
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, remoteCimModule.ModuleName);
// we don't know the version of the module
ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, remoteCimModule.ModuleName);
}
}
@@ -1874,7 +1875,7 @@ namespace Microsoft.PowerShell.Commands
// of doing Get-Module -list
foreach (PSModuleInfo module in ModuleInfo)
{
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, module.Name);
ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, module.Name, module.Version?.ToString());
RemoteDiscoveryHelper.DispatchModuleInfoProcessing(
module,
localAction: () =>
@@ -1902,7 +1903,8 @@ namespace Microsoft.PowerShell.Commands
// Now load all of the supplied assemblies...
foreach (Assembly suppliedAssembly in Assembly)
{
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, suppliedAssembly.GetName().Name);
// we don't know what the version of the module is.
ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, suppliedAssembly.GetName().Name);
ImportModule_ViaAssembly(importModuleOptions, suppliedAssembly);
}
}
@@ -1933,7 +1935,7 @@ namespace Microsoft.PowerShell.Commands
ImportModule_RemotelyViaPsrpSession(importModuleOptions, null, FullyQualifiedName, this.PSSession);
foreach (ModuleSpecification modulespec in FullyQualifiedName)
{
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name);
ApplicationInsightsTelemetry.SendModuleTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name, modulespec.Version?.ToString());
}
}
else if (this.ParameterSetName.Equals(ParameterSet_ViaWinCompat, StringComparison.OrdinalIgnoreCase)
@@ -1077,8 +1077,6 @@ namespace System.Management.Automation.Internal
CommandState.Started,
commandProcessor.Command.MyInvocation);
// Send telemetry that includes the type of command.
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ApplicationType, commandProcessor.Command.CommandInfo.CommandType.ToString());
#if LEGACYTELEMETRY
Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceExecutedCommand(commandProcessor.Command.CommandInfo, commandProcessor.Command.CommandOrigin);
#endif
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Management.Automation;
using System.Runtime.InteropServices;
@@ -94,8 +95,15 @@ namespace Microsoft.PowerShell.Telemetry
// private const string _psCoreTelemetryKey = "ee4b2115-d347-47b0-adb6-b19c2c763808"; // Production
private const string _psCoreTelemetryKey = "d26a5ef4-d608-452c-a6b8-a4a55935f70d"; // V7 Preview 3
// In the event there is a problem in creating the node identifier file, use the default identifier.
// This can happen if we are running in a system which has a read-only filesystem.
private static readonly Guid _defaultNodeIdentifier = new Guid("2f998828-3f4a-4741-bf50-d11c6be42f50");
// Use "anonymous" as the string to return when you can't report a name
private const string _anonymous = "anonymous";
private const string Anonymous = "anonymous";
// Use '0.0' as the string for an anonymous module version
private const string AnonymousVersion = "0.0";
// the telemetry failure string
private const string _telemetryFailure = "TELEMETRY_FAILURE";
@@ -240,6 +248,7 @@ namespace Microsoft.PowerShell.Telemetry
"Az.StorageTable",
"Az.StreamAnalytics",
"Az.Subscription",
"Az.Tools.Predictor",
"Az.TrafficManager",
"Az.Websites",
"Azs.Azurebridge.Admin",
@@ -642,6 +651,28 @@ namespace Microsoft.PowerShell.Telemetry
return defaultValue;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="telemetryType">The type of telemetry that we'll be sending.</param>
/// <param name="moduleName">The module name to report. If it is not allowed, then it is set to 'anonymous'.</param>
/// <param name="moduleVersion">The module version to report. The default value is the anonymous version '0.0.0.0'.</param>
internal static void SendModuleTelemetryMetric(TelemetryType telemetryType, string moduleName, string moduleVersion = AnonymousVersion)
{
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);
}
catch
{
// Ignore errors.
}
}
/// <summary>
/// Send telemetry as a metric.
/// </summary>
@@ -654,7 +685,9 @@ namespace Microsoft.PowerShell.Telemetry
return;
}
SendPSCoreStartupTelemetry("hosted");
// These should be handled by SendModuleTelemetryMetric.
Debug.Assert(metricId != TelemetryType.ModuleLoad, "ModuleLoad should be handled by SendModuleTelemetryMetric.");
Debug.Assert(metricId != TelemetryType.WinCompatModuleLoad, "WinCompatModuleLoad should be handled by SendModuleTelemetryMetric.");
string metricName = metricId.ToString();
try
@@ -671,11 +704,6 @@ namespace Microsoft.PowerShell.Telemetry
string experimentalFeatureName = GetExperimentalFeatureName(data);
s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, experimentalFeatureName);
break;
case TelemetryType.ModuleLoad:
case TelemetryType.WinCompatModuleLoad:
string moduleName = GetModuleName(data); // This will return anonymous if the modulename is not on the report list
s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, moduleName);
break;
}
}
catch
@@ -697,7 +725,7 @@ namespace Microsoft.PowerShell.Telemetry
return featureNameToValidate;
}
return _anonymous;
return Anonymous;
}
// Get the module name. If we can report it, we'll return the name, otherwise, we'll return "anonymous"
@@ -708,7 +736,7 @@ namespace Microsoft.PowerShell.Telemetry
return moduleNameToValidate;
}
return _anonymous;
return Anonymous;
}
/// <summary>
@@ -716,7 +744,8 @@ namespace Microsoft.PowerShell.Telemetry
/// This is done only once during for the console host.
/// </summary>
/// <param name="mode">The "mode" of the startup.</param>
internal static void SendPSCoreStartupTelemetry(string mode)
/// <param name="parametersUsed">The parameter bitmap used when starting.</param>
internal static void SendPSCoreStartupTelemetry(string mode, double parametersUsed)
{
// Check if we already sent startup telemetry
if (Interlocked.CompareExchange(ref s_startupEventSent, 1, 0) == 1)
@@ -729,22 +758,30 @@ namespace Microsoft.PowerShell.Telemetry
return;
}
// This is the payload which reports the startup information of OS and shell details.
var properties = new Dictionary<string, string>();
// The variable POWERSHELL_DISTRIBUTION_CHANNEL is set in our docker images.
// This allows us to track the actual docker OS as OSDescription provides only "linuxkit"
// which has limited usefulness
// This is the payload for the parameter data which is sent as a metric.
var parameters = new Dictionary<string, double>();
// The variable POWERSHELL_DISTRIBUTION_CHANNEL is set in our docker images and
// by various other environments. This allows us to track the actual docker OS as
// OSDescription provides only "linuxkit" which has limited usefulness.
var channel = Environment.GetEnvironmentVariable("POWERSHELL_DISTRIBUTION_CHANNEL");
// Construct the payload for the OS and shell details.
properties.Add("SessionId", s_sessionId);
properties.Add("UUID", s_uniqueUserIdentifier);
properties.Add("GitCommitID", PSVersionInfo.GitCommitId);
properties.Add("OSDescription", RuntimeInformation.OSDescription);
properties.Add("OSChannel", string.IsNullOrEmpty(channel) ? "unknown" : channel);
properties.Add("StartMode", string.IsNullOrEmpty(mode) ? "unknown" : mode);
// Construct the payload for the parameters used.
parameters.Add("Param", parametersUsed);
try
{
s_telemetryClient.TrackEvent("ConsoleHostStartup", properties, null);
s_telemetryClient.TrackEvent("ConsoleHostStartup", properties, parameters);
}
catch
{
@@ -798,50 +835,53 @@ namespace Microsoft.PowerShell.Telemetry
/// Try to create a unique identifier and persist it to the telemetry.uuid file.
/// </summary>
/// <param name="telemetryFilePath">The path to the persisted telemetry.uuid file.</param>
/// <param name="id">The created identifier.</param>
/// <returns>
/// The method returns a bool indicating success or failure of creating the id.
/// The method node id.
/// </returns>
private static bool TryCreateUniqueIdentifierAndFile(string telemetryFilePath, out Guid id)
private static Guid CreateUniqueIdentifierAndFile(string telemetryFilePath)
{
// one last attempt to retrieve before creating incase we have a lot of simultaneous entry into the mutex.
id = Guid.Empty;
Guid id = Guid.Empty;
if (TryGetIdentifier(telemetryFilePath, out id))
{
return true;
return id;
}
// The directory may not exist, so attempt to create it
// CreateDirectory will simply return the directory if exists
bool attemptFileCreation = true;
try
{
Directory.CreateDirectory(Path.GetDirectoryName(telemetryFilePath));
}
catch
{
// send a telemetry indicating a problem with the cache dir
// it's likely something is seriously wrong so we should at least report it.
// We don't want to provide reasons here, that's not the point, but we
// would like to know if we're having a generalized problem which we can trace statistically
CanSendTelemetry = false;
s_telemetryClient.GetMetric(_telemetryFailure, "Detail").TrackValue(1, "cachedir");
return false;
// There was a problem in creating the directory for the file, do not attempt to create the file.
// We don't send telemetry here because there are valid reasons for the directory to not exist
// and not be able to be created.
attemptFileCreation = false;
}
// Create and save the new identifier, and if there's a problem, disable telemetry
try
// If we were able to create the directory, try to create the file,
// if this fails we will send telemetry to indicate this and then use the default identifier.
if (attemptFileCreation)
{
id = Guid.NewGuid();
File.WriteAllBytes(telemetryFilePath, id.ToByteArray());
return true;
}
catch
{
// another bit of telemetry to notify us about a problem with saving the unique id.
s_telemetryClient.GetMetric(_telemetryFailure, "Detail").TrackValue(1, "saveuuid");
try
{
id = Guid.NewGuid();
File.WriteAllBytes(telemetryFilePath, id.ToByteArray());
return id;
}
catch
{
// another bit of telemetry to notify us about a problem with saving the unique id.
s_telemetryClient.GetMetric(_telemetryFailure, "Detail").TrackValue(1, "saveuuid");
}
}
return false;
// all attempts to create an identifier have failed, so use the default node id.
id = _defaultNodeIdentifier;
return id;
}
/// <summary>
@@ -865,15 +905,12 @@ namespace Microsoft.PowerShell.Telemetry
// simultaneous shell starts without the persisted file which attempt to create the file.
try
{
// TryCreateUniqueIdentifierAndFile shouldn't throw, but the mutex might
// CreateUniqueIdentifierAndFile shouldn't throw, but the mutex might
using var m = new Mutex(true, "CreateUniqueUserId");
m.WaitOne();
try
{
if (TryCreateUniqueIdentifierAndFile(uuidPath, out id))
{
return id;
}
return CreateUniqueIdentifierAndFile(uuidPath);
}
finally
{
@@ -129,28 +129,14 @@ Describe "Telemetry for shell startup" -Tag CI {
$result | Should -Be $expectedValue
}
It "Should resend startup event if the semaphore says we haven't sent telemetry" {
It "Should send startup event" {
$resultJson = & $PWSH -NoProfile -c {
# this should ensure that the startup telemetry event is sent.
$null = Get-Date | Out-String
$telemetryType = [Microsoft.PowerShell.Telemetry.ApplicationInsightsTelemetry]
$bindingFlags = [System.Reflection.BindingFlags]"NonPublic,Static"
$initialValue = ${telemetryType}.GetMember("s_startupEventSent", $bindingFlags)[0].GetValue($null)
# force a resend of the startup telemetry
$null = ${telemetryType}.GetMember("s_startupEventSent", $bindingFlags)[0].SetValue($null,0)
$null = Get-Date | Out-String
# now check it again, it should be true now that something has executed
$finalValue = ${telemetryType}.GetMember("s_startupEventSent", $bindingFlags)[0].GetValue($null)
@{
initialValue = $initialValue
finalValue = $finalValue
} | ConvertTo-Json -Compress
$observedValue = ${telemetryType}.GetMember("s_startupEventSent", $bindingFlags)[0].GetValue($null)
$observedValue | Should -Be 1 -Because "Should have sent telemetry on console startup"
}
$result = $resultJson | ConvertFrom-Json
$result.InitialValue | Should -Be 1 -Because "Should have sent telemetry on console startup"
$result.FinalValue | Should -Be 1 -Because "Should have resent telemetry"
}
}