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