MegaBites-AI/Windows-powershell
0372
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 