diff --git a/src/NuGet.Clients/NuGet.Console/Xamls/ConsoleContainer.xaml.cs b/src/NuGet.Clients/NuGet.Console/Xamls/ConsoleContainer.xaml.cs index f8c5cc3a737..6c715d45e8d 100644 --- a/src/NuGet.Clients/NuGet.Console/Xamls/ConsoleContainer.xaml.cs +++ b/src/NuGet.Clients/NuGet.Console/Xamls/ConsoleContainer.xaml.cs @@ -14,6 +14,7 @@ using NuGet.VisualStudio; using NuGet.VisualStudio.Common; using NuGet.VisualStudio.Internal.Contracts; +using NuGet.VisualStudio.Telemetry.PowerShell; namespace NuGetConsole { @@ -28,6 +29,9 @@ public ConsoleContainer() { InitializeComponent(); + Loaded += ConsoleContainer_Loaded; + Unloaded += ConsoleContainer_UnLoaded; + ThreadHelper.JoinableTaskFactory.StartOnIdle( async () => { @@ -76,6 +80,9 @@ public void NotifyInitializationCompleted() public void Dispose() { + Loaded -= ConsoleContainer_Loaded; + Unloaded -= ConsoleContainer_UnLoaded; + // Use more verbose null-checking syntax to avoid ISB001 misfiring. if (_solutionManager != null) { @@ -84,5 +91,15 @@ public void Dispose() GC.SuppressFinalize(this); } + + void ConsoleContainer_Loaded(object sender, RoutedEventArgs e) + { + NuGetPowerShellUsage.RaisePmcWindowsLoadEvent(isLoad: true); + } + + void ConsoleContainer_UnLoaded(object sender, RoutedEventArgs e) + { + NuGetPowerShellUsage.RaisePmcWindowsLoadEvent(isLoad: false); + } } } diff --git a/src/NuGet.Clients/NuGet.PackageManagement.PowerShellCmdlets/NuGetPowerShellBaseCommand.cs b/src/NuGet.Clients/NuGet.PackageManagement.PowerShellCmdlets/NuGetPowerShellBaseCommand.cs index 0b5917d81d5..831d3e57341 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.PowerShellCmdlets/NuGetPowerShellBaseCommand.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.PowerShellCmdlets/NuGetPowerShellBaseCommand.cs @@ -28,6 +28,7 @@ using NuGet.Protocol.Core.Types; using NuGet.Versioning; using NuGet.VisualStudio; +using NuGet.VisualStudio.Telemetry.PowerShell; using ExecutionContext = NuGet.ProjectManagement.ExecutionContext; namespace NuGet.PackageManagement.PowerShellCmdlets @@ -214,6 +215,9 @@ protected override sealed void ProcessRecord() stopWatch.Start(); try { + // Record NuGetCmdlet executed + NuGetPowerShellUsage.RaiseNuGetCmdletExecutedEvent(); + ProcessRecordCore(); } catch (Exception ex) diff --git a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/IDE/VSSolutionManager.cs b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/IDE/VSSolutionManager.cs index b9d94e4d944..ffe312f6ad4 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/IDE/VSSolutionManager.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.VisualStudio/IDE/VSSolutionManager.cs @@ -26,6 +26,7 @@ using NuGet.Protocol; using NuGet.Protocol.Core.Types; using NuGet.VisualStudio; +using NuGet.VisualStudio.Telemetry.PowerShell; using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; using Task = System.Threading.Tasks.Task; @@ -184,8 +185,6 @@ private async Task InitializeAsync() }); }); - TelemetryActivity.NuGetTelemetryService = new NuGetVSTelemetryService(); - _vsMonitorSelection = await _asyncServiceProvider.GetServiceAsync(); var solutionLoadedGuid = VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_guid; @@ -499,6 +498,8 @@ private async Task OnSolutionExistsAndFullyLoadedAsync() SolutionOpening?.Invoke(this, EventArgs.Empty); + NuGetPowerShellUsage.RaiseSolutionOpenEvent(); + // although the SolutionOpened event fires, the solution may be only in memory (e.g. when // doing File - New File). In that case, we don't want to act on the event. if (!await IsSolutionOpenAsync()) @@ -526,6 +527,8 @@ private void OnAfterClosing() private void OnBeforeClosing() { + NuGetPowerShellUsage.RaiseSolutionCloseEvent(); + SolutionClosing?.Invoke(this, EventArgs.Empty); } diff --git a/src/NuGet.Clients/NuGet.Tools/NuGetPackage.cs b/src/NuGet.Clients/NuGet.Tools/NuGetPackage.cs index c85519b6d86..e487567017f 100644 --- a/src/NuGet.Clients/NuGet.Tools/NuGetPackage.cs +++ b/src/NuGet.Clients/NuGet.Tools/NuGetPackage.cs @@ -28,6 +28,7 @@ using NuGet.VisualStudio.Common; using NuGet.VisualStudio.Internal.Contracts; using NuGet.VisualStudio.Telemetry; +using NuGet.VisualStudio.Telemetry.PowerShell; using NuGetConsole; using NuGetConsole.Implementation; using ContractsNuGetServices = NuGet.VisualStudio.Contracts.NuGetServices; @@ -96,6 +97,7 @@ public sealed class NuGetPackage : AsyncPackage, IVsPackageExtensionProvider, IV private uint _solutionExistsCookie; private bool _powerConsoleCommandExecuting; private bool _initialized; + private NuGetPowerShellUsageCollector _nuGetPowerShellUsageCollector; public NuGetPackage() { @@ -145,6 +147,9 @@ public NuGetPackage() /// protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { + _nuGetPowerShellUsageCollector = new NuGetPowerShellUsageCollector(); + NuGet.Common.TelemetryActivity.NuGetTelemetryService = new NuGetVSTelemetryService(); + await base.InitializeAsync(cancellationToken, progress); // Add our command handlers for menu (commands must exist in the .vsct file) @@ -179,6 +184,8 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke ThreadHelper.JoinableTaskFactory); await NuGetBrokeredServiceFactory.ProfferServicesAsync(this); + + VsShellUtilities.ShutdownToken.Register(RegisterEmitVSInstancePowerShellTelemetry); } /// @@ -1247,6 +1254,11 @@ private void OnBeginShutDown() _dteEvents = null; } + private void RegisterEmitVSInstancePowerShellTelemetry() + { + NuGetPowerShellUsage.RaiseVSInstanceCloseEvent(); + } + #region IVsPersistSolutionOpts // Called by the shell when a solution is opened and the SUO file is read. diff --git a/src/NuGet.Clients/NuGet.VisualStudio.Common/Telemetry/NuGetPowerShellUsageCollector.cs b/src/NuGet.Clients/NuGet.VisualStudio.Common/Telemetry/NuGetPowerShellUsageCollector.cs new file mode 100644 index 00000000000..5e9405f3a41 --- /dev/null +++ b/src/NuGet.Clients/NuGet.VisualStudio.Common/Telemetry/NuGetPowerShellUsageCollector.cs @@ -0,0 +1,365 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using NuGet.Common; +using NuGet.VisualStudio.Telemetry.PowerShell; + +namespace NuGet.VisualStudio.Telemetry +{ + public sealed class NuGetPowerShellUsageCollector : IDisposable + { + // PMC, PMUI powershell telemetry consts + public const string PmcExecuteCommandCount = nameof(PmcExecuteCommandCount); + public const string PmuiExecuteCommandCount = nameof(PmuiExecuteCommandCount); + public const string NuGetCommandUsed = nameof(NuGetCommandUsed); + public const string InitPs1LoadPmui = nameof(InitPs1LoadPmui); + public const string InitPs1LoadPmc = nameof(InitPs1LoadPmc); + public const string InitPs1LoadedFromPmcFirst = nameof(InitPs1LoadedFromPmcFirst); + public const string LoadedFromPmui = nameof(LoadedFromPmui); + public const string FirstTimeLoadedFromPmui = nameof(FirstTimeLoadedFromPmui); + public const string LoadedFromPmc = nameof(LoadedFromPmc); + public const string FirstTimeLoadedFromPmc = nameof(FirstTimeLoadedFromPmc); + public const string SolutionLoaded = nameof(SolutionLoaded); + public const string Trigger = nameof(Trigger); + public const string Pmc = nameof(Pmc); + public const string Pmui = nameof(Pmui); + + // PMC UI Console Container telemetry consts + public const string PmcWindowLoadCount = nameof(PmcWindowLoadCount); + public const string ReOpenAtStart = nameof(ReOpenAtStart); + + // Const name for emitting when VS solution close or VS instance close. + public const string SolutionClose = nameof(SolutionClose); + public const string InstanceClose = nameof(InstanceClose); + public const string PowerShellHost = "PowerShellHost."; + public const string SolutionCount = nameof(SolutionCount); + public const string PmcPowerShellLoadedSolutionCount = nameof(PmcPowerShellLoadedSolutionCount); + public const string PmuiPowerShellLoadedSolutionCount = nameof(PmuiPowerShellLoadedSolutionCount); + public const string PowerShellLoaded = nameof(PowerShellLoaded); + + private int _solutionCount; + private SolutionData _vsSolutionData; + private readonly InstanceData _vsInstanceData; + private object _lock = new object(); + + public NuGetPowerShellUsageCollector() + { + _vsSolutionData = new SolutionData(); + _vsInstanceData = new InstanceData(); + + NuGetPowerShellUsage.PowerShellLoadEvent += NuGetPowerShellUsage_PMCLoadEventHandler; + NuGetPowerShellUsage.PowerShellCommandExecuteEvent += NuGetPowerShellUsage_PowerShellCommandExecuteEventHandler; + NuGetPowerShellUsage.NuGetCmdletExecutedEvent += NuGetPowerShellUsage_NuGetCmdletExecutedEventHandler; + NuGetPowerShellUsage.InitPs1LoadEvent += NuGetPowerShellUsage_InitPs1LoadEventHandler; + NuGetPowerShellUsage.PmcWindowsEvent += NuGetPowerShellUsage_PMCWindowsEventHandler; + NuGetPowerShellUsage.SolutionOpenEvent += NuGetPowerShellUsage_SolutionOpenHandler; + NuGetPowerShellUsage.SolutionCloseEvent += NuGetPowerShellUsage_SolutionCloseHandler; + NuGetPowerShellUsage.VSInstanceCloseEvent += NuGetPowerShellUsage_VSInstanseCloseHandler; + } + + private void NuGetPowerShellUsage_PMCLoadEventHandler(bool isPMC) + { + AddPowerShellLoadedData(isPMC, _vsSolutionData); + } + + internal void AddPowerShellLoadedData(bool isPMC, SolutionData vsSolutionData) + { + lock (_lock) + { + if (isPMC) + { + if (!vsSolutionData.LoadedFromPmc) + { + vsSolutionData.FirstTimeLoadedFromPmc = true; + _vsInstanceData.PmcLoadedSolutionCount++; + } + + vsSolutionData.LoadedFromPmc = true; + } + else + { + if (!vsSolutionData.LoadedFromPmui) + { + vsSolutionData.FirstTimeLoadedFromPmui = true; + _vsInstanceData.PmuiLoadedSolutionCount++; + } + + vsSolutionData.LoadedFromPmui = true; + } + } + } + + private void NuGetPowerShellUsage_PowerShellCommandExecuteEventHandler(bool isPMC) + { + AddPowerShellCommandExecuteData(isPMC, _vsSolutionData); + } + + internal void AddPowerShellCommandExecuteData(bool isPMC, SolutionData vsSolutionData) + { + lock (_lock) + { + // Please note: Direct PMC and PMUI don't share same code path for installing packages with *.ps1 files + if (isPMC) + { + // For PMC all installation done in one pass so no double counting. + vsSolutionData.PmcExecuteCommandCount++; + _vsInstanceData.PmcExecuteCommandCount++; + } + else + { + // This one is called for both init.ps1 and install.ps1 seperately. + // install.ps1 running inside MSBuildNuGetProject.cs (InstallPackageAsync method) may result in duplicate counting. + // Also this concern valid for dependent packages (of installing package) with *.ps1 files. + vsSolutionData.PmuiExecuteCommandCount++; + _vsInstanceData.PmuiExecuteCommandCount++; + } + } + } + + private void NuGetPowerShellUsage_NuGetCmdletExecutedEventHandler() + { + AddNuGetCmdletExecutedData(_vsSolutionData); + } + + internal void AddNuGetCmdletExecutedData(SolutionData vsSolutionData) + { + lock (_lock) + { + vsSolutionData.NuGetCommandUsed = true; + } + } + + private void NuGetPowerShellUsage_InitPs1LoadEventHandler(bool isPMC) + { + AddInitPs1LoadData(isPMC, _vsSolutionData); + } + + internal void AddInitPs1LoadData(bool isPMC, SolutionData vsSolutionData) + { + lock (_lock) + { + if (isPMC && (!vsSolutionData.FirstTimeLoadedFromPmc && !vsSolutionData.FirstTimeLoadedFromPmui)) + { + vsSolutionData.InitPs1LoadedFromPmcFirst = true; + } + + if (isPMC) + { + vsSolutionData.InitPs1LoadPmc = true; + + if (!vsSolutionData.LoadedFromPmc) + { + vsSolutionData.FirstTimeLoadedFromPmc = true; + _vsInstanceData.PmcLoadedSolutionCount++; + } + + vsSolutionData.LoadedFromPmc = true; + } + else + { + vsSolutionData.InitPs1LoadPmui = true; + + if (!vsSolutionData.LoadedFromPmui) + { + vsSolutionData.FirstTimeLoadedFromPmui = true; + _vsInstanceData.PmuiLoadedSolutionCount++; + } + + vsSolutionData.LoadedFromPmui = true; + } + } + } + + private void NuGetPowerShellUsage_PMCWindowsEventHandler(bool isLoad) + { + AddPMCWindowsEventData(isLoad); + } + + internal void AddPMCWindowsEventData(bool isLoad) + { + lock (_lock) + { + if (isLoad) + { + _vsSolutionData.PmcWindowLoadCount++; + _vsInstanceData.PmcWindowLoadCount++; + } + + // Shutdown call happen before Unload event for PMCWindow so VSInstanceClose event going to have current up to date status. + _vsInstanceData.ReOpenAtStart = isLoad; + } + } + + private void NuGetPowerShellUsage_SolutionOpenHandler() + { + lock (_lock) + { + // Edge case: PMC used without solution load + if (!_vsSolutionData.SolutionLoaded && _vsSolutionData.PmcExecuteCommandCount > 0) + { + // PMC used before any solution is loaded, let's emit what we have for nugetvsinstanceclose event. + TelemetryActivity.EmitTelemetryEvent(_vsSolutionData.ToTelemetryEvent()); + ClearSolutionData(); + } + + if (_vsSolutionData.LoadedFromPmc) + { + _vsInstanceData.PmcLoadedSolutionCount++; + } + + if (_vsSolutionData.LoadedFromPmui) + { + _vsInstanceData.PmuiLoadedSolutionCount++; + } + + _vsInstanceData.SolutionCount = ++_solutionCount; + _vsSolutionData.SolutionLoaded = true; + } + } + + private void NuGetPowerShellUsage_SolutionCloseHandler() + { + lock (_lock) + { + TelemetryActivity.EmitTelemetryEvent(_vsSolutionData.ToTelemetryEvent()); + ClearSolutionData(); + } + } + + private void NuGetPowerShellUsage_VSInstanseCloseHandler() + { + lock (_lock) + { + // Edge case: PMC used without solution load + if (!_vsSolutionData.SolutionLoaded && _vsSolutionData.PmcExecuteCommandCount > 0) + { + // PMC used before any solution is loaded, let's emit what we have for nugetvsinstanceclose event. + TelemetryActivity.EmitTelemetryEvent(_vsSolutionData.ToTelemetryEvent()); + } + + // Emit VS Instance telemetry + TelemetryActivity.EmitTelemetryEvent(_vsInstanceData.ToTelemetryEvent()); + } + } + + // If open new solution then need to clear previous solution events. But powershell remain loaded in memory. + private void ClearSolutionData() + { + bool pmcPowershellLoad = _vsSolutionData.LoadedFromPmc; + bool pmuiPowershellLoad = _vsSolutionData.LoadedFromPmui; + + _vsSolutionData = new SolutionData(); + + _vsSolutionData.LoadedFromPmc = pmcPowershellLoad; + _vsSolutionData.LoadedFromPmui = pmuiPowershellLoad; + } + + public void Dispose() + { + NuGetPowerShellUsage.PowerShellLoadEvent -= NuGetPowerShellUsage_PMCLoadEventHandler; + NuGetPowerShellUsage.PowerShellCommandExecuteEvent -= NuGetPowerShellUsage_PowerShellCommandExecuteEventHandler; + NuGetPowerShellUsage.NuGetCmdletExecutedEvent -= NuGetPowerShellUsage_NuGetCmdletExecutedEventHandler; + NuGetPowerShellUsage.InitPs1LoadEvent -= NuGetPowerShellUsage_InitPs1LoadEventHandler; + NuGetPowerShellUsage.PmcWindowsEvent -= NuGetPowerShellUsage_PMCWindowsEventHandler; + NuGetPowerShellUsage.SolutionOpenEvent -= NuGetPowerShellUsage_SolutionOpenHandler; + NuGetPowerShellUsage.SolutionCloseEvent -= NuGetPowerShellUsage_SolutionCloseHandler; + NuGetPowerShellUsage.VSInstanceCloseEvent -= NuGetPowerShellUsage_VSInstanseCloseHandler; + } + + internal class SolutionData + { + internal bool FirstTimeLoadedFromPmc { get; set; } + internal bool FirstTimeLoadedFromPmui { get; set; } + internal bool InitPs1LoadedFromPmcFirst { get; set; } + internal bool InitPs1LoadPmc { get; set; } + internal bool InitPs1LoadPmui { get; set; } + internal bool LoadedFromPmc { get; set; } + internal bool LoadedFromPmui { get; set; } + internal bool NuGetCommandUsed { get; set; } + internal int PmcExecuteCommandCount { get; set; } + internal int PmcWindowLoadCount { get; set; } + internal int PmuiExecuteCommandCount { get; set; } + internal bool SolutionLoaded { get; set; } + + internal SolutionData() + { + FirstTimeLoadedFromPmc = false; + FirstTimeLoadedFromPmui = false; + InitPs1LoadedFromPmcFirst = false; + InitPs1LoadPmc = false; + InitPs1LoadPmui = false; + LoadedFromPmc = false; + LoadedFromPmui = false; + NuGetCommandUsed = false; + PmcExecuteCommandCount = 0; + PmcWindowLoadCount = 0; + PmuiExecuteCommandCount = 0; + SolutionLoaded = false; + + } + + internal TelemetryEvent ToTelemetryEvent() + { + var telemetry = new TelemetryEvent(SolutionClose, + new Dictionary() + { + { PowerShellHost + NuGetPowerShellUsageCollector.FirstTimeLoadedFromPmc , FirstTimeLoadedFromPmc }, + { PowerShellHost + NuGetPowerShellUsageCollector.FirstTimeLoadedFromPmui , FirstTimeLoadedFromPmui }, + { PowerShellHost + NuGetPowerShellUsageCollector.InitPs1LoadedFromPmcFirst , InitPs1LoadedFromPmcFirst }, + { PowerShellHost + NuGetPowerShellUsageCollector.InitPs1LoadPmc , InitPs1LoadPmc }, + { PowerShellHost + NuGetPowerShellUsageCollector.InitPs1LoadPmui , InitPs1LoadPmui }, + { PowerShellHost + NuGetPowerShellUsageCollector.LoadedFromPmc , LoadedFromPmc }, + { PowerShellHost + NuGetPowerShellUsageCollector.LoadedFromPmui , LoadedFromPmui }, + { PowerShellHost + NuGetPowerShellUsageCollector.NuGetCommandUsed , NuGetCommandUsed }, + { PowerShellHost + NuGetPowerShellUsageCollector.PmcExecuteCommandCount , PmcExecuteCommandCount }, + { PowerShellHost + NuGetPowerShellUsageCollector.PmcWindowLoadCount , PmcWindowLoadCount }, + { PowerShellHost + NuGetPowerShellUsageCollector.PmuiExecuteCommandCount , PmuiExecuteCommandCount }, + { PowerShellHost + NuGetPowerShellUsageCollector.SolutionLoaded , SolutionLoaded } + }); + + return telemetry; + } + } + + internal class InstanceData + { + internal int PmcExecuteCommandCount { get; set; } + internal int PmcWindowLoadCount { get; set; } + internal int PmuiExecuteCommandCount { get; set; } + internal int PmcLoadedSolutionCount { get; set; } + internal int PmuiLoadedSolutionCount { get; set; } + internal bool ReOpenAtStart { get; set; } + internal int SolutionCount { get; set; } + + internal InstanceData() + { + PmcExecuteCommandCount = 0; + PmcWindowLoadCount = 0; + PmuiExecuteCommandCount = 0; + PmcLoadedSolutionCount = 0; + PmuiLoadedSolutionCount = 0; + ReOpenAtStart = false; + SolutionCount = 0; + } + + internal TelemetryEvent ToTelemetryEvent() + { + var telemetry = new TelemetryEvent(InstanceClose, + new Dictionary() + { + { PowerShellHost + NuGetPowerShellUsageCollector.PmcExecuteCommandCount , PmcExecuteCommandCount }, + { PowerShellHost + NuGetPowerShellUsageCollector.PmcWindowLoadCount , PmcWindowLoadCount }, + { PowerShellHost + NuGetPowerShellUsageCollector.PmuiExecuteCommandCount , PmuiExecuteCommandCount }, + { PowerShellHost + NuGetPowerShellUsageCollector.PmcPowerShellLoadedSolutionCount , PmcLoadedSolutionCount }, + { PowerShellHost + NuGetPowerShellUsageCollector.PmuiPowerShellLoadedSolutionCount , PmuiLoadedSolutionCount }, + { PowerShellHost + NuGetPowerShellUsageCollector.ReOpenAtStart , ReOpenAtStart }, + { PowerShellHost + NuGetPowerShellUsageCollector.SolutionCount , SolutionCount }, + }); + + return telemetry; + } + } + } +} diff --git a/src/NuGet.Clients/NuGet.VisualStudio.Common/Telemetry/Powershell/NuGetPowerShellUsage.cs b/src/NuGet.Clients/NuGet.VisualStudio.Common/Telemetry/Powershell/NuGetPowerShellUsage.cs new file mode 100644 index 00000000000..2241b429648 --- /dev/null +++ b/src/NuGet.Clients/NuGet.VisualStudio.Common/Telemetry/Powershell/NuGetPowerShellUsage.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace NuGet.VisualStudio.Telemetry.PowerShell +{ + public static class NuGetPowerShellUsage + { + public delegate void SolutionOpenHandler(); + public static event SolutionOpenHandler SolutionOpenEvent; + + public delegate void SolutionCloseHandler(); + public static event SolutionCloseHandler SolutionCloseEvent; + + public delegate void VSInstanceCloseHandler(); + public static event VSInstanceCloseHandler VSInstanceCloseEvent; + + public delegate void PowerShellLoadEventHandler(bool isPMC); + public static event PowerShellLoadEventHandler PowerShellLoadEvent; + + public delegate void PowerShellCommandExecuteEventHandler(bool isPMC); + public static event PowerShellCommandExecuteEventHandler PowerShellCommandExecuteEvent; + + public delegate void NuGetCmdletExecutedEventHandler(); + public static event NuGetCmdletExecutedEventHandler NuGetCmdletExecutedEvent; + + public delegate void InitPs1LoadEventHandler(bool isPMC); + public static event InitPs1LoadEventHandler InitPs1LoadEvent; + + public delegate void PmcWindowEventHandler(bool isLoad); + public static event PmcWindowEventHandler PmcWindowsEvent; + + public static void RaisePowerShellLoadEvent(bool isPMC) + { + PowerShellLoadEvent?.Invoke(isPMC); + } + + public static void RaiseCommandExecuteEvent(bool isPMC) + { + PowerShellCommandExecuteEvent?.Invoke(isPMC); + } + + public static void RaiseNuGetCmdletExecutedEvent() + { + NuGetCmdletExecutedEvent?.Invoke(); + } + + public static void RaiseInitPs1LoadEvent(bool isPMC) + { + InitPs1LoadEvent?.Invoke(isPMC); + } + + public static void RaisePmcWindowsLoadEvent(bool isLoad) + { + PmcWindowsEvent?.Invoke(isLoad); + } + + public static void RaiseSolutionOpenEvent() + { + SolutionOpenEvent?.Invoke(); + } + + public static void RaiseSolutionCloseEvent() + { + SolutionCloseEvent?.Invoke(); + } + + public static void RaiseVSInstanceCloseEvent() + { + VSInstanceCloseEvent?.Invoke(); + } + } +} diff --git a/src/NuGet.Clients/NuGetConsole.Host.PowerShell/PowerShellHost.cs b/src/NuGet.Clients/NuGetConsole.Host.PowerShell/PowerShellHost.cs index 1b08399b451..7c9428fe7d3 100644 --- a/src/NuGet.Clients/NuGetConsole.Host.PowerShell/PowerShellHost.cs +++ b/src/NuGet.Clients/NuGetConsole.Host.PowerShell/PowerShellHost.cs @@ -29,6 +29,7 @@ using NuGet.Protocol.Core.Types; using NuGet.VisualStudio; using NuGet.VisualStudio.Telemetry; +using NuGet.VisualStudio.Telemetry.PowerShell; using Task = System.Threading.Tasks.Task; namespace NuGetConsole.Host.PowerShell.Implementation @@ -38,6 +39,7 @@ internal abstract class PowerShellHost : IHost, IPathExpansion, IDisposable private static readonly string AggregateSourceName = Resources.AggregateSourceName; private static readonly TimeSpan ExecuteInitScriptsRetryDelay = TimeSpan.FromMilliseconds(400); private static readonly int MaxTasks = 16; + private static bool PowerShellLoaded = false; private Microsoft.VisualStudio.Threading.AsyncLazy _vsMonitorSelection; private IVsMonitorSelection VsMonitorSelection => ThreadHelper.JoinableTaskFactory.Run(_vsMonitorSelection.GetValueAsync); @@ -114,7 +116,6 @@ protected PowerShellHost(string name, IRestoreEvents restoreEvents, IRunspaceMan _sourceControlManagerProvider = new Lazy( () => ServiceLocator.GetInstanceSafe()); _commonOperations = new Lazy(() => ServiceLocator.GetInstanceSafe()); - _name = name; IsCommandEnabled = true; @@ -300,6 +301,7 @@ public void Initialize(IConsole console) { try { + bool _isPmc = console is IWpfConsole; var result = _runspaceManager.GetRunspace(console, _name); Runspace = result.Item1; _nugetHost = result.Item2; @@ -312,10 +314,24 @@ public void Initialize(IConsole console) } UpdateWorkingDirectory(); + + if (!PowerShellLoaded) + { + var telemetryEvent = new TelemetryEvent(NuGetPowerShellUsageCollector.PowerShellLoaded, new Dictionary + { + { NuGetPowerShellUsageCollector.Trigger, _isPmc ? NuGetPowerShellUsageCollector.Pmc : NuGetPowerShellUsageCollector.Pmui } + }); + + TelemetryActivity.EmitTelemetryEvent(telemetryEvent); + PowerShellLoaded = true; + } + + NuGetPowerShellUsage.RaisePowerShellLoadEvent(isPMC: _isPmc); + await ExecuteInitScriptsAsync(); // check if PMC console is actually opened, then only hook to solution load/close events. - if (console is IWpfConsole) + if (_isPmc) { // Hook up solution events _solutionManager.Value.SolutionOpened += (_, __) => HandleSolutionOpened(); @@ -471,20 +487,24 @@ private async Task ExecuteInitPs1Async(string installPath, PackageIdentity ident AddPathToEnvironment(toolsPath); var scriptPath = Path.Combine(toolsPath, PowerShellScripts.Init); - if (File.Exists(scriptPath) && - _scriptExecutor.Value.TryMarkVisited(identity, PackageInitPS1State.FoundAndExecuted)) + if (File.Exists(scriptPath)) { - // always execute init script on a background thread - await TaskScheduler.Default; + NuGetPowerShellUsage.RaiseInitPs1LoadEvent(isPMC: _activeConsole is IWpfConsole); - var request = new ScriptExecutionRequest(scriptPath, installPath, identity, project: null); + if (_scriptExecutor.Value.TryMarkVisited(identity, PackageInitPS1State.FoundAndExecuted)) + { + // always execute init script on a background thread + await TaskScheduler.Default; - Runspace.Invoke( - request.BuildCommand(), - request.BuildInput(), - outputResults: true); + var request = new ScriptExecutionRequest(scriptPath, installPath, identity, project: null); - return; + Runspace.Invoke( + request.BuildCommand(), + request.BuildInput(), + outputResults: true); + + return; + } } } @@ -528,6 +548,8 @@ public bool Execute(IConsole console, string command, params object[] inputs) throw new ArgumentNullException(nameof(command)); } + NuGetPowerShellUsage.RaiseCommandExecuteEvent(isPMC: console is IWpfConsole); + // since install.ps1/uninstall.ps1 could depend on init scripts, so we need to make sure // to run it once for each solution NuGetUIThreadHelper.JoinableTaskFactory.Run(async () =>