Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
PSTelemetryMethods.cs535 linesDownload Raw Back to utils
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#if LEGACYTELEMETRY5 6using System;7using System.Collections.Generic;8using System.Diagnostics;9using System.Management.Automation.Internal;10using System.Diagnostics.CodeAnalysis;11using System.Management.Automation;12using System.Management.Automation.Language;13using System.Threading;14using System.Threading.Tasks;15 16namespace Microsoft.PowerShell.Telemetry.Internal17{18    /// <summary>19    /// </summary>20    [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")]21    public static class TelemetryAPI22    {23#region Public API24 25        /// <summary>26        /// Public API to expose Telemetry in PowerShell27        /// Provide meaningful message. Ex: PSCONSOLE_START, PSRUNSPACE_START28        /// arguments are of anonymous type. Ex: new { PSVersion = "5.0", PSRemotingProtocolVersion = "2.2"}29        /// </summary>30        public static void TraceMessage<T>(string message, T arguments)31        {32            TelemetryWrapper.TraceMessage(message, arguments);33        }34 35#endregion36 37        private static int s_anyPowerShellSessionOpen;38        private static DateTime s_sessionStartTime;39 40        private enum HostIsInteractive41        {42            Unknown,43            Interactive,44            NonInteractive45        }46 47        /// <summary>48        /// Called either after opening a runspace (the default), or by the host application.49        /// </summary>50        public static void ReportStartupTelemetry(IHostProvidesTelemetryData ihptd)51        {52            // Avoid reporting startup more than once, except if we report "exited" and53            // another runspace gets opened.54            if (Interlocked.CompareExchange(ref s_anyPowerShellSessionOpen, 1, 0) == 1)55                return;56 57            bool is32Bit = !Environment.Is64BitProcess;58            var psversion = PSVersionInfo.PSVersion.ToString();59            var hostName = Process.GetCurrentProcess().ProcessName;60            if (ihptd != null)61            {62                TelemetryWrapper.TraceMessage("PSHostStart", new63                {64                    Interactive = ihptd.HostIsInteractive ? HostIsInteractive.Interactive : HostIsInteractive.NonInteractive,65                    ProfileLoadTime = ihptd.ProfileLoadTimeInMS,66                    ReadyForInputTime = ihptd.ReadyForInputTimeInMS,67                    Is32Bit = is32Bit,68                    PSVersion = psversion,69                    ProcessName = hostName,70                });71            }72            else73            {74                TelemetryWrapper.TraceMessage("PSHostStart", new75                {76                    Interactive = HostIsInteractive.Unknown,77                    ProfileLoadTime = 0,78                    ReadyForInputTime = 0,79                    Is32Bit = is32Bit,80                    PSVersion = psversion,81                    ProcessName = hostName,82                });83            }84 85            s_sessionStartTime = DateTime.Now;86        }87 88        /// <summary>89        /// Called after there are no more open runspaces. In some host applications, this could90        /// report multiple exits.91        /// </summary>92        public static void ReportExitTelemetry(IHostProvidesTelemetryData ihptd)93        {94            TelemetryWrapper.TraceMessage("PSHostStop", new95            {96                InteractiveCommandCount = ihptd != null ? ihptd.InteractiveCommandCount : 0,97                TabCompletionTimes = s_tabCompletionTimes,98                TabCompletionCounts = s_tabCompletionCounts,99                TabCompletionResultCounts = s_tabCompletionResultCounts,100                SessionTime = (DateTime.Now - s_sessionStartTime).TotalMilliseconds101            });102 103            // In case a host opens another runspace, we will want another PSHostStart event,104            // so reset our flag here to allow that event to fire.105            s_anyPowerShellSessionOpen = 0;106        }107 108        /// <summary>109        /// Report Get-Help requests, how many results are returned, and how long it took.110        /// </summary>111        internal static void ReportGetHelpTelemetry(string name, int topicsFound, long timeInMS, bool updatedHelp)112        {113            TelemetryWrapper.TraceMessage("PSHelpRequest", new114            {115                TopicCount = topicsFound,116                TimeInMS = timeInMS,117                RanUpdateHelp = updatedHelp,118                HelpTopic = name,119            });120        }121 122        /// <summary>123        /// Report when Get-Command fails to find something.124        /// </summary>125        internal static void ReportGetCommandFailed(string[] name, long timeInMS)126        {127            TelemetryWrapper.TraceMessage("PSGetCommandFailed", new { TimeInMS = timeInMS, CommandNames = name });128        }129 130        private static long[] s_tabCompletionTimes = new long[(int)CompletionResultType.DynamicKeyword + 1];131        private static int[] s_tabCompletionCounts = new int[(int)CompletionResultType.DynamicKeyword + 1];132        private static int[] s_tabCompletionResultCounts = new int[(int)CompletionResultType.DynamicKeyword + 1];133        internal static void ReportTabCompletionTelemetry(long elapsedMilliseconds, int count, CompletionResultType completionResultType)134        {135            // We'll collect some general statistics.136            int idx = (int)completionResultType;137            if (idx >= 0 && idx <= (int)CompletionResultType.DynamicKeyword)138            {139                s_tabCompletionTimes[idx] += elapsedMilliseconds;140                s_tabCompletionCounts[idx]++;141                s_tabCompletionResultCounts[idx] += count;142            }143 144            // Also write an event for any slow tab completion (> 250ms).145            if (elapsedMilliseconds > 250)146            {147                TelemetryWrapper.TraceMessage("PSSlowTabCompletion", new148                {149                    Time = elapsedMilliseconds,150                    Count = count,151                    Type = completionResultType,152                });153            }154        }155 156        /// <summary>157        /// Report that a module was loaded, but only do so for modules that *might* be authored by Microsoft. We can't158        /// be 100% certain, but we'll ignore non-Microsoft module names when looking at any data, so it's best to159        /// at least attempt avoiding collecting data we'll ignore.160        /// </summary>161        internal static void ReportModuleLoad(PSModuleInfo foundModule)162        {163            var modulePath = foundModule.Path;164            var companyName = foundModule.CompanyName;165            bool couldBeMicrosoftModule =166                (modulePath != null &&167                 (modulePath.StartsWith(Utils.DefaultPowerShellAppBase, StringComparison.OrdinalIgnoreCase) ||168                  // The following covers both 64 and 32 bit Program Files by assuming 32bit is just ...\Program Files + " (x86)"169                  modulePath.StartsWith(Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles), StringComparison.OrdinalIgnoreCase))) ||170                (companyName != null &&171                 foundModule.CompanyName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase));172            if (couldBeMicrosoftModule)173            {174                TelemetryWrapper.TraceMessage("PSImportModule", new175                {176                    ModuleName = foundModule.Name,177                    Version = foundModule.Version.ToString()178                });179            }180        }181 182        /// <summary>183        /// Report that a new local session (runspace) is created.184        /// </summary>185        internal static void ReportLocalSessionCreated(186            System.Management.Automation.Runspaces.InitialSessionState iss,187            System.Management.Automation.Host.TranscriptionData transcriptionData)188        {189            bool isConstrained = (iss != null) && (iss.DefaultCommandVisibility != SessionStateEntryVisibility.Public) && (iss.LanguageMode != PSLanguageMode.FullLanguage);190            bool isTranscripting = (transcriptionData != null) && (transcriptionData.SystemTranscript != null);191 192            TelemetryWrapper.TraceMessage("PSNewLocalSession", new193            {194                Constrained = isConstrained,195                Transcripting = isTranscripting196            });197        }198 199        private enum RemoteSessionType200        {201            Unknown,202            LocalProcess,203            WinRMRemote,204            HyperVRemote,205            ContainerRemote206        }207 208        private enum RemoteConfigurationType209        {210            Unknown,211            PSDefault,212            PSWorkflow,213            ServerManagerWorkflow,214            Custom215        }216 217        /// <summary>218        /// Report that a new remote session (runspace) is created.219        /// </summary>220        internal static void ReportRemoteSessionCreated(221            System.Management.Automation.Runspaces.RunspaceConnectionInfo connectionInfo)222        {223            RemoteSessionType sessionType = RemoteSessionType.Unknown;224            RemoteConfigurationType configurationType = RemoteConfigurationType.Unknown;225            if (connectionInfo is System.Management.Automation.Runspaces.NewProcessConnectionInfo)226            {227                sessionType = RemoteSessionType.LocalProcess;228                configurationType = RemoteConfigurationType.PSDefault;229            }230            else231            {232                System.Management.Automation.Runspaces.WSManConnectionInfo wsManConnectionInfo = connectionInfo as System.Management.Automation.Runspaces.WSManConnectionInfo;233                if (wsManConnectionInfo != null)234                {235                    sessionType = RemoteSessionType.WinRMRemote;236 237                    // Parse configuration name from ShellUri:238                    //  ShellUri = 'http://schemas.microsoft.com/powershell/Microsoft.PowerShell'239                    //  ConfigName = 'Microsoft.PowerShell'240                    string configurationName = wsManConnectionInfo.ShellUri;241                    if (!string.IsNullOrEmpty(configurationName))242                    {243                        int index = configurationName.LastIndexOf('/');244                        if (index > -1)245                        {246                            configurationName = configurationName.Substring(index + 1);247                        }248                    }249 250                    configurationType = GetConfigurationTypefromName(configurationName);251                }252                else253                {254                    System.Management.Automation.Runspaces.VMConnectionInfo vmConnectionInfo = connectionInfo as System.Management.Automation.Runspaces.VMConnectionInfo;255                    if (vmConnectionInfo != null)256                    {257                        sessionType = RemoteSessionType.HyperVRemote;258                        configurationType = GetConfigurationTypefromName(vmConnectionInfo.ConfigurationName);259                    }260                    else261                    {262                        System.Management.Automation.Runspaces.ContainerConnectionInfo containerConnectionInfo = connectionInfo as System.Management.Automation.Runspaces.ContainerConnectionInfo;263                        if (containerConnectionInfo != null)264                        {265                            sessionType = RemoteSessionType.ContainerRemote;266                            configurationType = GetConfigurationTypefromName(267                                (containerConnectionInfo.ContainerProc != null) ? containerConnectionInfo.ContainerProc.ConfigurationName : string.Empty);268                        }269                    }270                }271            }272 273            TelemetryWrapper.TraceMessage("PSNewRemoteSession", new274            {275                Type = sessionType,276                Configuration = configurationType277            });278        }279 280        private static RemoteConfigurationType GetConfigurationTypefromName(string name)281        {282            string configName = (name != null) ? name.Trim() : string.Empty;283 284            if (string.IsNullOrEmpty(configName) ||285                configName.Equals("microsoft.powershell", StringComparison.OrdinalIgnoreCase) ||286                configName.Equals("microsoft.powershell32", StringComparison.OrdinalIgnoreCase))287            {288                return RemoteConfigurationType.PSDefault;289            }290            else if (configName.Equals("microsoft.powershell.workflow", StringComparison.OrdinalIgnoreCase))291            {292                return RemoteConfigurationType.PSWorkflow;293            }294            else if (configName.Equals("microsoft.windows.servermanagerworkflows", StringComparison.OrdinalIgnoreCase))295            {296                return RemoteConfigurationType.ServerManagerWorkflow;297            }298            else299            {300                return RemoteConfigurationType.Custom;301            }302        }303 304        private enum ScriptFileType305        {306            None = 0,307            Ps1 = 1,308            Psd1 = 2,309            Psm1 = 3,310            Other = 4,311        }312 313        private static readonly int s_promptHashCode = "prompt".GetHashCode();314 315        /// <summary>316        /// Report some telemetry about the scripts that are run.317        /// </summary>318        internal static void ReportScriptTelemetry(Ast ast, bool dotSourced, long compileTimeInMS)319        {320            if (ast.Parent != null || !TelemetryWrapper.IsEnabled)321                return;322 323            Task.Run(() =>324            {325                var extent = ast.Extent;326                var text = extent.Text;327                var hash = text.GetHashCode();328 329                // Ignore 'prompt' so we don't generate an event for every 'prompt' that is invoked.330                // (We really should only create 'prompt' once, but we don't.331                if (hash == s_promptHashCode)332                    return;333 334                var visitor = new ScriptBlockTelemetry();335 336                ast.Visit(visitor);337 338                var scriptFileType = ScriptFileType.None;339                var fileName = extent.File;340                if (fileName != null)341                {342                    var ext = System.IO.Path.GetExtension(fileName);343                    if (".ps1".Equals(ext, StringComparison.OrdinalIgnoreCase))344                    {345                        scriptFileType = ScriptFileType.Ps1;346                    }347                    else if (".psd1".Equals(ext, StringComparison.OrdinalIgnoreCase))348                    {349                        scriptFileType = ScriptFileType.Psd1;350                    }351                    else if (".psm1".Equals(ext, StringComparison.OrdinalIgnoreCase))352                    {353                        scriptFileType = ScriptFileType.Psm1;354                    }355                    else356                    {357                        // Reachable?358                        scriptFileType = ScriptFileType.Other;359                    }360                }361 362                TelemetryWrapper.TraceMessage("PSScriptDetails", new363                {364                    Hash = hash,365                    IsDotSourced = dotSourced,366                    ScriptFileType = scriptFileType,367                    Length = text.Length,368                    LineCount = extent.EndLineNumber - extent.StartLineNumber,369                    CompileTimeInMS = compileTimeInMS,370                    StatementCount = visitor.StatementCount,371                    CountOfCommands = visitor.CountOfCommands,372                    CountOfDotSourcedCommands = visitor.CountOfDotSourcedCommands,373                    MaxArrayLength = visitor.MaxArraySize,374                    ArrayLiteralCount = visitor.ArrayLiteralCount,375                    ArrayLiteralCumulativeSize = visitor.ArrayLiteralCumulativeSize,376                    MaxStringLength = visitor.MaxStringSize,377                    StringLiteralCount = visitor.StringLiteralCount,378                    StringLiteralCumulativeSize = visitor.StringLiteralCumulativeSize,379                    MaxPipelineDepth = visitor.MaxPipelineDepth,380                    PipelineCount = visitor.PipelineCount,381                    FunctionCount = visitor.FunctionCount,382                    ScriptBlockCount = visitor.ScriptBlockCount,383                    ClassCount = visitor.ClassCount,384                    EnumCount = visitor.EnumCount,385                    CommandsCalled = visitor.CommandsCalled,386                });387            });388        }389    }390 391    internal class ScriptBlockTelemetry : AstVisitor2392    {393        internal ScriptBlockTelemetry()394        {395            CommandsCalled = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);396        }397 398        internal Dictionary<string, int> CommandsCalled { get; private set; }399 400        internal int CountOfCommands { get; private set; }401 402        internal int CountOfDotSourcedCommands { get; private set; }403 404        public override AstVisitAction VisitCommand(CommandAst commandAst)405        {406            CountOfCommands++;407            var commandName = commandAst.GetCommandName();408            if (commandName != null)409            {410                int commandCount;411                CommandsCalled.TryGetValue(commandName, out commandCount);412                CommandsCalled[commandName] = commandCount + 1;413            }414 415            if (commandAst.InvocationOperator == TokenKind.Dot)416                CountOfDotSourcedCommands++;417 418            return AstVisitAction.Continue;419        }420 421        internal int MaxStringSize { get; private set; }422 423        internal int StringLiteralCount { get; private set; }424 425        internal int StringLiteralCumulativeSize { get; private set; }426 427        public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst)428        {429            var stringSize = stringConstantExpressionAst.Value.Length;430            StringLiteralCount += 1;431            StringLiteralCumulativeSize += stringSize;432            MaxStringSize = Math.Max(MaxStringSize, stringSize);433            return AstVisitAction.Continue;434        }435 436        public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst)437        {438            var stringSize = expandableStringExpressionAst.Value.Length;439            StringLiteralCount += 1;440            StringLiteralCumulativeSize += stringSize;441            MaxStringSize = Math.Max(MaxStringSize, stringSize);442            return AstVisitAction.Continue;443        }444 445        internal int MaxArraySize { get; private set; }446 447        internal int ArrayLiteralCount { get; private set; }448 449        internal int ArrayLiteralCumulativeSize { get; private set; }450 451        public override AstVisitAction VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst)452        {453            var elementCount = arrayLiteralAst.Elements.Count;454            ArrayLiteralCount += 1;455            ArrayLiteralCumulativeSize += elementCount;456            MaxArraySize = Math.Max(MaxArraySize, elementCount);457            return AstVisitAction.Continue;458        }459 460        internal int StatementCount { get; private set; }461 462        public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatementAst)463        {464            StatementCount += blockStatementAst.Body.Statements.Count;465            return AstVisitAction.Continue;466        }467 468        public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst)469        {470            StatementCount += namedBlockAst.Statements.Count;471            return AstVisitAction.Continue;472        }473 474        internal int FunctionCount { get; private set; }475 476        public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)477        {478            FunctionCount += 1;479            return AstVisitAction.Continue;480        }481 482        internal int ScriptBlockCount { get; private set; }483 484        public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)485        {486            ScriptBlockCount += 1;487            return AstVisitAction.Continue;488        }489 490        internal int MaxPipelineDepth { get; private set; }491 492        internal int PipelineCount { get; private set; }493 494        public override AstVisitAction VisitPipeline(PipelineAst pipelineAst)495        {496            MaxPipelineDepth = Math.Max(MaxPipelineDepth, pipelineAst.PipelineElements.Count);497            PipelineCount += 1;498            return AstVisitAction.Continue;499        }500 501        internal int ClassCount { get; private set; }502 503        internal int EnumCount { get; private set; }504 505        public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst)506        {507            if (typeDefinitionAst.IsClass)508                ClassCount += 1;509            else if (typeDefinitionAst.IsEnum)510                EnumCount += 1;511            return AstVisitAction.Continue;512        }513    }514 515    /// <summary>516    /// If implemented by the host, the host should call <see cref="TelemetryAPI.ReportStartupTelemetry"/> and <see cref="TelemetryAPI.ReportExitTelemetry"/>517    /// and track the data defined by this interface.518    /// </summary>519    public interface IHostProvidesTelemetryData520    {521        /// <summary>A host sets this property as appropriate - used when reporting telemetry.</summary>522        bool HostIsInteractive { get; }523 524        /// <summary>A host sets this property as appropriate - used when reporting telemetry.</summary>525        double ProfileLoadTimeInMS { get; }526 527        /// <summary>A host sets this property as appropriate - used when reporting telemetry.</summary>528        double ReadyForInputTimeInMS { get; }529 530        /// <summary>A host sets this property as appropriate - used when reporting telemetry.</summary>531        int InteractiveCommandCount { get; }532    }533}534#endif535