Additional Telemetry - Implementation of RFC0036
(#10336)
This commit is contained in:
parent
c8e72d1e66
commit
fe2cc6aca8
@ -1012,7 +1012,7 @@ function Start-PSPester {
|
|||||||
# All concatenated commands/arguments are suffixed with the delimiter (space)
|
# All concatenated commands/arguments are suffixed with the delimiter (space)
|
||||||
|
|
||||||
# Disable telemetry for all startups of pwsh in tests
|
# Disable telemetry for all startups of pwsh in tests
|
||||||
$command = "`$env:POWERSHELL_TELEMETRY_OPTOUT = 1;"
|
$command = "`$env:POWERSHELL_TELEMETRY_OPTOUT = 'yes';"
|
||||||
if ($Terse)
|
if ($Terse)
|
||||||
{
|
{
|
||||||
$command += "`$ProgressPreference = 'silentlyContinue'; "
|
$command += "`$ProgressPreference = 'silentlyContinue'; "
|
||||||
@ -1153,7 +1153,7 @@ function Start-PSPester {
|
|||||||
try {
|
try {
|
||||||
$originalModulePath = $env:PSModulePath
|
$originalModulePath = $env:PSModulePath
|
||||||
$originalTelemetry = $env:POWERSHELL_TELEMETRY_OPTOUT
|
$originalTelemetry = $env:POWERSHELL_TELEMETRY_OPTOUT
|
||||||
$env:POWERSHELL_TELEMETRY_OPTOUT = 1
|
$env:POWERSHELL_TELEMETRY_OPTOUT = 'yes'
|
||||||
if ($Unelevate)
|
if ($Unelevate)
|
||||||
{
|
{
|
||||||
Start-UnelevatedProcess -process $powershell -arguments ($PSFlags + "-c $Command")
|
Start-UnelevatedProcess -process $powershell -arguments ($PSFlags + "-c $Command")
|
||||||
|
@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" />
|
<ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" />
|
||||||
<!-- the following package(s) are from https://github.com/microsoft/applicationinsights-??? -->
|
|
||||||
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.10.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -24,6 +24,7 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.PowerShell.Telemetry;
|
||||||
|
|
||||||
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
|
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
|
||||||
using Dbg = System.Management.Automation.Diagnostics;
|
using Dbg = System.Management.Automation.Diagnostics;
|
||||||
@ -123,12 +124,8 @@ namespace Microsoft.PowerShell
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string profileDir;
|
string profileDir = Platform.CacheDirectory;
|
||||||
#if UNIX
|
#if ! UNIX
|
||||||
profileDir = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE);
|
|
||||||
#else
|
|
||||||
profileDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerShell";
|
|
||||||
|
|
||||||
if (!Directory.Exists(profileDir))
|
if (!Directory.Exists(profileDir))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(profileDir);
|
Directory.CreateDirectory(profileDir);
|
||||||
@ -200,12 +197,14 @@ namespace Microsoft.PowerShell
|
|||||||
// First check for and handle PowerShell running in a server mode.
|
// First check for and handle PowerShell running in a server mode.
|
||||||
if (s_cpp.ServerMode)
|
if (s_cpp.ServerMode)
|
||||||
{
|
{
|
||||||
|
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode");
|
||||||
ProfileOptimization.StartProfile("StartupProfileData-ServerMode");
|
ProfileOptimization.StartProfile("StartupProfileData-ServerMode");
|
||||||
System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand);
|
System.Management.Automation.Remoting.Server.OutOfProcessMediator.Run(s_cpp.InitialCommand);
|
||||||
exitCode = 0;
|
exitCode = 0;
|
||||||
}
|
}
|
||||||
else if (s_cpp.NamedPipeServerMode)
|
else if (s_cpp.NamedPipeServerMode)
|
||||||
{
|
{
|
||||||
|
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe");
|
||||||
ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode");
|
ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode");
|
||||||
System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode(
|
System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode(
|
||||||
s_cpp.ConfigurationName);
|
s_cpp.ConfigurationName);
|
||||||
@ -213,12 +212,14 @@ namespace Microsoft.PowerShell
|
|||||||
}
|
}
|
||||||
else if (s_cpp.SSHServerMode)
|
else if (s_cpp.SSHServerMode)
|
||||||
{
|
{
|
||||||
|
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SSHServer");
|
||||||
ProfileOptimization.StartProfile("StartupProfileData-SSHServerMode");
|
ProfileOptimization.StartProfile("StartupProfileData-SSHServerMode");
|
||||||
System.Management.Automation.Remoting.Server.SSHProcessMediator.Run(s_cpp.InitialCommand);
|
System.Management.Automation.Remoting.Server.SSHProcessMediator.Run(s_cpp.InitialCommand);
|
||||||
exitCode = 0;
|
exitCode = 0;
|
||||||
}
|
}
|
||||||
else if (s_cpp.SocketServerMode)
|
else if (s_cpp.SocketServerMode)
|
||||||
{
|
{
|
||||||
|
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode");
|
||||||
ProfileOptimization.StartProfile("StartupProfileData-SocketServerMode");
|
ProfileOptimization.StartProfile("StartupProfileData-SocketServerMode");
|
||||||
System.Management.Automation.Remoting.Server.HyperVSocketMediator.Run(s_cpp.InitialCommand,
|
System.Management.Automation.Remoting.Server.HyperVSocketMediator.Run(s_cpp.InitialCommand,
|
||||||
s_cpp.ConfigurationName);
|
s_cpp.ConfigurationName);
|
||||||
@ -242,7 +243,7 @@ namespace Microsoft.PowerShell
|
|||||||
PSHost.IsStdOutputRedirected = Console.IsOutputRedirected;
|
PSHost.IsStdOutputRedirected = Console.IsOutputRedirected;
|
||||||
|
|
||||||
// Send startup telemetry for ConsoleHost startup
|
// Send startup telemetry for ConsoleHost startup
|
||||||
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry();
|
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal");
|
||||||
|
|
||||||
exitCode = s_theConsoleHost.Run(s_cpp, false);
|
exitCode = s_theConsoleHost.Run(s_cpp, false);
|
||||||
}
|
}
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
// Licensed under the MIT License.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Management.Automation;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
using Microsoft.ApplicationInsights;
|
|
||||||
using Microsoft.ApplicationInsights.DataContracts;
|
|
||||||
using Microsoft.ApplicationInsights.Extensibility;
|
|
||||||
|
|
||||||
namespace Microsoft.PowerShell
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Send up telemetry for startup.
|
|
||||||
/// </summary>
|
|
||||||
internal static class ApplicationInsightsTelemetry
|
|
||||||
{
|
|
||||||
// If this env var is true, yes, or 1, telemetry will NOT be sent.
|
|
||||||
private const string TelemetryOptoutEnvVar = "POWERSHELL_TELEMETRY_OPTOUT";
|
|
||||||
|
|
||||||
// Telemetry client to be reused when we start sending more telemetry
|
|
||||||
private static TelemetryClient _telemetryClient = null;
|
|
||||||
|
|
||||||
// Set this to true to reduce the latency of sending the telemetry
|
|
||||||
private static bool _developerMode = false;
|
|
||||||
|
|
||||||
// PSCoreInsight2 telemetry key
|
|
||||||
private const string _psCoreTelemetryKey = "ee4b2115-d347-47b0-adb6-b19c2c763808";
|
|
||||||
|
|
||||||
static ApplicationInsightsTelemetry()
|
|
||||||
{
|
|
||||||
TelemetryConfiguration.Active.InstrumentationKey = _psCoreTelemetryKey;
|
|
||||||
TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = _developerMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool GetEnvironmentVariableAsBool(string name, bool defaultValue)
|
|
||||||
{
|
|
||||||
var str = Environment.GetEnvironmentVariable(name);
|
|
||||||
if (string.IsNullOrEmpty(str))
|
|
||||||
{
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (str.ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case "true":
|
|
||||||
case "1":
|
|
||||||
case "yes":
|
|
||||||
return true;
|
|
||||||
case "false":
|
|
||||||
case "0":
|
|
||||||
case "no":
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send the telemetry.
|
|
||||||
/// </summary>
|
|
||||||
private static void SendTelemetry(string eventName, Dictionary<string, string> payload)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var enabled = !GetEnvironmentVariableAsBool(name: TelemetryOptoutEnvVar, defaultValue: false);
|
|
||||||
|
|
||||||
if (!enabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_telemetryClient == null)
|
|
||||||
{
|
|
||||||
_telemetryClient = new TelemetryClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
_telemetryClient.TrackEvent(eventName, payload, null);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
; // Do nothing, telemetry can't be sent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create the startup payload and send it up.
|
|
||||||
/// </summary>
|
|
||||||
internal static void SendPSCoreStartupTelemetry()
|
|
||||||
{
|
|
||||||
var properties = new Dictionary<string, string>();
|
|
||||||
properties.Add("GitCommitID", PSVersionInfo.GitCommitId);
|
|
||||||
properties.Add("OSDescription", RuntimeInformation.OSDescription);
|
|
||||||
SendTelemetry("ConsoleHostStartup", properties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -140,6 +140,21 @@ namespace System.Management.Automation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the location for the various caches.
|
||||||
|
/// </summary>
|
||||||
|
internal static string CacheDirectory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
#if UNIX
|
||||||
|
return Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE);
|
||||||
|
#else
|
||||||
|
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerShell";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if !UNIX
|
#if !UNIX
|
||||||
private static bool? _isNanoServer = null;
|
private static bool? _isNanoServer = null;
|
||||||
private static bool? _isIoT = null;
|
private static bool? _isIoT = null;
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- the following package(s) are from https://github.com/JamesNK/Newtonsoft.Json -->
|
<!-- the following package(s) are from https://github.com/JamesNK/Newtonsoft.Json -->
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||||
|
<!-- the Application Insights package -->
|
||||||
|
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.10.0" />
|
||||||
<!-- the following package(s) are from https://github.com/dotnet/corefx -->
|
<!-- the following package(s) are from https://github.com/dotnet/corefx -->
|
||||||
<PackageReference Include="Microsoft.Win32.Registry.AccessControl" Version="4.6.0-preview8.19405.3" />
|
<PackageReference Include="Microsoft.Win32.Registry.AccessControl" Version="4.6.0-preview8.19405.3" />
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.6.0-preview8.19405.3" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.6.0-preview8.19405.3" />
|
||||||
|
@ -515,9 +515,12 @@ namespace System.Management.Automation
|
|||||||
// This is the start of the real implementation of autocomplete/intellisense/tab completion
|
// This is the start of the real implementation of autocomplete/intellisense/tab completion
|
||||||
private static CommandCompletion CompleteInputImpl(Ast ast, Token[] tokens, IScriptPosition positionOfCursor, Hashtable options)
|
private static CommandCompletion CompleteInputImpl(Ast ast, Token[] tokens, IScriptPosition positionOfCursor, Hashtable options)
|
||||||
{
|
{
|
||||||
|
#if LEGACYTELEMETRY
|
||||||
|
// We could start collecting telemetry at a later date.
|
||||||
|
// We will leave the #if to remind us that we did this once.
|
||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
|
#endif
|
||||||
using (var powershell = PowerShell.Create(RunspaceMode.CurrentRunspace))
|
using (var powershell = PowerShell.Create(RunspaceMode.CurrentRunspace))
|
||||||
{
|
{
|
||||||
var context = LocalPipeline.GetExecutionContextFromTLS();
|
var context = LocalPipeline.GetExecutionContextFromTLS();
|
||||||
@ -590,8 +593,10 @@ namespace System.Management.Automation
|
|||||||
}
|
}
|
||||||
|
|
||||||
var completionResults = results ?? EmptyCompletionResult;
|
var completionResults = results ?? EmptyCompletionResult;
|
||||||
sw.Stop();
|
|
||||||
#if LEGACYTELEMETRY
|
#if LEGACYTELEMETRY
|
||||||
|
// no telemetry here. We don't capture tab completion performance.
|
||||||
|
sw.Stop();
|
||||||
TelemetryAPI.ReportTabCompletionTelemetry(sw.ElapsedMilliseconds, completionResults.Count,
|
TelemetryAPI.ReportTabCompletionTelemetry(sw.ElapsedMilliseconds, completionResults.Count,
|
||||||
completionResults.Count > 0 ? completionResults[0].ResultType : CompletionResultType.Text);
|
completionResults.Count > 0 ? completionResults[0].ResultType : CompletionResultType.Text);
|
||||||
#endif
|
#endif
|
||||||
|
@ -9,6 +9,7 @@ using System.Management.Automation.Configuration;
|
|||||||
using System.Management.Automation.Internal;
|
using System.Management.Automation.Internal;
|
||||||
using System.Management.Automation.Tracing;
|
using System.Management.Automation.Tracing;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using Microsoft.PowerShell.Telemetry;
|
||||||
|
|
||||||
namespace System.Management.Automation
|
namespace System.Management.Automation
|
||||||
{
|
{
|
||||||
@ -150,6 +151,7 @@ namespace System.Management.Automation
|
|||||||
if (IsModuleFeatureName(name))
|
if (IsModuleFeatureName(name))
|
||||||
{
|
{
|
||||||
list.Add(name);
|
list.Add(name);
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalModuleFeatureActivation, name);
|
||||||
}
|
}
|
||||||
else if (IsEngineFeatureName(name))
|
else if (IsEngineFeatureName(name))
|
||||||
{
|
{
|
||||||
@ -157,6 +159,7 @@ namespace System.Management.Automation
|
|||||||
{
|
{
|
||||||
feature.Enabled = true;
|
feature.Enabled = true;
|
||||||
list.Add(name);
|
list.Add(name);
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalEngineFeatureActivation, name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -369,8 +369,9 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected override void BeginProcessing()
|
protected override void BeginProcessing()
|
||||||
{
|
{
|
||||||
|
#if LEGACYTELEMETRY
|
||||||
_timer.Start();
|
_timer.Start();
|
||||||
|
#endif
|
||||||
base.BeginProcessing();
|
base.BeginProcessing();
|
||||||
|
|
||||||
if (ShowCommandInfo.IsPresent && Syntax.IsPresent)
|
if (ShowCommandInfo.IsPresent && Syntax.IsPresent)
|
||||||
@ -552,9 +553,11 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LEGACYTELEMETRY
|
||||||
_timer.Stop();
|
_timer.Stop();
|
||||||
|
|
||||||
#if LEGACYTELEMETRY
|
// No telemetry here - capturing the name of a command which we are not familiar with
|
||||||
|
// may be confidential customer information
|
||||||
// We want telementry on commands people look for but don't exist - this should give us an idea
|
// We want telementry on commands people look for but don't exist - this should give us an idea
|
||||||
// what sort of commands people expect but either don't exist, or maybe should be installed by default.
|
// what sort of commands people expect but either don't exist, or maybe should be installed by default.
|
||||||
// The StartsWith is to avoid logging telemetry when suggestion mode checks the
|
// The StartsWith is to avoid logging telemetry when suggestion mode checks the
|
||||||
@ -1443,8 +1446,9 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
private Collection<WildcardPattern> _nounPatterns;
|
private Collection<WildcardPattern> _nounPatterns;
|
||||||
private Collection<WildcardPattern> _modulePatterns;
|
private Collection<WildcardPattern> _modulePatterns;
|
||||||
|
|
||||||
|
#if LEGACYTELEMETRY
|
||||||
private Stopwatch _timer = new Stopwatch();
|
private Stopwatch _timer = new Stopwatch();
|
||||||
|
#endif
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ShowCommandInfo support
|
#region ShowCommandInfo support
|
||||||
|
@ -10,6 +10,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Management.Automation;
|
using System.Management.Automation;
|
||||||
using System.Management.Automation.Internal;
|
using System.Management.Automation.Internal;
|
||||||
|
using System.Management.Automation.Language;
|
||||||
using System.Management.Automation.Runspaces;
|
using System.Management.Automation.Runspaces;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
@ -17,13 +18,13 @@ using System.Threading;
|
|||||||
|
|
||||||
using Microsoft.Management.Infrastructure;
|
using Microsoft.Management.Infrastructure;
|
||||||
using Microsoft.PowerShell.Cmdletization;
|
using Microsoft.PowerShell.Cmdletization;
|
||||||
|
using Microsoft.PowerShell.Telemetry;
|
||||||
|
|
||||||
using Dbg = System.Management.Automation.Diagnostics;
|
using Dbg = System.Management.Automation.Diagnostics;
|
||||||
|
|
||||||
using System.Management.Automation.Language;
|
|
||||||
using Parser = System.Management.Automation.Language.Parser;
|
using Parser = System.Management.Automation.Language.Parser;
|
||||||
using ScriptBlock = System.Management.Automation.ScriptBlock;
|
using ScriptBlock = System.Management.Automation.ScriptBlock;
|
||||||
using Token = System.Management.Automation.Language.Token;
|
using Token = System.Management.Automation.Language.Token;
|
||||||
|
|
||||||
#if LEGACYTELEMETRY
|
#if LEGACYTELEMETRY
|
||||||
using Microsoft.PowerShell.Telemetry.Internal;
|
using Microsoft.PowerShell.Telemetry.Internal;
|
||||||
#endif
|
#endif
|
||||||
@ -825,6 +826,12 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send telemetry on the imported modules
|
||||||
|
foreach (PSModuleInfo moduleInfo in remotelyImportedModules)
|
||||||
|
{
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, moduleInfo.Name);
|
||||||
|
}
|
||||||
|
|
||||||
return remotelyImportedModules;
|
return remotelyImportedModules;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1251,6 +1258,7 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
foreach (RemoteDiscoveryHelper.CimModule remoteCimModule in remotePsCimModules)
|
foreach (RemoteDiscoveryHelper.CimModule remoteCimModule in remotePsCimModules)
|
||||||
{
|
{
|
||||||
ImportModule_RemotelyViaCimModuleData(importModuleOptions, remoteCimModule, cimSession);
|
ImportModule_RemotelyViaCimModuleData(importModuleOptions, remoteCimModule, cimSession);
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, remoteCimModule.ModuleName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1743,6 +1751,7 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
// of doing Get-Module -list
|
// of doing Get-Module -list
|
||||||
foreach (PSModuleInfo module in ModuleInfo)
|
foreach (PSModuleInfo module in ModuleInfo)
|
||||||
{
|
{
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, module.Name);
|
||||||
RemoteDiscoveryHelper.DispatchModuleInfoProcessing(
|
RemoteDiscoveryHelper.DispatchModuleInfoProcessing(
|
||||||
module,
|
module,
|
||||||
localAction: delegate ()
|
localAction: delegate ()
|
||||||
@ -1772,6 +1781,7 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
{
|
{
|
||||||
foreach (Assembly suppliedAssembly in Assembly)
|
foreach (Assembly suppliedAssembly in Assembly)
|
||||||
{
|
{
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, suppliedAssembly.GetName().Name);
|
||||||
ImportModule_ViaAssembly(importModuleOptions, suppliedAssembly);
|
ImportModule_ViaAssembly(importModuleOptions, suppliedAssembly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1785,6 +1795,8 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
{
|
{
|
||||||
SetModuleBaseForEngineModules(foundModule.Name, this.Context);
|
SetModuleBaseForEngineModules(foundModule.Name, this.Context);
|
||||||
|
|
||||||
|
// Telemetry here - report module load
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, foundModule.Name);
|
||||||
#if LEGACYTELEMETRY
|
#if LEGACYTELEMETRY
|
||||||
TelemetryAPI.ReportModuleLoad(foundModule);
|
TelemetryAPI.ReportModuleLoad(foundModule);
|
||||||
#endif
|
#endif
|
||||||
@ -1809,6 +1821,7 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
BaseGuid = modulespec.Guid;
|
BaseGuid = modulespec.Guid;
|
||||||
|
|
||||||
PSModuleInfo foundModule = ImportModule_LocallyViaName(importModuleOptions, modulespec.Name);
|
PSModuleInfo foundModule = ImportModule_LocallyViaName(importModuleOptions, modulespec.Name);
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name);
|
||||||
if (foundModule != null)
|
if (foundModule != null)
|
||||||
{
|
{
|
||||||
SetModuleBaseForEngineModules(foundModule.Name, this.Context);
|
SetModuleBaseForEngineModules(foundModule.Name, this.Context);
|
||||||
@ -1818,6 +1831,10 @@ namespace Microsoft.PowerShell.Commands
|
|||||||
else if (this.ParameterSetName.Equals(ParameterSet_FQName_ViaPsrpSession, StringComparison.OrdinalIgnoreCase))
|
else if (this.ParameterSetName.Equals(ParameterSet_FQName_ViaPsrpSession, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
ImportModule_RemotelyViaPsrpSession(importModuleOptions, null, FullyQualifiedName, this.PSSession);
|
ImportModule_RemotelyViaPsrpSession(importModuleOptions, null, FullyQualifiedName, this.PSSession);
|
||||||
|
foreach (ModuleSpecification modulespec in FullyQualifiedName)
|
||||||
|
{
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ModuleLoad, modulespec.Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@ -16,6 +17,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Microsoft.Management.Infrastructure;
|
using Microsoft.Management.Infrastructure;
|
||||||
|
using Microsoft.PowerShell.Telemetry;
|
||||||
|
|
||||||
using Dbg = System.Management.Automation.Diagnostics;
|
using Dbg = System.Management.Automation.Diagnostics;
|
||||||
|
|
||||||
@ -638,6 +640,7 @@ namespace System.Management.Automation
|
|||||||
Streams = new PSDataStreams(this);
|
Streams = new PSDataStreams(this);
|
||||||
_endInvokeMethod = EndInvoke;
|
_endInvokeMethod = EndInvoke;
|
||||||
_endStopMethod = EndStop;
|
_endStopMethod = EndStop;
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.PowerShellCreate, "create");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -7,6 +7,7 @@ using System.Management.Automation.Runspaces;
|
|||||||
using System.Management.Automation.Tracing;
|
using System.Management.Automation.Tracing;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
|
using Microsoft.PowerShell.Telemetry;
|
||||||
|
|
||||||
using Dbg = System.Management.Automation.Diagnostics;
|
using Dbg = System.Management.Automation.Diagnostics;
|
||||||
|
|
||||||
@ -1025,6 +1026,10 @@ namespace System.Management.Automation.Internal
|
|||||||
CommandState.Started,
|
CommandState.Started,
|
||||||
commandProcessor.Command.MyInvocation);
|
commandProcessor.Command.MyInvocation);
|
||||||
|
|
||||||
|
// Telemetry here
|
||||||
|
// the type of command should be sent along
|
||||||
|
// commandProcessor.CommandInfo.CommandType
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ApplicationType, commandProcessor.Command.CommandInfo.CommandType.ToString());
|
||||||
#if LEGACYTELEMETRY
|
#if LEGACYTELEMETRY
|
||||||
Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceExecutedCommand(commandProcessor.Command.CommandInfo, commandProcessor.Command.CommandOrigin);
|
Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceExecutedCommand(commandProcessor.Command.CommandInfo, commandProcessor.Command.CommandOrigin);
|
||||||
#endif
|
#endif
|
||||||
|
@ -10,6 +10,7 @@ using System.Management.Automation.Remoting;
|
|||||||
using System.Management.Automation.Remoting.Client;
|
using System.Management.Automation.Remoting.Client;
|
||||||
using System.Management.Automation.Tracing;
|
using System.Management.Automation.Tracing;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Microsoft.PowerShell.Telemetry;
|
||||||
|
|
||||||
using Dbg = System.Management.Automation.Diagnostics;
|
using Dbg = System.Management.Automation.Diagnostics;
|
||||||
#if LEGACYTELEMETRY
|
#if LEGACYTELEMETRY
|
||||||
@ -854,6 +855,8 @@ namespace System.Management.Automation.Runspaces.Internal
|
|||||||
PSEtwLog.LogOperationalVerbose(PSEventId.RunspacePoolOpen, PSOpcode.Open,
|
PSEtwLog.LogOperationalVerbose(PSEventId.RunspacePoolOpen, PSOpcode.Open,
|
||||||
PSTask.CreateRunspace, PSKeyword.UseAlwaysOperational);
|
PSTask.CreateRunspace, PSKeyword.UseAlwaysOperational);
|
||||||
|
|
||||||
|
// Telemetry here - remote session
|
||||||
|
ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.RemoteSessionOpen, isAsync.ToString());
|
||||||
#if LEGACYTELEMETRY
|
#if LEGACYTELEMETRY
|
||||||
TelemetryAPI.ReportRemoteSessionCreated(_connectionInfo);
|
TelemetryAPI.ReportRemoteSessionCreated(_connectionInfo);
|
||||||
#endif
|
#endif
|
||||||
|
397
src/System.Management.Automation/utils/Telemetry.cs
Normal file
397
src/System.Management.Automation/utils/Telemetry.cs
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Management.Automation;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
using Microsoft.ApplicationInsights;
|
||||||
|
using Microsoft.ApplicationInsights.Extensibility;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerShell.Telemetry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The category of telemetry.
|
||||||
|
/// </summary>
|
||||||
|
internal enum TelemetryType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Telemetry of the application type (cmdlet, script, etc).
|
||||||
|
/// </summary>
|
||||||
|
ApplicationType,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send telemetry when we load a module, only module names in the s_knownModules list
|
||||||
|
/// will be reported, otherwise it will be "anonymous".
|
||||||
|
/// </summary>
|
||||||
|
ModuleLoad,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send telemetry for experimental module feature activation.
|
||||||
|
/// All experimental engine features will be have telemetry.
|
||||||
|
/// </summary>
|
||||||
|
ExperimentalEngineFeatureActivation,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send telemetry for experimental module feature activation.
|
||||||
|
/// Experimental module features will send telemetry based on the module it is in.
|
||||||
|
/// If we send telemetry for the module, we will also do so for any experimental feature
|
||||||
|
/// in that module.
|
||||||
|
/// </summary>
|
||||||
|
ExperimentalModuleFeatureActivation,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send telemetry for each PowerShell.Create API.
|
||||||
|
/// </summary>
|
||||||
|
PowerShellCreate,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remote session creation.
|
||||||
|
/// </summary>
|
||||||
|
RemoteSessionOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send up telemetry for startup.
|
||||||
|
/// </summary>
|
||||||
|
public static class ApplicationInsightsTelemetry
|
||||||
|
{
|
||||||
|
// If this env var is true, yes, or 1, telemetry will NOT be sent.
|
||||||
|
private const string _telemetryOptoutEnvVar = "POWERSHELL_TELEMETRY_OPTOUT";
|
||||||
|
|
||||||
|
// PSCoreInsight2 telemetry key
|
||||||
|
// private const string _psCoreTelemetryKey = "ee4b2115-d347-47b0-adb6-b19c2c763808"; // Production
|
||||||
|
private const string _psCoreTelemetryKey = "d26a5ef4-d608-452c-a6b8-a4a55935f70d"; // V7 Preview 3
|
||||||
|
|
||||||
|
// Use "anonymous" as the string to return when you can't report a name
|
||||||
|
private const string _anonymous = "anonymous";
|
||||||
|
|
||||||
|
// the telemetry failure string
|
||||||
|
private const string _telemetryFailure = "TELEMETRY_FAILURE";
|
||||||
|
|
||||||
|
// Telemetry client to be reused when we start sending more telemetry
|
||||||
|
private static TelemetryClient s_telemetryClient { get; set; }
|
||||||
|
|
||||||
|
// the unique identifier for the user, when we start we
|
||||||
|
private static string s_uniqueUserIdentifier { get; set; }
|
||||||
|
|
||||||
|
// the session identifier
|
||||||
|
private static string s_sessionId { get; set; }
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
private static HashSet<string> s_knownModules;
|
||||||
|
|
||||||
|
/// <summary>Gets a value indicating whether telemetry can be sent.</summary>
|
||||||
|
public static bool CanSendTelemetry { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes static members of the <see cref="ApplicationInsightsTelemetry"/> class.
|
||||||
|
/// Static constructor determines whether telemetry is to be sent, and then
|
||||||
|
/// sets the telemetry key and set the telemetry delivery mode.
|
||||||
|
/// Creates the session ID and initializes the HashSet of known module names.
|
||||||
|
/// Gets or constructs the unique identifier.
|
||||||
|
/// </summary>
|
||||||
|
static ApplicationInsightsTelemetry()
|
||||||
|
{
|
||||||
|
// If we can't send telemetry, there's no reason to do any of this
|
||||||
|
CanSendTelemetry = !GetEnvironmentVariableAsBool(name: _telemetryOptoutEnvVar, defaultValue: false);
|
||||||
|
if (CanSendTelemetry)
|
||||||
|
{
|
||||||
|
s_telemetryClient = new TelemetryClient();
|
||||||
|
TelemetryConfiguration.Active.InstrumentationKey = _psCoreTelemetryKey;
|
||||||
|
// Set this to true to reduce latency during development
|
||||||
|
TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = false;
|
||||||
|
s_sessionId = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
// use a hashset when looking for module names, it should be quicker than a string comparison
|
||||||
|
s_knownModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
|
||||||
|
"Microsoft.PowerShell.Archive",
|
||||||
|
"Microsoft.PowerShell.Host",
|
||||||
|
"Microsoft.PowerShell.Management",
|
||||||
|
"Microsoft.PowerShell.Security",
|
||||||
|
"Microsoft.PowerShell.Utility",
|
||||||
|
"PackageManagement",
|
||||||
|
"Pester",
|
||||||
|
"PowerShellGet",
|
||||||
|
"PSDesiredStateConfiguration",
|
||||||
|
"PSReadLine",
|
||||||
|
"ThreadJob",
|
||||||
|
};
|
||||||
|
s_uniqueUserIdentifier = GetUniqueIdentifier().ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine whether the environment variable is set and how.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the environment variable.</param>
|
||||||
|
/// <param name="defaultValue">If the environment variable is not set, use this as the default value.</param>
|
||||||
|
/// <returns>A boolean representing the value of the environment variable.</returns>
|
||||||
|
private static bool GetEnvironmentVariableAsBool(string name, bool defaultValue)
|
||||||
|
{
|
||||||
|
var str = Environment.GetEnvironmentVariable(name);
|
||||||
|
if (string.IsNullOrEmpty(str))
|
||||||
|
{
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (str.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "true":
|
||||||
|
case "1":
|
||||||
|
case "yes":
|
||||||
|
return true;
|
||||||
|
case "false":
|
||||||
|
case "0":
|
||||||
|
case "no":
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Send telemetry as a metric.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metricId">The type of telemetry that we'll be sending.</param>
|
||||||
|
/// <param name="data">The specific details about the telemetry.</param>
|
||||||
|
internal static void SendTelemetryMetric(TelemetryType metricId, string data)
|
||||||
|
{
|
||||||
|
if (!CanSendTelemetry)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string metricName = metricId.ToString();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (metricId)
|
||||||
|
{
|
||||||
|
case TelemetryType.ApplicationType:
|
||||||
|
case TelemetryType.PowerShellCreate:
|
||||||
|
case TelemetryType.RemoteSessionOpen:
|
||||||
|
case TelemetryType.ExperimentalEngineFeatureActivation:
|
||||||
|
s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, data);
|
||||||
|
break;
|
||||||
|
case TelemetryType.ExperimentalModuleFeatureActivation:
|
||||||
|
string experimentalFeatureName = GetExperimentalFeatureName(data);
|
||||||
|
s_telemetryClient.GetMetric(metricName, "uuid", "SessionId", "Detail").TrackValue(metricValue: 1.0, s_uniqueUserIdentifier, s_sessionId, experimentalFeatureName);
|
||||||
|
break;
|
||||||
|
case TelemetryType.ModuleLoad:
|
||||||
|
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
|
||||||
|
{
|
||||||
|
// do nothing, telemetry can't be sent
|
||||||
|
// don't send the panic telemetry as if we have failed above, it will likely fail here.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the experimental feature name. If we can report it, we'll return the name of the feature, otherwise, we'll return "anonymous"
|
||||||
|
private static string GetExperimentalFeatureName(string featureNameToValidate)
|
||||||
|
{
|
||||||
|
// An experimental feature in a module is guaranteed to start with the module name
|
||||||
|
// we can strip out the text past the last '.' as the text before that will be the ModuleName
|
||||||
|
int lastDotIndex = featureNameToValidate.LastIndexOf('.');
|
||||||
|
string moduleName = featureNameToValidate.Substring(0, lastDotIndex);
|
||||||
|
if (s_knownModules.Contains(moduleName))
|
||||||
|
{
|
||||||
|
return featureNameToValidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _anonymous;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the module name. If we can report it, we'll return the name, otherwise, we'll return "anonymous"
|
||||||
|
private static string GetModuleName(string moduleNameToValidate)
|
||||||
|
{
|
||||||
|
if (s_knownModules.Contains(moduleNameToValidate))
|
||||||
|
{
|
||||||
|
return moduleNameToValidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _anonymous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create the startup payload and send it up.
|
||||||
|
/// 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)
|
||||||
|
{
|
||||||
|
if (!CanSendTelemetry)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
var channel = Environment.GetEnvironmentVariable("POWERSHELL_DISTRIBUTION_CHANNEL");
|
||||||
|
|
||||||
|
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);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
s_telemetryClient.TrackEvent("ConsoleHostStartup", properties, null);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// do nothing, telemetry cannot be sent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to read the file and collect the guid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="telemetryFilePath">The path to the telemetry file.</param>
|
||||||
|
/// <param name="id">The newly created id.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The method returns a bool indicating success or failure of creating the id.
|
||||||
|
/// </returns>
|
||||||
|
private static bool TryGetIdentifier(string telemetryFilePath, out Guid id)
|
||||||
|
{
|
||||||
|
if (File.Exists(telemetryFilePath))
|
||||||
|
{
|
||||||
|
// attempt to read the persisted identifier
|
||||||
|
const int GuidSize = 16;
|
||||||
|
byte[] buffer = new byte[GuidSize];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (FileStream fs = new FileStream(telemetryFilePath, FileMode.Open, FileAccess.Read))
|
||||||
|
{
|
||||||
|
// if the read is invalid, or wrong size, we return it
|
||||||
|
int n = fs.Read(buffer, 0, GuidSize);
|
||||||
|
if (n == GuidSize)
|
||||||
|
{
|
||||||
|
// it's possible this could through
|
||||||
|
id = new Guid(buffer);
|
||||||
|
if (id != Guid.Empty)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// something went wrong, the file may not exist or not have enough bytes, so return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id = Guid.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </returns>
|
||||||
|
private static bool TryCreateUniqueIdentifierAndFile(string telemetryFilePath, out Guid id)
|
||||||
|
{
|
||||||
|
// one last attempt to retrieve before creating incase we have a lot of simultaneous entry into the mutex.
|
||||||
|
id = Guid.Empty;
|
||||||
|
if (TryGetIdentifier(telemetryFilePath, out id))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The directory may not exist, so attempt to create it
|
||||||
|
// CreateDirectory will simply return the directory if exists
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and save the new identifier, and if there's a problem, disable telemetry
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the unique identifier from the persisted file, if it doesn't exist create it.
|
||||||
|
/// Generate a guid which will be used as the UUID.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A guid which represents the unique identifier.</returns>
|
||||||
|
private static Guid GetUniqueIdentifier()
|
||||||
|
{
|
||||||
|
// Try to get the unique id. If this returns false, we'll
|
||||||
|
// create/recreate the telemetry.uuid file to persist for next startup.
|
||||||
|
Guid id = Guid.Empty;
|
||||||
|
string uuidPath = Path.Join(Platform.CacheDirectory, "telemetry.uuid");
|
||||||
|
if (TryGetIdentifier(uuidPath, out id))
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple processes may start simultaneously so we need a system wide
|
||||||
|
// way to control access to the file in the case (although remote) when we have
|
||||||
|
// simulataneous shell starts without the persisted file which attempt to create the file.
|
||||||
|
using (var m = new Mutex(true, "CreateUniqueUserId"))
|
||||||
|
{
|
||||||
|
// TryCreateUniqueIdentifierAndFile shouldn't throw, but the mutex might
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m.WaitOne();
|
||||||
|
if (TryCreateUniqueIdentifierAndFile(uuidPath, out id))
|
||||||
|
{
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Any problem in generating a uuid will result in no telemetry being sent.
|
||||||
|
// Try to send the failure in telemetry, but it will have no unique id.
|
||||||
|
s_telemetryClient.GetMetric(_telemetryFailure, "Detail").TrackValue(1, "mutex");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m.ReleaseMutex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// something bad happened, turn off telemetry since the unique id wasn't set.
|
||||||
|
CanSendTelemetry = false;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -95,6 +95,11 @@ Describe "Verify Markdown Links" {
|
|||||||
it "<url> should work" -TestCases $trueFailures {
|
it "<url> should work" -TestCases $trueFailures {
|
||||||
param($url)
|
param($url)
|
||||||
|
|
||||||
|
# there could be multiple reasons why a failure is ok
|
||||||
|
# check against the allowed failures
|
||||||
|
# 503 = service temporarily unavailable
|
||||||
|
$allowedFailures = @( 503 )
|
||||||
|
|
||||||
$prefix = $url.Substring(0,7)
|
$prefix = $url.Substring(0,7)
|
||||||
|
|
||||||
# Logging for diagnosability. Azure DevOps sometimes redacts the full url.
|
# Logging for diagnosability. Azure DevOps sometimes redacts the full url.
|
||||||
@ -108,7 +113,9 @@ Describe "Verify Markdown Links" {
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
throw "retry of URL failed with error: $($_.Message)"
|
if ( $allowedFailures -notcontains $_.Exception.Response.StatusCode ) {
|
||||||
|
throw "retry of URL failed with error: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -43,7 +43,6 @@ Describe "Validate start of console host" -Tag CI {
|
|||||||
'System.Private.CoreLib.dll'
|
'System.Private.CoreLib.dll'
|
||||||
'System.Private.Uri.dll'
|
'System.Private.Uri.dll'
|
||||||
'System.Private.Xml.dll'
|
'System.Private.Xml.dll'
|
||||||
'System.Private.Xml.Linq.dll'
|
|
||||||
'System.Reflection.Emit.ILGeneration.dll'
|
'System.Reflection.Emit.ILGeneration.dll'
|
||||||
'System.Reflection.Emit.Lightweight.dll'
|
'System.Reflection.Emit.Lightweight.dll'
|
||||||
'System.Reflection.Primitives.dll'
|
'System.Reflection.Primitives.dll'
|
||||||
@ -67,9 +66,7 @@ Describe "Validate start of console host" -Tag CI {
|
|||||||
'System.Threading.Tasks.Parallel.dll'
|
'System.Threading.Tasks.Parallel.dll'
|
||||||
'System.Threading.Thread.dll'
|
'System.Threading.Thread.dll'
|
||||||
'System.Threading.ThreadPool.dll'
|
'System.Threading.ThreadPool.dll'
|
||||||
'System.Threading.Timer.dll'
|
|
||||||
'System.Xml.ReaderWriter.dll'
|
'System.Xml.ReaderWriter.dll'
|
||||||
'System.Xml.XDocument.dll'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if ($IsWindows) {
|
if ($IsWindows) {
|
||||||
|
131
test/powershell/engine/Basic/Telemetry.Tests.ps1
Normal file
131
test/powershell/engine/Basic/Telemetry.Tests.ps1
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
# Licensed under the MIT License.
|
||||||
|
|
||||||
|
# unit tests for telemetry
|
||||||
|
# these tests aren't going to check that telemetry is being sent
|
||||||
|
# only that we're not treating the telemetry.uuid file correctly
|
||||||
|
|
||||||
|
Describe "Telemetry for shell startup" -Tag CI {
|
||||||
|
BeforeAll {
|
||||||
|
# if the telemetry file exists, move it out of the way
|
||||||
|
# the member is internal, but we can retrieve it via reflection
|
||||||
|
$cacheDir = [System.Management.Automation.Platform].GetMember("CacheDirectory","NonPublic,Static").GetMethod.Invoke($null, $null)
|
||||||
|
$uuidPath = Join-Path -Path $cacheDir -ChildPath telemetry.uuid
|
||||||
|
$uuidFileExists = Test-Path -Path $uuidPath
|
||||||
|
if ( $uuidFileExists ) {
|
||||||
|
$originalBytes = Get-Content -AsByteStream -Path $uuidPath
|
||||||
|
Rename-Item -Path $uuidPath -NewName "${uuidPath}.original"
|
||||||
|
}
|
||||||
|
|
||||||
|
$PWSH = (Get-Process -Id $PID).MainModule.FileName
|
||||||
|
$telemetrySet = Test-Path -Path env:POWERSHELL_TELEMETRY_OPTOUT
|
||||||
|
$SendingTelemetry = $env:POWERSHELL_TELEMETRY_OPTOUT
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterAll {
|
||||||
|
# check and reset the telemetry.uuid file
|
||||||
|
if ( $uuidFileExists ) {
|
||||||
|
if ( Test-Path -Path "${uuidPath}.original" ) {
|
||||||
|
Rename-Item -NewName $uuidPath -Path "${uuidPath}.original" -Force
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[System.IO.File]::WriteAllBytes($uuidPath, $originalBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( $telemetrySet ) {
|
||||||
|
$env:POWERSHELL_TELEMETRY_OPTOUT = $SendingTelemetry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AfterEach {
|
||||||
|
if ( Test-Path -Path $uuidPath ) {
|
||||||
|
Remove-Item -Path $uuidPath
|
||||||
|
}
|
||||||
|
if ( Test-Path -Path env:POWERSHELL_TELEMETRY_OPTOUT ) {
|
||||||
|
Remove-Item env:POWERSHELL_TELEMETRY_OPTOUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should not create a uuid file if telemetry is opted out" {
|
||||||
|
$env:POWERSHELL_TELEMETRY_OPTOUT = 1
|
||||||
|
& $PWSH -NoProfile -Command "exit"
|
||||||
|
$uuidPath | Should -Not -Exist
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should create a uuid file if telemetry is opted in" {
|
||||||
|
$env:POWERSHELL_TELEMETRY_OPTOUT = "no"
|
||||||
|
& $PWSH -NoProfile -Command "exit"
|
||||||
|
$uuidPath | Should -Exist
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should create a uuid file by default" {
|
||||||
|
if ( Test-Path env:POWERSHELL_TELEMETRY_OPTOUT ) { Remove-Item -Path env:POWERSHELL_TELEMETRY_OPTOUT }
|
||||||
|
& $PWSH -NoProfile -Command "exit"
|
||||||
|
$uuidPath | Should -Exist
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should create a property uuid file when telemetry is sent" {
|
||||||
|
$env:POWERSHELL_TELEMETRY_OPTOUT = "no"
|
||||||
|
& $PWSH -NoProfile -Command "exit"
|
||||||
|
$uuidPath | Should -Exist
|
||||||
|
(Get-ChildItem -Path $uuidPath).Length | Should -Be 16
|
||||||
|
[byte[]]$newBytes = Get-Content -AsByteStream -Path $uuidPath
|
||||||
|
[System.Guid]::New($newBytes) | Should -BeOfType [System.Guid]
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should not create a telemetry file if one already exists and telemetry is opted in" {
|
||||||
|
[byte[]]$bytes = [System.Guid]::NewGuid().ToByteArray()
|
||||||
|
[System.IO.File]::WriteAllBytes($uuidPath, $bytes)
|
||||||
|
& $PWSH -NoProfile -Command "exit"
|
||||||
|
[byte[]]$newBytes = Get-Content -AsByteStream -Path $uuidPath
|
||||||
|
Compare-Object -ReferenceObject $bytes -DifferenceObject $newBytes | Should -BeNullOrEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should create a new telemetry file if the current one is 00000000-0000-0000-0000-000000000000" {
|
||||||
|
[byte[]]$zeroGuid = [System.Guid]::Empty.ToByteArray()
|
||||||
|
[System.IO.File]::WriteAllBytes($uuidPath, $zeroGuid)
|
||||||
|
& $PWSH -NoProfile -Command "exit"
|
||||||
|
[byte[]]$newBytes = Get-Content -AsByteStream -Path $uuidPath
|
||||||
|
# we could legitimately have zeros in the new guid, so we can't check for that
|
||||||
|
# we're just making sure that there *is* a difference
|
||||||
|
Compare-Object -ReferenceObject $zeroGuid -DifferenceObject $newBytes | Should -Not -BeNullOrEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should create a new telemetry file if the current one is smaller than 16 bytes" {
|
||||||
|
$badBytes = [byte[]]::new(8);
|
||||||
|
[System.IO.File]::WriteAllBytes($uuidPath, $badBytes)
|
||||||
|
& $PWSH -NoProfile -Command "exit"
|
||||||
|
[byte[]]$nb = Get-Content -AsByteStream -Path $uuidPath
|
||||||
|
[System.Guid]::New($nb) | Should -BeOfType [System.Guid]
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should not create a new telemetry file if the current one has a valid guid and is larger than 16 bytes" {
|
||||||
|
$g = [Guid]::newGuid()
|
||||||
|
$tooManyBytes = $g.ToByteArray() * 2
|
||||||
|
[System.IO.File]::WriteAllBytes($uuidPath, $tooManyBytes)
|
||||||
|
[byte[]]$nb = Get-Content -Path $uuidPath -AsByteStream | Select-Object -First 16
|
||||||
|
$ng = [System.Guid]::new($nb)
|
||||||
|
$g | Should -Be $ng
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should properly set whether telemetry is sent based on when environment variable is not set" {
|
||||||
|
$result = & $PWSH -NoProfile -Command '[Microsoft.PowerShell.Telemetry.ApplicationInsightsTelemetry]::CanSendTelemetry'
|
||||||
|
$result | Should -Be "True"
|
||||||
|
}
|
||||||
|
|
||||||
|
$telemetryIsSetData = @(
|
||||||
|
@{ name = "set to no"; Value = "no" ; expectedValue = "True" }
|
||||||
|
@{ name = "set to 0"; Value = "0"; expectedValue = "True" }
|
||||||
|
@{ name = "set to false"; Value = "false"; expectedValue = "True" }
|
||||||
|
@{ name = "set to yes"; Value = "yes"; expectedValue = "False" }
|
||||||
|
@{ name = "set to 1"; Value = "1"; expectedValue = "False" }
|
||||||
|
@{ name = "set to true"; Value = "true"; expectedValue = "False" }
|
||||||
|
)
|
||||||
|
|
||||||
|
It "Should properly set whether telemetry is sent based on environment variable when <name>" -TestCases $telemetryIsSetData {
|
||||||
|
param ( [string]$name, [string]$value, [string]$expectedValue )
|
||||||
|
$env:POWERSHELL_TELEMETRY_OPTOUT = $value
|
||||||
|
$result = & $PWSH -NoProfile -Command '[Microsoft.PowerShell.Telemetry.ApplicationInsightsTelemetry]::CanSendTelemetry'
|
||||||
|
$result | Should -Be $expectedValue
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user