Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
HelpFileHelpProvider.cs418 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.IO;8using System.Linq;9using System.Management.Automation.Internal;10 11namespace System.Management.Automation12{13    /// <summary>14    /// Class HelpFileHelpProvider implement the help provider for help.txt kinds of15    /// help contents.16    ///17    /// Help File help information are stored in '.help.txt' files. These files are18    /// located in the Monad / CustomShell Path as well as in the Application Base19    /// of PSSnapIns.20    /// </summary>21    internal class HelpFileHelpProvider : HelpProviderWithCache22    {23        /// <summary>24        /// Constructor for HelpProvider.25        /// </summary>26        internal HelpFileHelpProvider(HelpSystem helpSystem) : base(helpSystem)27        {28        }29 30        #region Common Properties31 32        /// <summary>33        /// Name of the provider.34        /// </summary>35        /// <value>Name of the provider</value>36        internal override string Name37        {38            get39            {40                return "HelpFile Help Provider";41            }42        }43 44        /// <summary>45        /// Help category of the provider.46        /// </summary>47        /// <value>Help category of the provider</value>48        internal override HelpCategory HelpCategory49        {50            get51            {52                return HelpCategory.HelpFile;53            }54        }55 56        #endregion57 58        #region Help Provider Interface59 60        internal override IEnumerable<HelpInfo> ExactMatchHelp(HelpRequest helpRequest)61        {62            int countHelpInfosFound = 0;63            string helpFileName = helpRequest.Target + ".help.txt";64            Collection<string> filesMatched = MUIFileSearcher.SearchFiles(helpFileName, GetExtendedSearchPaths());65 66            Diagnostics.Assert(filesMatched != null, "Files collection should not be null.");67            var matchedFilesToRemove = FilterToLatestModuleVersion(filesMatched);68 69            foreach (string file in filesMatched)70            {71                if (matchedFilesToRemove.Contains(file))72                    continue;73 74                // Check whether the file is already loaded75                if (!_helpFiles.ContainsKey(file))76                {77                    try78                    {79                        LoadHelpFile(file);80                    }81                    catch (IOException ioException)82                    {83                        ReportHelpFileError(ioException, helpRequest.Target, file);84                    }85                    catch (System.Security.SecurityException securityException)86                    {87                        ReportHelpFileError(securityException, helpRequest.Target, file);88                    }89                }90 91                HelpInfo helpInfo = GetCache(file);92 93                if (helpInfo != null)94                {95                    countHelpInfosFound++;96                    yield return helpInfo;97 98                    if ((countHelpInfosFound >= helpRequest.MaxResults) && (helpRequest.MaxResults > 0))99                        yield break;100                }101            }102        }103 104        private Collection<string> FilterToLatestModuleVersion(Collection<string> filesMatched)105        {106            Collection<string> matchedFilesToRemove = new Collection<string>();107 108            if (filesMatched.Count > 1)109            {110                // Dictionary<<ModuleName,fileName>, <Version, helpFileFullName>>111                Dictionary<Tuple<string, string>, Tuple<string, Version>> modulesAndVersion = new Dictionary<Tuple<string, string>, Tuple<string, Version>>();112                HashSet<string> filesProcessed = new HashSet<string>();113 114                var allPSModulePaths = ModuleIntrinsics.GetModulePath(false, this.HelpSystem.ExecutionContext);115 116                foreach (string fileFullName in filesMatched)117                {118                    // Use the filename as a check if we need to process further.119                    // Single module can have multiple .help.txt files.120                    var fileName = Path.GetFileName(fileFullName);121 122                    foreach (string psModulePath in allPSModulePaths)123                    {124                        Version moduleVersionFromPath = null;125                        string moduleName = null;126                        GetModuleNameAndVersion(psModulePath, fileFullName, out moduleName, out moduleVersionFromPath);127 128                        // Skip modules whose root we cannot determine or which do not have versions.129                        if (moduleVersionFromPath != null && moduleName != null)130                        {131                            Tuple<string, Version> moduleVersion = null;132                            Tuple<string, string> key = new Tuple<string, string>(moduleName, fileName);133                            if (modulesAndVersion.TryGetValue(key, out moduleVersion))134                            {135                                // Consider for further processing only if the help file name is same.136                                if (filesProcessed.Contains(fileName))137                                {138                                    if (moduleVersionFromPath > moduleVersion.Item2)139                                    {140                                        modulesAndVersion[key] = new Tuple<string, Version>(fileFullName, moduleVersionFromPath);141 142                                        // Remove the old file since we found a newer version.143                                        matchedFilesToRemove.Add(moduleVersion.Item1);144                                    }145                                    else146                                    {147                                        // Remove the new file as higher version item is already in dictionary.148                                        matchedFilesToRemove.Add(fileFullName);149                                    }150                                }151                            }152                            else153                            {154                                // Add the module to the dictionary as it was not processes earlier.155                                modulesAndVersion.Add(new Tuple<string, string>(moduleName, fileName),156                                                      new Tuple<string, Version>(fileFullName, moduleVersionFromPath));157                            }158                        }159                    }160 161                    filesProcessed.Add(fileName);162                }163            }164 165            // Deduplicate by filename to compensate for two sources, currentuser scope and allusers scope.166            // This is done after the version check filtering to ensure we do not remove later version files.167            HashSet<string> fileNameHash = new HashSet<string>();168 169            foreach (var file in filesMatched)170            {171                string fileName = Path.GetFileName(file);172 173                if (!fileNameHash.Add(fileName))174                {175                    // If the file need to be removed, add it to matchedFilesToRemove, if not already present.176                    if (!matchedFilesToRemove.Contains(file))177                    {178                        matchedFilesToRemove.Add(file);179                    }180                }181            }182 183            return matchedFilesToRemove;184        }185 186        internal override IEnumerable<HelpInfo> SearchHelp(HelpRequest helpRequest, bool searchOnlyContent)187        {188            string target = helpRequest.Target;189            string pattern = target;190            int countOfHelpInfoObjectsFound = 0;191            // this will be used only when searchOnlyContent == true192            WildcardPattern wildCardPattern = null;193 194            if ((!searchOnlyContent) && (!WildcardPattern.ContainsWildcardCharacters(target)))195            {196                // Search all the about conceptual topics. This pattern197                // makes about topics discoverable without actually198                // using the word "about_" as in "get-help while".199                pattern = "*" + pattern + "*";200            }201 202            if (searchOnlyContent)203            {204                string searchTarget = helpRequest.Target;205                if (!WildcardPattern.ContainsWildcardCharacters(helpRequest.Target))206                {207                    searchTarget = "*" + searchTarget + "*";208                }209 210                wildCardPattern = WildcardPattern.Get(searchTarget, WildcardOptions.Compiled | WildcardOptions.IgnoreCase);211                // search all about_* topics212                pattern = "*";213            }214 215            pattern += ".help.txt";216 217            Collection<string> files = MUIFileSearcher.SearchFiles(pattern, GetExtendedSearchPaths());218 219            var matchedFilesToRemove = FilterToLatestModuleVersion(files);220 221            if (files == null)222                yield break;223 224            foreach (string file in files)225            {226                if (matchedFilesToRemove.Contains(file))227                    continue;228 229                // Check whether the file is already loaded230                if (!_helpFiles.ContainsKey(file))231                {232                    try233                    {234                        LoadHelpFile(file);235                    }236                    catch (IOException ioException)237                    {238                        ReportHelpFileError(ioException, helpRequest.Target, file);239                    }240                    catch (System.Security.SecurityException securityException)241                    {242                        ReportHelpFileError(securityException, helpRequest.Target, file);243                    }244                }245 246                HelpFileHelpInfo helpInfo = GetCache(file) as HelpFileHelpInfo;247 248                if (helpInfo != null)249                {250                    if (searchOnlyContent)251                    {252                        if (!helpInfo.MatchPatternInContent(wildCardPattern))253                        {254                            continue;255                        }256                    }257 258                    countOfHelpInfoObjectsFound++;259                    yield return helpInfo;260 261                    if (countOfHelpInfoObjectsFound >= helpRequest.MaxResults && helpRequest.MaxResults > 0)262                        yield break;263                }264            }265        }266 267        private static void GetModuleNameAndVersion(string psmodulePathRoot, string filePath, out string moduleName, out Version moduleVersion)268        {269            moduleVersion = null;270            moduleName = null;271 272            if (filePath.StartsWith(psmodulePathRoot, StringComparison.OrdinalIgnoreCase))273            {274                var moduleRootSubPath = filePath.Remove(0, psmodulePathRoot.Length);275                var pathParts = moduleRootSubPath.Split(Utils.Separators.Directory, StringSplitOptions.RemoveEmptyEntries);276                moduleName = pathParts[0];277                var potentialVersion = pathParts[1];278                Version result;279                if (Version.TryParse(potentialVersion, out result))280                {281                    moduleVersion = result;282                }283            }284        }285 286        /// <summary>287        /// Load help file based on the file path.288        /// </summary>289        /// <param name="path">File path to load help from.</param>290        /// <returns>Help info object loaded from the file.</returns>291        private HelpInfo LoadHelpFile(string path)292        {293            string fileName = Path.GetFileName(path);294 295            // Bug906435: Get-help for special devices throws an exception296            // There might be situations where path does not end with .help.txt extension297            // The assumption that path ends with .help.txt is broken under special298            // conditions when user uses "get-help" with device names like "prn","com1" etc.299            // First check whether path ends with .help.txt.300 301            // If path does not end with ".help.txt" return.302            if (!path.EndsWith(".help.txt", StringComparison.OrdinalIgnoreCase))303                return null;304 305            string name = fileName.Substring(0, fileName.Length - 9 /* ".help.txt".Length */);306 307            if (string.IsNullOrEmpty(name))308                return null;309 310            HelpInfo helpInfo = GetCache(path);311 312            if (helpInfo != null)313                return helpInfo;314 315            string helpText = null;316 317            using (TextReader tr = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)))318            {319                helpText = tr.ReadToEnd();320            }321            // Add this file into _helpFiles hashtable to prevent it to be loaded again.322            _helpFiles[path] = 0;323            helpInfo = HelpFileHelpInfo.GetHelpInfo(name, helpText, path);324 325            AddCache(path, helpInfo);326 327            return helpInfo;328        }329 330        /// <summary>331        /// Gets the extended search paths for about_topics help. To be able to get about_topics help from unloaded modules,332        /// we will add $pshome and the folders under PS module paths to the collection of paths to search.333        /// </summary>334        /// <returns>A collection of string representing locations.</returns>335        internal Collection<string> GetExtendedSearchPaths()336        {337            Collection<string> searchPaths = GetSearchPaths();338 339            // Add $pshome at the top of the list340            string defaultShellSearchPath = GetDefaultShellSearchPath();341 342            int index = searchPaths.IndexOf(defaultShellSearchPath);343            if (index != 0)344            {345                if (index > 0)346                {347                    searchPaths.RemoveAt(index);348                }349 350                searchPaths.Insert(0, defaultShellSearchPath);351            }352 353            // Add the CurrentUser help path.354            searchPaths.Add(HelpUtils.GetUserHomeHelpSearchPath());355 356            // Add modules that are not loaded. Since using 'get-module -listavailable' is very expensive,357            // we load all the directories (which are not empty) under the module path.358            foreach (string psModulePath in ModuleIntrinsics.GetModulePath(false, this.HelpSystem.ExecutionContext))359            {360                if (Directory.Exists(psModulePath))361                {362                    try363                    {364                        // Get all the directories under the module path365                        // * and SearchOption.AllDirectories gets all the version directories.366                        IEnumerable<string> directories = Directory.EnumerateDirectories(psModulePath, "*", SearchOption.AllDirectories);367 368                        var possibleModuleDirectories = directories.Where(static directory => !ModuleUtils.IsPossibleResourceDirectory(directory));369 370                        foreach (string directory in possibleModuleDirectories)371                        {372                            // Add only directories that are not empty373                            if (Directory.EnumerateFiles(directory).Any())374                            {375                                if (!searchPaths.Contains(directory))376                                {377                                    searchPaths.Add(directory);378                                }379                            }380                        }381                    }382                    // Absorb any exception related to enumerating directories383                    catch (System.ArgumentException) { }384                    catch (System.IO.IOException) { }385                    catch (System.UnauthorizedAccessException) { }386                    catch (System.Security.SecurityException) { }387                }388            }389 390            return searchPaths;391        }392 393        /// <summary>394        /// This will reset the help cache. Normally this corresponds to a395        /// help culture change.396        /// </summary>397        internal override void Reset()398        {399            base.Reset();400 401            _helpFiles.Clear();402        }403 404        #endregion405 406        #region Private Data407 408        /// <summary>409        /// This is a hashtable to track which help files are loaded already.410        ///411        /// This will avoid one help file getting loaded again and again.412        /// </summary>413        private readonly Hashtable _helpFiles = new Hashtable();414 415        #endregion416    }417}418