Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
HelpCommands.cs905 linesDownload Raw Back to help
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics;8using System.Diagnostics.CodeAnalysis;9using System.Globalization;10using System.IO;11using System.Linq;12using System.Management.Automation;13using System.Management.Automation.Help;14using System.Management.Automation.Internal;15using System.Management.Automation.Runspaces;16using System.Runtime.InteropServices;17#if !UNIX18using Microsoft.Win32;19#endif20 21namespace Microsoft.PowerShell.Commands22{23    /// <summary>24    /// This class implements get-help command.25    /// </summary>26    [Cmdlet(VerbsCommon.Get, "Help", DefaultParameterSetName = "AllUsersView", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096483")]27    public sealed class GetHelpCommand : PSCmdlet28    {29        /// <summary>30        /// Help Views.31        /// </summary>32        internal enum HelpView33        {34            Default = 0x00, // Default View35            DetailedView = 0x01,36            FullView = 0x02,37            ExamplesView = 0x0338        }39 40        /// <summary>41        /// Default constructor for the GetHelpCommand class.42        /// </summary>43        public GetHelpCommand()44        {45        }46 47        #region Cmdlet Parameters48 49        /// <summary>50        /// Target to search for help.51        /// </summary>52        [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)]53        [ValidateNotNullOrEmpty()]54        public string Name { get; set; } = string.Empty;55 56        /// <summary>57        /// Path to provider location that user is curious about.58        /// </summary>59        [Parameter]60        public string Path { get; set; }61 62        /// <summary>63        /// List of help categories to search for help.64        /// </summary>65        [Parameter]66        [ValidateSet(67            "Alias", "Cmdlet", "Provider", "General", "FAQ", "Glossary", "HelpFile", "ScriptCommand", "Function", "Filter", "ExternalScript", "All", "DefaultHelp", "DscResource", "Class", "Configuration",68             IgnoreCase = true)]69        public string[] Category { get; set; }70 71        private readonly string _provider = string.Empty;72 73        /// <summary>74        /// Changes the view of HelpObject returned.75        /// </summary>76        /// <remarks>77        /// Currently we support following views:78        ///79        /// 1. Reminder (Default - Experienced User)80        /// 2. Detailed (Beginner - Beginning User)81        /// 3. Full     (All Users)82        /// 4. Examples83        /// 5. Parameters84        ///85        /// Currently we support these views only for Cmdlets.86        /// A SnapIn developer can however change these views.87        /// </remarks>88        [Parameter(ParameterSetName = "DetailedView", Mandatory = true)]89        public SwitchParameter Detailed90        {91            set92            {93                if (value.ToBool())94                {95                    _viewTokenToAdd = HelpView.DetailedView;96                }97            }98        }99 100        /// <summary>101        /// Changes the view of HelpObject returned.102        /// </summary>103        /// <remarks>104        /// Currently we support following views:105        ///106        /// 1. Reminder (Default - Experienced User)107        /// 2. Detailed (Beginner - Beginning User)108        /// 3. Full     (All Users)109        /// 4. Examples110        /// 5. Parameters111        ///112        /// Currently we support these views only for Cmdlets.113        /// A SnapIn developer can however change these views.114        /// </remarks>115        [Parameter(ParameterSetName = "AllUsersView")]116        public SwitchParameter Full117        {118            set119            {120                if (value.ToBool())121                {122                    _viewTokenToAdd = HelpView.FullView;123                }124            }125        }126 127        /// <summary>128        /// Changes the view of HelpObject returned.129        /// </summary>130        /// <remarks>131        /// Currently we support following views:132        ///133        /// 1. Reminder (Default - Experienced User)134        /// 2. Detailed (Beginner - Beginning User)135        /// 3. Full     (All Users)136        /// 4. Examples137        ///138        /// Currently we support these views only for Cmdlets.139        /// A SnapIn developer can however change these views.140        /// </remarks>141        [Parameter(ParameterSetName = "Examples", Mandatory = true)]142        public SwitchParameter Examples143        {144            set145            {146                if (value.ToBool())147                {148                    _viewTokenToAdd = HelpView.ExamplesView;149                }150            }151        }152 153        /// <summary>154        /// Parameter name.155        /// </summary>156        /// <remarks>157        /// Support WildCard strings as supported by WildcardPattern class.158        /// </remarks>159        [Parameter(ParameterSetName = "Parameters", Mandatory = true)]160        public string[] Parameter { get; set; }161 162        /// <summary>163        /// Gets and sets list of Component's to search on.164        /// </summary>165        [Parameter]166        public string[] Component { get; set; }167 168        /// <summary>169        /// Gets and sets list of Functionality's to search on.170        /// </summary>171        [Parameter]172        public string[] Functionality { get; set; }173 174        /// <summary>175        /// Gets and sets list of Role's to search on.176        /// </summary>177        [Parameter]178        public string[] Role { get; set; }179 180        /// <summary>181        /// This parameter,if true, will direct get-help cmdlet to182        /// navigate to a URL (stored in the command MAML file under183        /// the uri node).184        /// </summary>185        [Parameter(ParameterSetName = "Online", Mandatory = true)]186        public SwitchParameter Online187        {188            get189            {190                return _showOnlineHelp;191            }192 193            set194            {195                _showOnlineHelp = value;196                if (_showOnlineHelp)197                {198                    VerifyParameterForbiddenInRemoteRunspace(this, "Online");199                }200            }201        }202 203        private bool _showOnlineHelp;204 205#if !UNIX206        private GraphicalHostReflectionWrapper graphicalHostReflectionWrapper;207        private bool showWindow;208 209        /// <summary>210        /// Gets or sets a value indicating whether the help should be displayed in a separate window.211        /// </summary>212        [Parameter(ParameterSetName = "ShowWindow", Mandatory = true)]213        public SwitchParameter ShowWindow214        {215            get216            {217                return showWindow;218            }219 220            set221            {222                showWindow = value;223                if (showWindow)224                {225                    VerifyParameterForbiddenInRemoteRunspace(this, "ShowWindow");226                }227            }228        }229#endif230 231        // The following variable controls the view.232        private HelpView _viewTokenToAdd = HelpView.Default;233 234#if LEGACYTELEMETRY235        private readonly Stopwatch _timer = new Stopwatch();236        private bool _updatedHelp;237#endif238 239        #endregion240 241        #region Cmdlet API implementation242 243        /// <summary>244        /// Implements the BeginProcessing() method for get-help command.245        /// </summary>246        protected override void BeginProcessing()247        {248#if LEGACYTELEMETRY249            _timer.Start();250#endif251        }252 253        /// <summary>254        /// Implements the ProcessRecord() method for get-help command.255        /// </summary>256        protected override void ProcessRecord()257        {258#if !UNIX259            string fileSystemPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(this.Name);260            string normalizedName = FileSystemProvider.NormalizePath(fileSystemPath);261            // In a restricted session, do not allow help on network paths or device paths, because device paths can be used to bypass the restrictions.262            if (Utils.IsSessionRestricted(this.Context) && (FileSystemProvider.PathIsNetworkPath(normalizedName) || Utils.PathIsDevicePath(normalizedName))) {263                Exception e = new ArgumentException(HelpErrors.NoNetworkCommands, "Name");264                ErrorRecord errorRecord = new ErrorRecord(e, "CommandNameNotAllowed", ErrorCategory.InvalidArgument, null);265                this.ThrowTerminatingError(errorRecord);266            }267#endif268 269            HelpSystem helpSystem = this.Context.HelpSystem;270            try271            {272#if !UNIX273                if (this.ShowWindow)274                {275                    this.graphicalHostReflectionWrapper = GraphicalHostReflectionWrapper.GetGraphicalHostReflectionWrapper(this, "Microsoft.PowerShell.Commands.Internal.HelpWindowHelper");276                }277#endif278                helpSystem.OnProgress += HelpSystem_OnProgress;279 280                bool failed = false;281                HelpCategory helpCategory = ToHelpCategory(Category, ref failed);282 283                if (failed)284                    return;285 286                // Validate input parameters287                ValidateAndThrowIfError(helpCategory);288 289                HelpRequest helpRequest = new HelpRequest(this.Name, helpCategory);290 291                helpRequest.Provider = _provider;292                helpRequest.Component = Component;293                helpRequest.Role = Role;294                helpRequest.Functionality = Functionality;295                helpRequest.ProviderContext = new ProviderContext(296                    this.Path,297                    this.Context.Engine.Context,298                    this.SessionState.Path);299                helpRequest.CommandOrigin = this.MyInvocation.CommandOrigin;300 301                // the idea is to use yield statement in the help lookup to speed up302                // perceived user experience....So HelpSystem.GetHelp returns an303                // IEnumerable..304                IEnumerable<HelpInfo> helpInfos = helpSystem.GetHelp(helpRequest);305                // HelpCommand acts differently when there is just one help object and when306                // there are more than one object...so handling this behavior through307                // some variables.308                HelpInfo firstHelpInfoObject = null;309                int countOfHelpInfos = 0;310                foreach (HelpInfo helpInfo in helpInfos)311                {312                    // honor Ctrl-C from user.313                    if (IsStopping)314                    {315                        return;316                    }317 318                    if (countOfHelpInfos == 0)319                    {320                        firstHelpInfoObject = helpInfo;321                    }322                    else323                    {324                        // write first help object only once.325                        if (firstHelpInfoObject != null)326                        {327                            WriteObjectsOrShowOnlineHelp(firstHelpInfoObject, false);328                            firstHelpInfoObject = null;329                        }330 331                        WriteObjectsOrShowOnlineHelp(helpInfo, false);332                    }333 334                    countOfHelpInfos++;335                }336 337#if LEGACYTELEMETRY338                _timer.Stop();339 340                if (!string.IsNullOrEmpty(Name))341                    Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.ReportGetHelpTelemetry(Name, countOfHelpInfos, _timer.ElapsedMilliseconds, _updatedHelp);342#endif343                // Write full help as there is only one help info object344                if (countOfHelpInfos == 1)345                {346                    WriteObjectsOrShowOnlineHelp(firstHelpInfoObject, true);347                }348                else if (_showOnlineHelp && (countOfHelpInfos > 1))349                {350                    throw PSTraceSource.NewInvalidOperationException(HelpErrors.MultipleOnlineTopicsNotSupported, "Online");351                }352 353                // show errors only if there is no wildcard search or VerboseHelpErrors is true.354                if (((countOfHelpInfos == 0) && (!WildcardPattern.ContainsWildcardCharacters(helpRequest.Target)))355                    || helpSystem.VerboseHelpErrors)356                {357                    // Check if there is any error happened. If yes,358                    // pipe out errors.359                    if (helpSystem.LastErrors.Count > 0)360                    {361                        foreach (ErrorRecord errorRecord in helpSystem.LastErrors)362                        {363                            WriteError(errorRecord);364                        }365                    }366                }367            }368            finally369            {370                helpSystem.OnProgress -= HelpSystem_OnProgress;371                HelpSystem_OnComplete();372 373                // finally clear the ScriptBlockAst -> Token[] cache374                helpSystem.ClearScriptBlockTokenCache();375            }376        }377 378        private HelpCategory ToHelpCategory(string[] category, ref bool failed)379        {380            if (category == null || category.Length == 0)381                return HelpCategory.None;382 383            HelpCategory helpCategory = HelpCategory.None;384 385            failed = false;386 387            for (int i = 0; i < category.Length; i++)388            {389                try390                {391                    HelpCategory temp = (HelpCategory)Enum.Parse(typeof(HelpCategory), category[i], true);392 393                    helpCategory |= temp;394                }395                catch (ArgumentException argumentException)396                {397                    Exception e = new HelpCategoryInvalidException(category[i], argumentException);398                    ErrorRecord errorRecord = new ErrorRecord(e, "InvalidHelpCategory", ErrorCategory.InvalidArgument, null);399                    this.WriteError(errorRecord);400 401                    failed = true;402                }403            }404 405            return helpCategory;406        }407 408        /// <summary>409        /// Change <paramref name="originalHelpObject"/> as per user request.410        ///411        /// This method creates a new type to the existing typenames412        /// depending on Detailed,Full,Example parameters and adds this413        /// new type(s) to the top of the list.414        /// </summary>415        /// <param name="originalHelpObject">Full help object to transform.</param>416        /// <returns>Transformed help object with new TypeNames.</returns>417        /// <remarks>If Detailed and Full are not specified, nothing is changed.</remarks>418        private PSObject TransformView(PSObject originalHelpObject)419        {420            Diagnostics.Assert(originalHelpObject != null,421                "HelpObject should not be null");422 423            if (_viewTokenToAdd == HelpView.Default)424            {425                s_tracer.WriteLine("Detailed, Full, Examples are not selected. Constructing default view.");426                return originalHelpObject;427            }428 429            string tokenToAdd = _viewTokenToAdd.ToString();430            // We are changing the types without modifying the original object.431            // The contract between help command and helpsystem does not432            // allow us to modify returned help objects.433            PSObject objectToReturn = originalHelpObject.Copy();434            objectToReturn.TypeNames.Clear();435 436            if (originalHelpObject.TypeNames.Count == 0)437            {438                string typeToAdd = string.Create(CultureInfo.InvariantCulture, $"HelpInfo#{tokenToAdd}");439                objectToReturn.TypeNames.Add(typeToAdd);440            }441            else442            {443                // User request at the top..444                foreach (string typeName in originalHelpObject.TypeNames)445                {446                    // dont add new types for System.String and System.Object..447                    // as they are handled differently for F&0..(bug935095)448                    if (typeName.ToLowerInvariant().Equals("system.string") ||449                        typeName.ToLowerInvariant().Equals("system.object"))450                    {451                        continue;452                    }453 454                    string typeToAdd = string.Create(CultureInfo.InvariantCulture, $"{typeName}#{tokenToAdd}");455                    s_tracer.WriteLine("Adding type {0}", typeToAdd);456                    objectToReturn.TypeNames.Add(typeToAdd);457                }458 459                // Existing typenames at the bottom..460                foreach (string typeName in originalHelpObject.TypeNames)461                {462                    s_tracer.WriteLine("Adding type {0}", typeName);463                    objectToReturn.TypeNames.Add(typeName);464                }465            }466 467            return objectToReturn;468        }469 470        /// <summary>471        /// Gets the parameter info for patterns identified by Parameter property.472        /// </summary>473        /// <param name="helpInfo">HelpInfo object to look for the parameter.</param>474        /// <returns>Array of parameter infos.</returns>475        private PSObject[] GetParameterInfo(HelpInfo helpInfo)476        {477            List<PSObject> parameterInfosList = new List<PSObject>(Parameter.Length);478 479            foreach (var parameter in Parameter)480            {481                foreach (var parameterInfo in helpInfo.GetParameter(parameter))482                {483                    parameterInfosList.Add(parameterInfo);484                }485            }486 487            return parameterInfosList.ToArray();488        }489 490        /// <summary>491        /// Gets the parameter info for patterns identified by Parameter property.492        /// Writes the parameter info(s) to the output stream. An error is thrown493        /// if a parameter with a given pattern is not found.494        /// </summary>495        /// <param name="helpInfo">HelpInfo Object to look for the parameter.</param>496        private void GetAndWriteParameterInfo(HelpInfo helpInfo)497        {498            s_tracer.WriteLine("Searching parameters for {0}", helpInfo.Name);499 500            PSObject[] pInfos = GetParameterInfo(helpInfo);501 502            if ((pInfos == null) || (pInfos.Length == 0))503            {504                Exception innerException = PSTraceSource.NewArgumentException("Parameter",505                    HelpErrors.NoParmsFound, Parameter);506                WriteError(new ErrorRecord(innerException, "NoParmsFound", ErrorCategory.InvalidArgument, helpInfo));507            }508            else509            {510                foreach (PSObject pInfo in pInfos)511                {512                    WriteObject(pInfo);513                }514            }515        }516 517        /// <summary>518        /// Validates input parameters. 519        /// </summary>520        /// <param name="cat">Category specified by the user.</param>521        /// <exception cref="ArgumentException">522        /// If the request can't be serviced.523        /// </exception>524        private void ValidateAndThrowIfError(HelpCategory cat)525        {526            if (cat == HelpCategory.None)527            {528                return;529            }530 531            // categories that support -Parameter, -Role, -Functionality, -Component parameters532            const HelpCategory supportedCategories =533                HelpCategory.Alias | HelpCategory.Cmdlet | HelpCategory.ExternalScript |534                HelpCategory.Filter | HelpCategory.Function | HelpCategory.ScriptCommand;535 536            if ((cat & supportedCategories) == 0)537            {538                if (Parameter != null)539                {540                    throw PSTraceSource.NewArgumentException("Parameter",541                        HelpErrors.ParamNotSupported, "-Parameter");542                }543 544                if (Component != null)545                {546                    throw PSTraceSource.NewArgumentException("Component",547                        HelpErrors.ParamNotSupported, "-Component");548                }549 550                if (Role != null)551                {552                    throw PSTraceSource.NewArgumentException("Role",553                        HelpErrors.ParamNotSupported, "-Role");554                }555 556                if (Functionality != null)557                {558                    throw PSTraceSource.NewArgumentException("Functionality",559                        HelpErrors.ParamNotSupported, "-Functionality");560                }561            }562        }563 564        /// <summary>565        /// Helper method used to Write the help object onto the output566        /// stream or show online help (URI extracted from the HelpInfo)567        /// object.568        /// </summary>569        private void WriteObjectsOrShowOnlineHelp(HelpInfo helpInfo, bool showFullHelp)570        {571            if (helpInfo != null)572            {573                // online help can be showed only if showFullHelp is true..574                // showFullHelp will be false when the help tries to display multiple help topics..575                // -Online should not work when multiple help topics are displayed.576                if (showFullHelp && _showOnlineHelp)577                {578                    bool onlineUriFound = false;579                    // show online help580                    s_tracer.WriteLine("Preparing to show help online.");581                    Uri onlineUri = helpInfo.GetUriForOnlineHelp();582                    if (onlineUri != null)583                    {584                        onlineUriFound = true;585                        LaunchOnlineHelp(onlineUri);586                        return;587                    }588 589                    if (!onlineUriFound)590                    {591                        throw PSTraceSource.NewInvalidOperationException(HelpErrors.NoURIFound);592                    }593                }594#if !UNIX595                else if (showFullHelp && ShowWindow)596                {597                    graphicalHostReflectionWrapper.CallStaticMethod("ShowHelpWindow", helpInfo.FullHelp, this);598                }599#endif600                else601                {602                    // show inline help603                    if (showFullHelp)604                    {605                        if (Parameter != null)606                        {607                            GetAndWriteParameterInfo(helpInfo);608                        }609                        else610                        {611                            PSObject objectToReturn = TransformView(helpInfo.FullHelp);612                            objectToReturn.IsHelpObject = true;613                            WriteObject(objectToReturn);614                        }615                    }616                    else617                    {618                        if (Parameter != null)619                        {620                            PSObject[] pInfos = GetParameterInfo(helpInfo);621 622                            if ((pInfos == null) || (pInfos.Length == 0))623                            {624                                return;625                            }626                        }627 628                        WriteObject(helpInfo.ShortHelp);629                    }630                }631            }632        }633 634        /// <summary>635        /// Opens the Uri. System's default application will be used636        /// to show the uri.637        /// </summary>638        /// <param name="uriToLaunch"></param>639        private void LaunchOnlineHelp(Uri uriToLaunch)640        {641            Diagnostics.Assert(uriToLaunch != null, "uriToLaunch should not be null");642 643            if (!uriToLaunch.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) &&644                !uriToLaunch.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))645            {646                throw PSTraceSource.NewInvalidOperationException(HelpErrors.ProtocolNotSupported,647                    uriToLaunch.ToString(),648                    "http",649                    "https");650            }651 652            // we use this test hook is to avoid actually calling out to another process653            if (InternalTestHooks.BypassOnlineHelpRetrieval)654            {655                this.WriteObject(string.Format(CultureInfo.InvariantCulture, HelpDisplayStrings.OnlineHelpUri, uriToLaunch.OriginalString));656                return;657            }658 659            Exception exception = null;660            bool wrapCaughtException = true;661            try662            {663                this.WriteVerbose(string.Format(CultureInfo.InvariantCulture, HelpDisplayStrings.OnlineHelpUri, uriToLaunch.OriginalString));664                System.Diagnostics.Process browserProcess = new System.Diagnostics.Process();665 666                if (Platform.IsNanoServer || Platform.IsIoT)667                {668                    // We cannot open the URL in browser on headless SKUs.669                    wrapCaughtException = false;670                    exception = PSTraceSource.NewInvalidOperationException(HelpErrors.CannotLaunchURI, uriToLaunch.OriginalString);671                }672                else673                {674                    browserProcess.StartInfo.FileName = uriToLaunch.OriginalString;675                    browserProcess.StartInfo.UseShellExecute = true;676                    browserProcess.Start();677                }678            }679            catch (InvalidOperationException ioe)680            {681                exception = ioe;682            }683            catch (System.ComponentModel.Win32Exception we)684            {685                exception = we;686            }687 688            if (exception != null)689            {690                if (wrapCaughtException)691                    throw PSTraceSource.NewInvalidOperationException(exception, HelpErrors.CannotLaunchURI, uriToLaunch.OriginalString);692                else693                    throw exception;694            }695        }696 697        #endregion698 699        private void HelpSystem_OnProgress(object sender, HelpProgressEventArgs arg)700        {701            var record = new ProgressRecord(0, this.CommandInfo.Name, arg.Activity)702            {703                PercentComplete = arg.PercentComplete704            };705 706            WriteProgress(record);707        }708 709        private void HelpSystem_OnComplete()710        {711            var record = new ProgressRecord(0, this.CommandInfo.Name, "Completed")712            {713                RecordType = ProgressRecordType.Completed714            };715 716            WriteProgress(record);717        }718 719        #region Helper methods for verification of parameters against NoLanguage mode720 721        internal static void VerifyParameterForbiddenInRemoteRunspace(Cmdlet cmdlet, string parameterName)722        {723            if (NativeCommandProcessor.IsServerSide)724            {725                string message = StringUtil.Format(CommandBaseStrings.ParameterNotValidInRemoteRunspace,726                    cmdlet.MyInvocation.InvocationName,727                    parameterName);728                Exception e = new InvalidOperationException(message);729                ErrorRecord errorRecord = new ErrorRecord(e, "ParameterNotValidInRemoteRunspace", ErrorCategory.InvalidArgument, null);730                cmdlet.ThrowTerminatingError(errorRecord);731            }732        }733 734        #endregion735 736        #region trace737        [TraceSource("GetHelpCommand", "GetHelpCommand")]738        private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("GetHelpCommand", "GetHelpCommand");739        #endregion740    }741 742    /// <summary>743    /// Helper methods used as powershell extension from a types file.744    /// </summary>745    public static class GetHelpCodeMethods746    {747        /// <summary>748        /// Checks whether the default runspace associated with the current thread has the standard Get-Help cmdlet.749        /// </summary>750        /// <returns>True if Get-Help is found, false otherwise.</returns>751        private static bool DoesCurrentRunspaceIncludeCoreHelpCmdlet()752        {753            InitialSessionState iss = Runspace.DefaultRunspace.InitialSessionState;754            if (iss is null)755            {756                return false;757            }758 759            Collection<SessionStateCommandEntry> getHelpEntries = iss.Commands["Get-Help"];760            SessionStateCommandEntry getHelpEntry = null;761            for (int i = 0; i < getHelpEntries.Count; ++i)762            {763                if (getHelpEntries[i].Visibility is not SessionStateEntryVisibility.Public)764                {765                    continue;766                }767 768                // If we have multiple entries for Get-Help,769                // our assumption is that the standard Get-Help is not available.770                if (getHelpEntry is not null)771                {772                    return false;773                }774 775                getHelpEntry = getHelpEntries[i];776            }777 778            return getHelpEntry is SessionStateCmdletEntry getHelpCmdlet779                && getHelpCmdlet.ImplementingType == typeof(GetHelpCommand);780        }781 782        /// <summary>783        /// Retrieves the HelpUri given a CommandInfo instance.784        /// </summary>785        /// <param name="commandInfoPSObject">786        /// CommandInfo instance wrapped as PSObject787        /// </param>788        /// <returns>789        /// null if <paramref name="commandInfoPSObject"/> is not a CommandInfo type.790        /// null if HelpUri could not be retrieved either from CommandMetadata or791        /// help content.792        /// </returns>793        [SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings")]794        public static string GetHelpUri(PSObject commandInfoPSObject)795        {796            if (commandInfoPSObject == null)797            {798                return string.Empty;799            }800 801            CommandInfo cmdInfo = PSObject.Base(commandInfoPSObject) as CommandInfo;802            // GetHelpUri helper method is expected to be used only by System.Management.Automation.CommandInfo803            // objects from types.ps1xml804            if ((cmdInfo == null) || (string.IsNullOrEmpty(cmdInfo.Name)))805            {806                return string.Empty;807            }808 809            // The type checking is needed to avoid a try..catch exception block as810            // the CommandInfo.CommandMetadata throws an InvalidOperationException811            // instead of returning null.812            if ((cmdInfo is CmdletInfo) || (cmdInfo is FunctionInfo) ||813                (cmdInfo is ExternalScriptInfo) || (cmdInfo is ScriptInfo))814            {815                if (!string.IsNullOrEmpty(cmdInfo.CommandMetadata.HelpUri))816                {817                    return cmdInfo.CommandMetadata.HelpUri;818                }819            }820 821            AliasInfo aliasInfo = cmdInfo as AliasInfo;822            if ((aliasInfo != null) &&823                (aliasInfo.ExternalCommandMetadata != null) &&824                (!string.IsNullOrEmpty(aliasInfo.ExternalCommandMetadata.HelpUri)))825            {826                return aliasInfo.ExternalCommandMetadata.HelpUri;827            }828 829            // if everything else fails..depend on Get-Help infrastructure to get us the Uri.830            string cmdName = cmdInfo.Name;831            if (!string.IsNullOrEmpty(cmdInfo.ModuleName))832            {833                cmdName = string.Create(CultureInfo.InvariantCulture, $"{cmdInfo.ModuleName}\\{cmdInfo.Name}");834            }835 836            if (DoesCurrentRunspaceIncludeCoreHelpCmdlet())837            {838                // Win8: 651300 if core get-help is present in the runspace (and it is the only get-help command), use839                // help system directly and avoid perf penalty.840                var currentContext = System.Management.Automation.Runspaces.LocalPipeline.GetExecutionContextFromTLS();841                if ((currentContext != null) && (currentContext.HelpSystem != null))842                {843                    HelpRequest helpRequest = new HelpRequest(cmdName, cmdInfo.HelpCategory);844                    helpRequest.ProviderContext = new ProviderContext(845                        string.Empty,846                        currentContext,847                        currentContext.SessionState.Path);848                    helpRequest.CommandOrigin = CommandOrigin.Runspace;849                    foreach (850                        Uri result in851                            currentContext.HelpSystem.ExactMatchHelp(helpRequest).Select(852                                helpInfo => helpInfo.GetUriForOnlineHelp()).Where(static result => result != null))853                    {854                        return result.OriginalString;855                    }856                }857            }858            else859            {860                // win8: 546025. Using Get-Help as command, instead of calling HelpSystem.ExactMatchHelp861                // for the following reasons:862                // 1. Exchange creates proxies for Get-Command and Get-Help in their scenario863                // 2. This method is primarily used to get uri faster while serializing the CommandInfo objects (from Get-Command)864                // 3. Exchange uses Get-Help proxy to not call Get-Help cmdlet at-all while serializing CommandInfo objects865                // 4. Using HelpSystem directly will not allow Get-Help proxy to do its job.866                var getHelpPS = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)867                    .AddCommand("get-help")868                    .AddParameter("Name", cmdName)869                    .AddParameter("Category", cmdInfo.HelpCategory.ToString());870                try871                {872                    Collection<PSObject> helpInfos = getHelpPS.Invoke();873 874                    if (helpInfos != null)875                    {876                        for (int index = 0; index < helpInfos.Count; index++)877                        {878                            HelpInfo helpInfo;879                            if (LanguagePrimitives.TryConvertTo<HelpInfo>(helpInfos[index], out helpInfo))880                            {881                                Uri result = helpInfo.GetUriForOnlineHelp();882                                if (result != null)883                                {884                                    return result.OriginalString;885                                }886                            }887                            else888                            {889                                Uri result = BaseCommandHelpInfo.GetUriFromCommandPSObject(helpInfos[index]);890                                return (result != null) ? result.OriginalString : string.Empty;891                            }892                        }893                    }894                }895                finally896                {897                    getHelpPS.Dispose();898                }899            }900 901            return string.Empty;902        }903    }904}905