diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
index 795921a0c5..aefcb58cfd 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
@@ -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"
};
+ ///
+ /// These represent the parameters that are used when starting pwsh.
+ /// We can query in our telemetry to determine how pwsh was invoked.
+ ///
+ [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);
}
+ ///
+ /// Determine the execution policy based on the supplied string.
+ /// If the string doesn't match to any known execution policy, set it to incorrect.
+ ///
+ /// The value provided on the command line.
+ /// The execution policy.
+ 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;
}
}
diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
index 4703d54d88..6a78b4c864 100644
--- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
+++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs
@@ -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);
}
diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs
index 8f503cbe46..8e178c0cb1 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.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)
diff --git a/src/System.Management.Automation/engine/pipeline.cs b/src/System.Management.Automation/engine/pipeline.cs
index 185e60e072..152b3e771e 100644
--- a/src/System.Management.Automation/engine/pipeline.cs
+++ b/src/System.Management.Automation/engine/pipeline.cs
@@ -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
diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs
index 1c1f1b8d2c..625f957165 100644
--- a/src/System.Management.Automation/utils/Telemetry.cs
+++ b/src/System.Management.Automation/utils/Telemetry.cs
@@ -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;
}
+ ///
+ /// 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 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)
+ {
+ 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.
+ }
+ }
+
///
/// Send telemetry as a metric.
///
@@ -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;
}
///
@@ -716,7 +744,8 @@ namespace Microsoft.PowerShell.Telemetry
/// This is done only once during for the console host.
///
/// The "mode" of the startup.
- internal static void SendPSCoreStartupTelemetry(string mode)
+ /// The parameter bitmap used when starting.
+ 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();
- // 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();
+
+ // 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.
///
/// The path to the persisted telemetry.uuid file.
- /// The created identifier.
///
- /// The method returns a bool indicating success or failure of creating the id.
+ /// The method node id.
///
- 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;
}
///
@@ -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
{
diff --git a/test/powershell/engine/Basic/Telemetry.Tests.ps1 b/test/powershell/engine/Basic/Telemetry.Tests.ps1
index 0ea3594b86..2378b9e5a6 100644
--- a/test/powershell/engine/Basic/Telemetry.Tests.ps1
+++ b/test/powershell/engine/Basic/Telemetry.Tests.ps1
@@ -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"
}
-
}