Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
CommandHelpProvider.cs1414 linesDownload Raw Back to help
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics;8using System.Diagnostics.CodeAnalysis;9using System.Globalization;10using System.IO;11using System.Management.Automation.Internal;12using System.Management.Automation.Runspaces;13using System.Xml;14 15using Dbg = System.Management.Automation.Diagnostics;16using System.Management.Automation.Help;17using System.Reflection;18 19namespace System.Management.Automation20{21    /// <summary>22    /// Class CommandHelpProvider implement the help provider for commands.23    /// </summary>24    /// <remarks>25    /// Command Help information are stored in 'help.xml' files. Location of these files26    /// can be found from through the engine execution context.27    /// </remarks>28    internal class CommandHelpProvider : HelpProviderWithCache29    {30        /// <summary>31        /// Constructor for CommandHelpProvider.32        /// </summary>33        internal CommandHelpProvider(HelpSystem helpSystem) : base(helpSystem)34        {35            _context = helpSystem.ExecutionContext;36        }37 38        /// <summary>39        /// </summary>40        static CommandHelpProvider()41        {42            s_engineModuleHelpFileCache.Add("Microsoft.PowerShell.Diagnostics", "Microsoft.PowerShell.Commands.Diagnostics.dll-Help.xml");43            s_engineModuleHelpFileCache.Add("Microsoft.PowerShell.Core", "System.Management.Automation.dll-Help.xml");44            s_engineModuleHelpFileCache.Add("Microsoft.PowerShell.Utility", "Microsoft.PowerShell.Commands.Utility.dll-Help.xml");45            s_engineModuleHelpFileCache.Add("Microsoft.PowerShell.Host", "Microsoft.PowerShell.ConsoleHost.dll-Help.xml");46            s_engineModuleHelpFileCache.Add("Microsoft.PowerShell.Management", "Microsoft.PowerShell.Commands.Management.dll-Help.xml");47            s_engineModuleHelpFileCache.Add("Microsoft.PowerShell.Security", "Microsoft.PowerShell.Security.dll-Help.xml");48            s_engineModuleHelpFileCache.Add("Microsoft.WSMan.Management", "Microsoft.Wsman.Management.dll-Help.xml");49        }50 51        private static readonly Dictionary<string, string> s_engineModuleHelpFileCache = new Dictionary<string, string>();52 53        private readonly ExecutionContext _context;54 55        #region Common Properties56 57        /// <summary>58        /// Name of this provider.59        /// </summary>60        /// <value>Name of this provider</value>61        internal override string Name62        {63            get64            {65                return "Command Help Provider";66            }67        }68 69        /// <summary>70        /// Help category for this provider, which is a constant: HelpCategory.Command.71        /// </summary>72        /// <value>Help category for this provider</value>73        internal override HelpCategory HelpCategory74        {75            get76            {77                return78                    HelpCategory.Alias |79                    HelpCategory.Cmdlet;80            }81        }82 83        #endregion84 85        #region Help Provider Interface86 87        private static void GetModulePaths(CommandInfo commandInfo, out string moduleName, out string moduleDir, out string nestedModulePath)88        {89            Dbg.Assert(commandInfo != null, "Caller should verify that commandInfo != null");90 91            CmdletInfo cmdletInfo = commandInfo as CmdletInfo;92            IScriptCommandInfo scriptCommandInfo = commandInfo as IScriptCommandInfo;93 94            string cmdNameWithoutPrefix = null;95            bool testWithoutPrefix = false;96 97            moduleName = null;98            moduleDir = null;99            nestedModulePath = null;100 101            if (commandInfo.Module != null)102            {103                moduleName = commandInfo.Module.Name;104                moduleDir = commandInfo.Module.ModuleBase;105 106                if (!string.IsNullOrEmpty(commandInfo.Prefix))107                {108                    testWithoutPrefix = true;109                    cmdNameWithoutPrefix = Microsoft.PowerShell.Commands.ModuleCmdletBase.RemovePrefixFromCommandName(commandInfo.Name, commandInfo.Prefix);110                }111 112                if (commandInfo.Module.NestedModules != null)113                {114                    foreach (PSModuleInfo nestedModule in commandInfo.Module.NestedModules)115                    {116                        if (cmdletInfo != null &&117                             (nestedModule.ExportedCmdlets.ContainsKey(commandInfo.Name) ||118                               (testWithoutPrefix && nestedModule.ExportedCmdlets.ContainsKey(cmdNameWithoutPrefix))))119                        {120                            nestedModulePath = nestedModule.Path;121                            break;122                        }123                        else if (scriptCommandInfo != null &&124                                  (nestedModule.ExportedFunctions.ContainsKey(commandInfo.Name) ||125                                    (testWithoutPrefix && nestedModule.ExportedFunctions.ContainsKey(cmdNameWithoutPrefix))))126                        {127                            nestedModulePath = nestedModule.Path;128                            break;129                        }130                    }131                }132            }133        }134 135        private static string GetHelpName(CommandInfo commandInfo)136        {137            Dbg.Assert(commandInfo != null, "Caller should verify that commandInfo != null");138 139            CmdletInfo cmdletInfo = commandInfo as CmdletInfo;140 141            if (cmdletInfo != null)142            {143                return cmdletInfo.FullName;144            }145 146            return commandInfo.Name;147        }148 149        private HelpInfo GetHelpInfoFromHelpFile(CommandInfo commandInfo, string helpFileToFind, Collection<string> searchPaths, bool reportErrors, out string helpFile)150        {151            Dbg.Assert(commandInfo != null, "Caller should verify that commandInfo != null");152            Dbg.Assert(helpFileToFind != null, "Caller should verify that helpFileToFind != null");153 154            CmdletInfo cmdletInfo = commandInfo as CmdletInfo;155            IScriptCommandInfo scriptCommandInfo = commandInfo as IScriptCommandInfo;156 157            HelpInfo result = null;158 159            helpFile = MUIFileSearcher.LocateFile(helpFileToFind, searchPaths);160 161            if (!string.IsNullOrEmpty(helpFile))162            {163                if (!_helpFiles.Contains(helpFile))164                {165                    if (cmdletInfo != null)166                    {167                        LoadHelpFile(helpFile, cmdletInfo.ModuleName, cmdletInfo.Name, reportErrors);168                    }169                    else if (scriptCommandInfo != null)170                    {171                        LoadHelpFile(helpFile, helpFile, commandInfo.Name, reportErrors);172                    }173                }174 175                if (cmdletInfo != null)176                {177                    result = GetFromCommandCacheOrCmdletInfo(cmdletInfo);178                }179                else if (scriptCommandInfo != null)180                {181                    result = GetFromCommandCache(helpFile, commandInfo);182                }183            }184 185            return result;186        }187 188        [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Justification = "TestUri is created only to check for source helpUriFromDotLink errors.")]189        private HelpInfo GetHelpInfo(CommandInfo commandInfo, bool reportErrors, bool searchOnlyContent)190        {191            Dbg.Assert(commandInfo != null, "Caller should verify that commandInfo != null");192 193            HelpInfo result = null; // The help result194            string helpFile = null; // The file that contains the help info195            string helpUri = null;196            string helpUriFromDotLink = null;197 198            CmdletInfo cmdletInfo = commandInfo as CmdletInfo;199            IScriptCommandInfo scriptCommandInfo = commandInfo as IScriptCommandInfo;200            FunctionInfo functionInfo = commandInfo as FunctionInfo;201            bool isCmdlet = cmdletInfo != null;202            bool isScriptCommand = scriptCommandInfo != null;203            bool isFunction = functionInfo != null;204 205            string moduleName = null;206            string moduleDir = null;207            string nestedModulePath = null;208 209            // When InternalTestHooks.BypassOnlineHelpRetrieval is enable, we force get-help to generate a metadata210            // driven object, which includes a helpUri that points to the fwlink defined in the cmdlet code.211            // This means that we are not going to load the help content from the GetFromCommandCache and212            // we are not going to read the help file.213 214            // Only gets help for Cmdlet or script command215            if (!isCmdlet && !isScriptCommand)216                return null;217 218            // Check if the help of the command is already in the cache.219            // If not, try load the file specified by HelpFile property and retrieve help.220            if (isCmdlet && !InternalTestHooks.BypassOnlineHelpRetrieval)221            {222                result = GetFromCommandCache(cmdletInfo.ModuleName, cmdletInfo.Name, cmdletInfo.HelpCategory);223 224                if (result == null)225                {226                    // Try load the help file specified by CmdletInfo.HelpFile property227                    helpFile = FindHelpFile(cmdletInfo);228                    if (!string.IsNullOrEmpty(helpFile) && !_helpFiles.Contains(helpFile))229                    {230                        LoadHelpFile(helpFile, cmdletInfo.ModuleName, cmdletInfo.Name, reportErrors);231                    }232 233                    result = GetFromCommandCacheOrCmdletInfo(cmdletInfo);234                }235            }236            else if (isFunction)237            {238                // Try load the help file specified by FunctionInfo.HelpFile property239                helpFile = functionInfo.HelpFile;240                if (!string.IsNullOrEmpty(helpFile))241                {242                    if (!_helpFiles.Contains(helpFile))243                    {244                        LoadHelpFile(helpFile, helpFile, commandInfo.Name, reportErrors);245                    }246 247                    result = GetFromCommandCache(helpFile, commandInfo);248                }249            }250 251            // For scripts, try to retrieve the help from the file specified by .ExternalHelp directive252            if (result == null && isScriptCommand)253            {254                ScriptBlock sb = null;255                try256                {257                    sb = scriptCommandInfo.ScriptBlock;258                }259                catch (RuntimeException)260                {261                    // parsing errors should not block searching for help262                    return null;263                }264 265                if (sb != null)266                {267                    helpFile = null;268                    // searchOnlyContent == true means get-help is looking into the content, in this case we dont269                    // want to download the content from the remote machine. Reason: In Exchange scenario there270                    // are ~700 proxy commands, downloading help for all the commands and searching in that271                    // content takes a lot of time (in the order of 30 minutes) for their scenarios.272                    result = sb.GetHelpInfo(_context, commandInfo, searchOnlyContent, HelpSystem.ScriptBlockTokenCache,273                        out helpFile, out helpUriFromDotLink);274 275                    if (!string.IsNullOrEmpty(helpUriFromDotLink))276                    {277                        try278                        {279                            Uri testUri = new Uri(helpUriFromDotLink);280                            helpUri = helpUriFromDotLink;281                        }282                        catch (UriFormatException)283                        {284                            // Do not add if helpUriFromDotLink is not a URI285                        }286                    }287 288                    if (result != null)289                    {290                        Uri uri = result.GetUriForOnlineHelp();291 292                        if (uri != null)293                        {294                            helpUri = uri.ToString();295                        }296                    }297 298                    if (!string.IsNullOrEmpty(helpFile) && !InternalTestHooks.BypassOnlineHelpRetrieval)299                    {300                        if (!_helpFiles.Contains(helpFile))301                        {302                            LoadHelpFile(helpFile, helpFile, commandInfo.Name, reportErrors);303                        }304 305                        result = GetFromCommandCache(helpFile, commandInfo) ?? result;306                    }307                }308            }309 310            // If the above fails to get help, try search for a file called <ModuleName>-Help.xml311            // in the appropriate UI culture subfolder of ModuleBase, and retrieve help312            // If still not able to get help, try search for a file called <NestedModuleName>-Help.xml313            // under the ModuleBase and the NestedModule's directory, and retrieve help314            if (result == null && !InternalTestHooks.BypassOnlineHelpRetrieval)315            {316                // Get the name and ModuleBase directory of the command's module317                // and the nested module that implements the command318                GetModulePaths(commandInfo, out moduleName, out moduleDir, out nestedModulePath);319 320                var userHomeHelpPath = HelpUtils.GetUserHomeHelpSearchPath();321 322                Collection<string> searchPaths = new Collection<string>() { userHomeHelpPath };323 324                if (!string.IsNullOrEmpty(moduleDir))325                {326                    searchPaths.Add(moduleDir);327                }328 329                if (!string.IsNullOrEmpty(userHomeHelpPath) && !string.IsNullOrEmpty(moduleName))330                {331                    searchPaths.Add(Path.Combine(userHomeHelpPath, moduleName));332                }333 334                if (!string.IsNullOrEmpty(moduleName) && !string.IsNullOrEmpty(moduleDir))335                {336                    // Search for <ModuleName>-Help.xml under ModuleBase folder337                    string helpFileToFind = moduleName + "-Help.xml";338                    result = GetHelpInfoFromHelpFile(commandInfo, helpFileToFind, searchPaths, reportErrors, out helpFile);339                }340 341                if (result == null && !string.IsNullOrEmpty(nestedModulePath))342                {343                    // Search for <NestedModuleName>-Help.xml under both ModuleBase and NestedModule's directory344                    searchPaths.Add(Path.GetDirectoryName(nestedModulePath));345                    string helpFileToFind = Path.GetFileName(nestedModulePath) + "-Help.xml";346                    result = GetHelpInfoFromHelpFile(commandInfo, helpFileToFind, searchPaths, reportErrors, out helpFile);347                }348            }349 350            // Set the HelpFile property to the file that contains the help content351            if (result != null && !string.IsNullOrEmpty(helpFile))352            {353                if (isCmdlet)354                {355                    cmdletInfo.HelpFile = helpFile;356                }357                else if (isFunction)358                {359                    functionInfo.HelpFile = helpFile;360                }361            }362 363            // If the above fails to get help, construct an HelpInfo object using the syntax and definition of the command364            if (result == null)365            {366                if (commandInfo.CommandType == CommandTypes.ExternalScript ||367                    commandInfo.CommandType == CommandTypes.Script)368                {369                    result = SyntaxHelpInfo.GetHelpInfo(commandInfo.Name, commandInfo.Syntax, commandInfo.HelpCategory);370                }371                else372                {373                    PSObject helpInfo = Help.DefaultCommandHelpObjectBuilder.GetPSObjectFromCmdletInfo(commandInfo);374 375                    helpInfo.TypeNames.Clear();376                    helpInfo.TypeNames.Add(DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp);377                    helpInfo.TypeNames.Add("CmdletHelpInfo");378                    helpInfo.TypeNames.Add("HelpInfo");379 380                    result = new MamlCommandHelpInfo(helpInfo, commandInfo.HelpCategory);381                }382            }383 384            if (result != null)385            {386                if (isScriptCommand && result.GetUriForOnlineHelp() == null)387                {388                    if (!string.IsNullOrEmpty(commandInfo.CommandMetadata.HelpUri))389                    {390                        DefaultCommandHelpObjectBuilder.AddRelatedLinksProperties(result.FullHelp, commandInfo.CommandMetadata.HelpUri);391                    }392                    else if (!string.IsNullOrEmpty(helpUri))393                    {394                        DefaultCommandHelpObjectBuilder.AddRelatedLinksProperties(result.FullHelp, helpUri);395                    }396                }397 398                if (isCmdlet && result.FullHelp.Properties["PSSnapIn"] == null)399                {400                    result.FullHelp.Properties.Add(new PSNoteProperty("PSSnapIn", cmdletInfo.PSSnapIn));401                }402 403                if (result.FullHelp.Properties["ModuleName"] == null)404                {405                    result.FullHelp.Properties.Add(new PSNoteProperty("ModuleName", commandInfo.ModuleName));406                }407            }408 409            return result;410        }411 412        /// <summary>413        /// ExactMatchHelp implementation for this help provider.414        /// </summary>415        /// <remarks>416        /// ExactMatchHelp is overridden instead of DoExactMatchHelp to make sure417        /// all help item retrieval will go through command discovery. Because each418        /// help file can contain multiple help items for different commands. Directly419        /// retrieve help cache can result in a invalid command to contain valid420        /// help item. Forcing each ExactMatchHelp to go through command discovery421        /// will make sure helpInfo for invalid command will not be returned.422        /// </remarks>423        /// <param name="helpRequest">Help request object.</param>424        /// <returns></returns>425        internal override IEnumerable<HelpInfo> ExactMatchHelp(HelpRequest helpRequest)426        {427            int countHelpInfosFound = 0;428            string target = helpRequest.Target;429            // this is for avoiding duplicate result from help output.430            var allHelpNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);431 432            CommandSearcher searcher = GetCommandSearcherForExactMatch(target, _context);433 434            while (searcher.MoveNext())435            {436                CommandInfo current = ((IEnumerator<CommandInfo>)searcher).Current;437 438                if (!SessionState.IsVisible(helpRequest.CommandOrigin, current))439                {440                    // this command is not visible to the user (from CommandOrigin) so441                    // dont show help topic for it.442                    continue;443                }444 445                HelpInfo helpInfo = GetHelpInfo(current, true, false);446                string helpName = GetHelpName(current);447 448                if (helpInfo != null && !string.IsNullOrEmpty(helpName))449                {450                    if (helpInfo.ForwardHelpCategory == helpRequest.HelpCategory &&451                        helpInfo.ForwardTarget.Equals(helpRequest.Target, StringComparison.OrdinalIgnoreCase))452                    {453                        throw new PSInvalidOperationException(HelpErrors.CircularDependencyInHelpForwarding);454                    }455 456                    if (allHelpNames.Contains(helpName))457                        continue;458 459                    if (!Match(helpInfo, helpRequest, current))460                    {461                        continue;462                    }463 464                    countHelpInfosFound++;465                    allHelpNames.Add(helpName);466                    yield return helpInfo;467 468                    if ((countHelpInfosFound >= helpRequest.MaxResults) && (helpRequest.MaxResults > 0))469                        yield break;470                }471            }472        }473 474        private static string GetCmdletAssemblyPath(CmdletInfo cmdletInfo)475        {476            if (cmdletInfo == null)477                return null;478 479            if (cmdletInfo.ImplementingType == null)480                return null;481 482            return Path.GetDirectoryName(cmdletInfo.ImplementingType.Assembly.Location);483        }484 485        /// <summary>486        /// This is a hashtable to track which help files are loaded already.487        ///488        /// This will avoid one help file getting loaded again and again.489        /// (Which should not happen unless some commandlet is pointing490        /// to a help file that actually doesn't contain the help for it).491        /// </summary>492        private readonly Hashtable _helpFiles = new Hashtable();493 494        private string GetHelpFile(string helpFile, CmdletInfo cmdletInfo)495        {496            string helpFileToLoad = helpFile;497 498            // Get the mshsnapinfo object for this cmdlet.499            PSSnapInInfo mshSnapInInfo = cmdletInfo.PSSnapIn;500 501            // Search fallback502            // 1. If cmdletInfo.HelpFile is a full path to an existing file, directly load that file503            // 2. If PSSnapInInfo exists, then always look in the application base of the mshsnapin504            // Otherwise,505            //    Look in the default search path and cmdlet assembly path506            Collection<string> searchPaths = new Collection<string>();507 508            if (!File.Exists(helpFileToLoad))509            {510                helpFileToLoad = Path.GetFileName(helpFileToLoad);511 512                if (mshSnapInInfo != null)513                {514                    Diagnostics.Assert(!string.IsNullOrEmpty(mshSnapInInfo.ApplicationBase),515                        "Application Base is null or empty.");516                    // not minishell case..517                    // we have to search only in the application base for a mshsnapin...518                    // if you create an absolute path for helpfile, then MUIFileSearcher519                    // will look only in that path.520 521                    searchPaths.Add(HelpUtils.GetUserHomeHelpSearchPath());522                    searchPaths.Add(mshSnapInInfo.ApplicationBase);523                }524                else if (cmdletInfo.Module != null && !string.IsNullOrEmpty(cmdletInfo.Module.Path) && !string.IsNullOrEmpty(cmdletInfo.Module.ModuleBase))525                {526                    searchPaths.Add(HelpUtils.GetModuleBaseForUserHelp(cmdletInfo.Module.ModuleBase, cmdletInfo.Module.Name));527                    searchPaths.Add(cmdletInfo.Module.ModuleBase);528                }529                else530                {531                    searchPaths.Add(HelpUtils.GetUserHomeHelpSearchPath());532                    searchPaths.Add(GetDefaultShellSearchPath());533                    searchPaths.Add(GetCmdletAssemblyPath(cmdletInfo));534                }535            }536            else537            {538                helpFileToLoad = Path.GetFullPath(helpFileToLoad);539            }540 541            string location = MUIFileSearcher.LocateFile(helpFileToLoad, searchPaths);542 543            // let caller take care of getting help info in a different way544            // like "get-command -syntax"545            if (string.IsNullOrEmpty(location))546            {547                s_tracer.WriteLine("Unable to load file {0}", helpFileToLoad);548            }549 550            return location;551        }552 553        /// <summary>554        /// Finds a help file associated with the given cmdlet.555        /// </summary>556        /// <param name="cmdletInfo"></param>557        /// <returns></returns>558        private string FindHelpFile(CmdletInfo cmdletInfo)559        {560            if (InternalTestHooks.BypassOnlineHelpRetrieval)561            {562                // By returning null, we force get-help to generate a metadata driven help object,563                // which includes a helpUri that points to the fwlink defined in the cmdlet code.564                return null;565            }566 567            if (cmdletInfo == null)568            {569                throw PSTraceSource.NewArgumentNullException(nameof(cmdletInfo));570            }571 572            // Get the help file name from the cmdlet metadata573            string helpFile = cmdletInfo.HelpFile;574 575            if (string.IsNullOrEmpty(helpFile))576            {577                if (cmdletInfo.Module != null)578                {579                    if (InitialSessionState.IsEngineModule(cmdletInfo.Module.Name))580                    {581                        return System.IO.Path.Combine(cmdletInfo.Module.ModuleBase, CultureInfo.CurrentCulture.Name, s_engineModuleHelpFileCache[cmdletInfo.Module.Name]);582                    }583                }584 585                return helpFile;586            }587 588            // This is the path to the help file.589            string location = null;590 591            if (helpFile.EndsWith(".ni.dll-Help.xml", StringComparison.OrdinalIgnoreCase))592            {593                // For PowerShell on OneCore, we ship Ngen binaries. As a result, the name of the assembly now contains '.ni' on it,594                // e.g., <AssemblyName>.ni.dll as supposed to <AssemblyName>.dll.595 596                // When cmdlet metadata is generated for the 'HelpFile' field, we use the name assembly and we append '-Help.xml' to it.597                // Because of this, if the cmdlet is part of an Ngen assembly, then 'HelpFile' field will be pointing to a help file which does not exist.598                // If this is the case, we remove '.ni' from the help file name and try again.599                // For example:600                // Ngen assembly name: Microsoft.PowerShell.Commands.Management.ni.dll601                // Cmdlet metadata 'HelpFile': Microsoft.PowerShell.Commands.Management.ni.dll-Help.xml602                // Actual help file name: Microsoft.PowerShell.Commands.Management.dll-Help.xml603 604                // Make sure that the assembly name contains more than '.ni.dll'605                string assemblyName = helpFile.Replace(".ni.dll-Help.xml", string.Empty);606 607                if (!string.IsNullOrEmpty(assemblyName))608                {609                    // In the first try, we remove '.ni' from the assembly name and we attempt to find the corresponding help file.610                    string helpFileName = cmdletInfo.HelpFile.Replace(".ni.dll-Help.xml", ".dll-Help.xml");611                    location = GetHelpFile(helpFileName, cmdletInfo);612 613                    if (string.IsNullOrEmpty(location))614                    {615                        // If the help file could not be found, then it is possible that the actual assembly name is something like616                        // <Name>.ni.dll, e.g., MyAssembly.ni.dll, so let's try to find the original help file in the cmdlet metadata.617                        location = GetHelpFile(helpFile, cmdletInfo);618                    }619                }620                else621                {622                    // the assembly name is actually '.ni.dll'.623                    location = GetHelpFile(helpFile, cmdletInfo);624                }625            }626            else627            {628                location = GetHelpFile(helpFile, cmdletInfo);629            }630 631            return location;632        }633 634        private void LoadHelpFile(string helpFile, string helpFileIdentifier, string commandName, bool reportErrors)635        {636            Exception e = null;637            try638            {639                LoadHelpFile(helpFile, helpFileIdentifier);640            }641            catch (IOException ioException)642            {643                e = ioException;644            }645            catch (System.Security.SecurityException securityException)646            {647                e = securityException;648            }649            catch (XmlException xmlException)650            {651                e = xmlException;652            }653            catch (NotSupportedException notSupportedException)654            {655                e = notSupportedException;656            }657            catch (UnauthorizedAccessException unauthorizedAccessException)658            {659                e = unauthorizedAccessException;660            }661            catch (InvalidOperationException invalidOperationException)662            {663                e = invalidOperationException;664            }665 666            if (reportErrors && (e != null))667            {668                ReportHelpFileError(e, commandName, helpFile);669            }670        }671 672        /// <summary>673        /// Load help file for HelpInfo objects. The HelpInfo objects will be674        /// put into help cache.675        /// </summary>676        /// <remarks>677        /// 1. Needs to pay special attention about error handling in this function.678        /// Common errors include: file not found and invalid xml. None of these error679        /// should cause help search to stop.680        /// 2. a helpfile cache is used to avoid same file got loaded again and again.681        /// </remarks>682        private void LoadHelpFile(string helpFile, string helpFileIdentifier)683        {684            XmlDocument doc = InternalDeserializer.LoadUnsafeXmlDocument(685                new FileInfo(helpFile),686                false, /* ignore whitespace, comments, etc. */687                null); /* default maxCharactersInDocument */688 689            // Add this file into _helpFiles hashtable to prevent it to be loaded again.690            _helpFiles[helpFile] = 0;691 692            XmlNode helpItemsNode = null;693 694            if (doc.HasChildNodes)695            {696                for (int i = 0; i < doc.ChildNodes.Count; i++)697                {698                    XmlNode node = doc.ChildNodes[i];699                    if (node.NodeType == XmlNodeType.Element && string.Equals(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase))700                    {701                        helpItemsNode = node;702                        break;703                    }704                }705            }706 707            if (helpItemsNode == null)708            {709                s_tracer.WriteLine("Unable to find 'helpItems' element in file {0}", helpFile);710                return;711            }712 713            bool isMaml = IsMamlHelp(helpFile, helpItemsNode);714 715            using (this.HelpSystem.Trace(helpFile))716            {717                if (helpItemsNode.HasChildNodes)718                {719                    for (int i = 0; i < helpItemsNode.ChildNodes.Count; i++)720                    {721                        XmlNode node = helpItemsNode.ChildNodes[i];722                        if (node.NodeType == XmlNodeType.Element && string.Equals(node.LocalName, "command", StringComparison.OrdinalIgnoreCase))723                        {724                            MamlCommandHelpInfo helpInfo = null;725 726                            if (isMaml)727                            {728                                helpInfo = MamlCommandHelpInfo.Load(node, HelpCategory.Cmdlet);729                            }730 731                            if (helpInfo != null)732                            {733                                this.HelpSystem.TraceErrors(helpInfo.Errors);734                                AddToCommandCache(helpFileIdentifier, helpInfo.Name, helpInfo);735                            }736                        }737 738                        if (node.NodeType == XmlNodeType.Element && string.Equals(node.Name, "UserDefinedData", StringComparison.OrdinalIgnoreCase))739                        {740                            UserDefinedHelpData userDefinedHelpData = UserDefinedHelpData.Load(node);741 742                            ProcessUserDefinedHelpData(helpFileIdentifier, userDefinedHelpData);743                        }744                    }745                }746            }747        }748 749        /// <summary>750        /// Process user defined help data by finding the corresponding helpInfo and inserting751        /// necessary helpdata info to command help.752        /// </summary>753        /// <param name="mshSnapInId">PSSnapIn Name for the current help file.</param>754        /// <param name="userDefinedHelpData"></param>755        private void ProcessUserDefinedHelpData(string mshSnapInId, UserDefinedHelpData userDefinedHelpData)756        {757            if (userDefinedHelpData == null)758                return;759 760            if (string.IsNullOrEmpty(userDefinedHelpData.Name))761                return;762 763            HelpInfo helpInfo = GetFromCommandCache(mshSnapInId, userDefinedHelpData.Name, HelpCategory.Cmdlet);764 765            if (helpInfo == null)766                return;767 768            if (!(helpInfo is MamlCommandHelpInfo commandHelpInfo))769                return;770 771            commandHelpInfo.AddUserDefinedData(userDefinedHelpData);772 773            return;774        }775 776        /// <summary>777        /// Gets the HelpInfo object corresponding to the command.778        /// </summary>779        /// <param name="helpFileIdentifier">Help file identifier (either name of PSSnapIn or simply full path to help file).</param>780        /// <param name="commandName">Name of the command.</param>781        /// <param name="helpCategory"></param>782        /// <returns>HelpInfo object.</returns>783        private HelpInfo GetFromCommandCache(string helpFileIdentifier, string commandName, HelpCategory helpCategory)784        {785            Debug.Assert(!string.IsNullOrEmpty(commandName), "Cmdlet Name should not be null or empty.");786 787            string key = commandName;788            if (!string.IsNullOrEmpty(helpFileIdentifier))789            {790                key = helpFileIdentifier + "\\" + key;791            }792 793            HelpInfo result = GetCache(key);794 795            // Win8: Win8:477680: When Function/Workflow Use External Help, Category Property is "Cmdlet"796            if ((result != null) && (result.HelpCategory != helpCategory))797            {798                MamlCommandHelpInfo original = (MamlCommandHelpInfo)result;799                result = original.Copy(helpCategory);800            }801 802            return result;803        }804 805        /// <summary>806        /// Gets the HelpInfo object corresponding to the CommandInfo.807        /// </summary>808        /// <param name="helpFileIdentifier">Help file identifier (simply full path to help file).</param>809        /// <param name="commandInfo"></param>810        /// <returns>HelpInfo object.</returns>811        private HelpInfo GetFromCommandCache(string helpFileIdentifier, CommandInfo commandInfo)812        {813            Debug.Assert(commandInfo != null, "commandInfo cannot be null");814            HelpInfo result = GetFromCommandCache(helpFileIdentifier, commandInfo.Name, commandInfo.HelpCategory);815            if (result == null)816            {817                // check if the command is prefixed and try retrieving help by removing the prefix818                if ((commandInfo.Module != null) && (!string.IsNullOrEmpty(commandInfo.Prefix)))819                {820                    MamlCommandHelpInfo newMamlHelpInfo = GetFromCommandCacheByRemovingPrefix(helpFileIdentifier, commandInfo);821                    if (newMamlHelpInfo != null)822                    {823                        // caching the changed help content under the prefixed name for faster824                        // retrieval later.825                        AddToCommandCache(helpFileIdentifier, commandInfo.Name, newMamlHelpInfo);826                        return newMamlHelpInfo;827                    }828                }829            }830 831            return result;832        }833 834        /// <summary>835        /// Tries to get the help for the Cmdlet from cache.836        /// </summary>837        /// <param name="cmdletInfo"></param>838        /// <returns>839        /// HelpInfo object representing help for the command.840        /// </returns>841        private HelpInfo GetFromCommandCacheOrCmdletInfo(CmdletInfo cmdletInfo)842        {843            Debug.Assert(cmdletInfo != null, "cmdletInfo cannot be null");844            HelpInfo result = GetFromCommandCache(cmdletInfo.ModuleName, cmdletInfo.Name, cmdletInfo.HelpCategory);845            if (result == null)846            {847                // check if the command is prefixed and try retrieving help by removing the prefix848                if ((cmdletInfo.Module != null) && (!string.IsNullOrEmpty(cmdletInfo.Prefix)))849                {850                    MamlCommandHelpInfo newMamlHelpInfo = GetFromCommandCacheByRemovingPrefix(cmdletInfo.ModuleName, cmdletInfo);851 852                    if (newMamlHelpInfo != null)853                    {854                        // Noun exists only for cmdlets...since prefix will change the Noun, updating855                        // the help content accordingly856                        if (newMamlHelpInfo.FullHelp.Properties["Details"] != null &&857                            newMamlHelpInfo.FullHelp.Properties["Details"].Value != null)858                        {859                            PSObject commandDetails = PSObject.AsPSObject(newMamlHelpInfo.FullHelp.Properties["Details"].Value);860                            if (commandDetails.Properties["Noun"] != null)861                            {862                                commandDetails.Properties.Remove("Noun");863                            }864 865                            commandDetails.Properties.Add(new PSNoteProperty("Noun", cmdletInfo.Noun));866                        }867 868                        // caching the changed help content under the prefixed name for faster869                        // retrieval later.870                        AddToCommandCache(cmdletInfo.ModuleName, cmdletInfo.Name, newMamlHelpInfo);871                        return newMamlHelpInfo;872                    }873                }874            }875 876            return result;877        }878 879        /// <summary>880        /// Used to retrieve helpinfo by removing the prefix from the noun portion of a command name.881        /// Import-Module and Import-PSSession supports changing the name of a command882        /// by supplying a custom prefix. In those cases, the help content is stored by using the883        /// original command name (without prefix) as the key.884        ///885        /// This method retrieves the help content by suppressing the prefix and then making a copy886        /// of the help content + change the name and then returns the copied help content.887        /// </summary>888        /// <param name="helpIdentifier"></param>889        /// <param name="cmdInfo"></param>890        /// <returns>891        /// Copied help content or null if no help content is found.892        /// </returns>893        private MamlCommandHelpInfo GetFromCommandCacheByRemovingPrefix(string helpIdentifier, CommandInfo cmdInfo)894        {895            Dbg.Assert(cmdInfo != null, "cmdInfo cannot be null");896 897            MamlCommandHelpInfo result = null;898            MamlCommandHelpInfo originalHelpInfo = GetFromCommandCache(helpIdentifier,899                        Microsoft.PowerShell.Commands.ModuleCmdletBase.RemovePrefixFromCommandName(cmdInfo.Name, cmdInfo.Prefix),900                        cmdInfo.HelpCategory) as MamlCommandHelpInfo;901 902            if (originalHelpInfo != null)903            {904                result = originalHelpInfo.Copy();905                // command's name can be changed using -Prefix while importing module.To give better user experience for906                // get-help (on par with get-command), it was decided to use the prefixed command name907                // for the help content.908                if (result.FullHelp.Properties["Name"] != null)909                {910                    result.FullHelp.Properties.Remove("Name");911                }912 913                result.FullHelp.Properties.Add(new PSNoteProperty("Name", cmdInfo.Name));914 915                if (result.FullHelp.Properties["Details"] != null &&916                    result.FullHelp.Properties["Details"].Value != null)917                {918                    // Note we are making a copy of the original instance and updating919                    // it..This is to protect the help content of the original object.920                    PSObject commandDetails = PSObject.AsPSObject(921                        result.FullHelp.Properties["Details"].Value).Copy();922 923                    if (commandDetails.Properties["Name"] != null)924                    {925                        commandDetails.Properties.Remove("Name");926                    }927 928                    commandDetails.Properties.Add(new PSNoteProperty("Name", cmdInfo.Name));929 930                    // Note we made the change to a copy..so assigning the copy back to931                    // the help content that is returned to the user.932                    result.FullHelp.Properties["Details"].Value = commandDetails;933                }934            }935 936            return result;937        }938 939        /// <summary>940        /// Prepends mshsnapin id to the cmdlet name and adds the result to help cache.941        /// </summary>942        /// <param name="mshSnapInId">PSSnapIn name that this cmdlet belongs to.</param>943        /// <param name="cmdletName">Name of the cmdlet.</param>944        /// <param name="helpInfo">Help object for the cmdlet.</param>945        private void AddToCommandCache(string mshSnapInId, string cmdletName, MamlCommandHelpInfo helpInfo)946        {947            Debug.Assert(!string.IsNullOrEmpty(cmdletName), "Cmdlet Name should not be null or empty.");948 949            string key = cmdletName;950 951            // Add snapin qualified type name for this command at the top..952            // this will enable customizations of the help object.953            helpInfo.FullHelp.TypeNames.Insert(954                index: 0,955                string.Create(956                    CultureInfo.InvariantCulture,957                    $"MamlCommandHelpInfo#{mshSnapInId}#{cmdletName}"));958 959            if (!string.IsNullOrEmpty(mshSnapInId))960            {961                key = mshSnapInId + "\\" + key;962                // Add snapin name to the typenames of this object963                helpInfo.FullHelp.TypeNames.Insert(964                    index: 1,965                    string.Create(966                        CultureInfo.InvariantCulture,967                        $"MamlCommandHelpInfo#{mshSnapInId}"));968            }969 970            AddCache(key, helpInfo);971        }972 973        /// <summary>974        /// Check whether a HelpItems node indicates that the help content is975        /// authored using maml schema.976        ///977        /// This covers two cases:978        ///     a. If the help file has an extension .maml.979        ///     b. If HelpItems node (which should be the top node of any command help file)980        ///        has an attribute "schema" with value "maml", its content is in maml981        ///        schema.982        /// </summary>983        /// <param name="helpFile"></param>984        /// <param name="helpItemsNode"></param>985        /// <returns></returns>986        internal static bool IsMamlHelp(string helpFile, XmlNode helpItemsNode)987        {988            if (helpFile.EndsWith(".maml", StringComparison.OrdinalIgnoreCase))989                return true;990 991            if (helpItemsNode.Attributes == null)992                return false;993 994            foreach (XmlNode attribute in helpItemsNode.Attributes)995            {996                if (attribute.Name.Equals("schema", StringComparison.OrdinalIgnoreCase)997                    && attribute.Value.Equals("maml", StringComparison.OrdinalIgnoreCase))998                {999                    return true;1000                }1001            }1002 1003            return false;1004        }1005 1006        /// <summary>1007        /// Search help for a specific target.1008        /// </summary>1009        /// <param name="helpRequest">Help request object.</param>1010        /// <param name="searchOnlyContent">1011        /// If true, searches for pattern in the help content of all cmdlets.1012        /// Otherwise, searches for pattern in the cmdlet names.1013        /// </param>1014        /// <returns></returns>1015        internal override IEnumerable<HelpInfo> SearchHelp(HelpRequest helpRequest, bool searchOnlyContent)1016        {1017            string target = helpRequest.Target;1018            Collection<string> patternList = new Collection<string>();1019            // this will be used only when searchOnlyContent == true1020            WildcardPattern wildCardPattern = null;1021            // Decorated Search means that original match target is a target without1022            // wildcard patterns. It come here to because exact match was not found1023            // and search target will be decorated with wildcard character '*' to1024            // search again.1025            bool decoratedSearch = !WildcardPattern.ContainsWildcardCharacters(helpRequest.Target);1026 1027            if (!searchOnlyContent)1028            {1029                if (decoratedSearch)1030                {1031                    if (target.Contains(StringLiterals.CommandVerbNounSeparator))1032                    {1033                        patternList.Add(target + "*");1034                    }1035                    else1036                    {1037                        patternList.Add("*" + target + "*");1038                    }1039                }1040                else1041                {1042                    patternList.Add(target);1043                }1044            }1045            else1046            {1047                // get help for all cmdlets.1048                patternList.Add("*");1049                string searchTarget = helpRequest.Target;1050                if (decoratedSearch)1051                {1052                    searchTarget = "*" + helpRequest.Target + "*";1053                }1054 1055                wildCardPattern = WildcardPattern.Get(searchTarget, WildcardOptions.Compiled | WildcardOptions.IgnoreCase);1056            }1057 1058            int countOfHelpInfoObjectsFound = 0;1059            // this is for avoiding duplicate result from help output.1060            var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);1061            var hiddenCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase);1062            foreach (string pattern in patternList)1063            {1064                CommandSearcher searcher = GetCommandSearcherForSearch(pattern, _context);1065 1066                while (searcher.MoveNext())1067                {1068                    if (_context.CurrentPipelineStopping)1069                    {1070                        yield break;1071                    }1072 1073                    CommandInfo current = ((IEnumerator<CommandInfo>)searcher).Current;1074 1075                    HelpInfo helpInfo = GetHelpInfo(current, !decoratedSearch, searchOnlyContent);1076                    string helpName = GetHelpName(current);1077 1078                    if (helpInfo != null && !string.IsNullOrEmpty(helpName))1079                    {1080                        if (!SessionState.IsVisible(helpRequest.CommandOrigin, current))1081                        {1082                            // this command is not visible to the user (from CommandOrigin) so1083                            // dont show help topic for it.1084                            hiddenCommands.Add(helpName);1085 1086                            continue;1087                        }1088 1089                        if (set.Contains(helpName))1090                            continue;1091 1092                        // filter out the helpInfo object depending on user request1093                        if (!Match(helpInfo, helpRequest, current))1094                        {1095                            continue;1096                        }1097 1098                        // Search content1099                        if (searchOnlyContent && (!helpInfo.MatchPatternInContent(wildCardPattern)))1100                        {1101                            continue;1102                        }1103 1104                        set.Add(helpName);1105                        countOfHelpInfoObjectsFound++;1106                        yield return helpInfo;1107 1108                        if (countOfHelpInfoObjectsFound >= helpRequest.MaxResults && helpRequest.MaxResults > 0)1109                            yield break;1110                    }1111                }1112 1113                if (this.HelpCategory == (HelpCategory.Alias | HelpCategory.Cmdlet))1114                {1115                    foreach (CommandInfo current in ModuleUtils.GetMatchingCommands(pattern, _context, helpRequest.CommandOrigin))1116                    {1117                        if (_context.CurrentPipelineStopping)1118                        {1119                            yield break;1120                        }1121 1122                        if (!SessionState.IsVisible(helpRequest.CommandOrigin, current))1123                        {1124                            // this command is not visible to the user (from CommandOrigin) so1125                            // dont show help topic for it.1126                            continue;1127                        }1128 1129                        HelpInfo helpInfo = GetHelpInfo(current, !decoratedSearch, searchOnlyContent);1130                        string helpName = GetHelpName(current);1131 1132                        if (helpInfo != null && !string.IsNullOrEmpty(helpName))1133                        {1134                            if (set.Contains(helpName))1135                                continue;1136 1137                            if (hiddenCommands.Contains(helpName))1138                                continue;1139 1140                            // filter out the helpInfo object depending on user request1141                            if (!Match(helpInfo, helpRequest, current))1142                            {1143                                continue;1144                            }1145 1146                            // Search content1147                            if (searchOnlyContent && (!helpInfo.MatchPatternInContent(wildCardPattern)))1148                            {1149                                continue;1150                            }1151 1152                            set.Add(helpName);1153                            countOfHelpInfoObjectsFound++;1154                            yield return helpInfo;1155 1156                            if (countOfHelpInfoObjectsFound >= helpRequest.MaxResults && helpRequest.MaxResults > 0)1157                                yield break;1158                        }1159                    }1160                }1161            }1162        }1163 1164        /// <summary>1165        /// Check if a helpInfo object matches the component/role/functionality1166        /// criteria from helpRequest.1167        /// </summary>1168        /// <param name="helpInfo"></param>1169        /// <param name="helpRequest"></param>1170        /// <param name="commandInfo"></param>1171        /// <returns></returns>1172        private static bool Match(HelpInfo helpInfo, HelpRequest helpRequest, CommandInfo commandInfo)1173        {1174            if (helpRequest == null)1175                return true;1176 1177            if ((helpRequest.HelpCategory & commandInfo.HelpCategory) == 0)1178            {1179                return false;1180            }1181 1182            if (helpInfo is not BaseCommandHelpInfo)1183                return false;1184 1185            if (!Match(helpInfo.Component, helpRequest.Component))1186            {1187                return false;1188            }1189 1190            if (!Match(helpInfo.Role, helpRequest.Role))1191            {1192                return false;1193            }1194 1195            if (!Match(helpInfo.Functionality, helpRequest.Functionality))1196            {1197                return false;1198            }1199 1200            return true;

Showing the first 1,200 of 1414 lines. Download the file for the rest.