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.Diagnostics;8using System.IO;9using System.Xml;10 11using Dbg = System.Management.Automation.Diagnostics;12 13namespace System.Management.Automation14{15 internal class PSClassHelpProvider : HelpProviderWithCache16 {17 /// <summary>18 /// Constructor for PSClassHelpProvider.19 /// </summary>20 internal PSClassHelpProvider(HelpSystem helpSystem)21 : base(helpSystem)22 {23 _context = helpSystem.ExecutionContext;24 }25 26 /// <summary>27 /// Execution context of the HelpSystem.28 /// </summary>29 private readonly ExecutionContext _context;30 31 /// <summary>32 /// This is a hashtable to track which help files are loaded already.33 ///34 /// This will avoid one help file getting loaded again and again.35 /// </summary>36 private readonly Hashtable _helpFiles = new Hashtable();37 38 [TraceSource("PSClassHelpProvider", "PSClassHelpProvider")]39 private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("PSClassHelpProvider", "PSClassHelpProvider");40 41 #region common properties42 43 /// <summary>44 /// Name of the Help Provider.45 /// </summary>46 internal override string Name47 {48 get { return "Powershell Class Help Provider"; }49 }50 51 /// <summary>52 /// Supported Help Categories.53 /// </summary>54 internal override HelpCategory HelpCategory55 {56 get { return Automation.HelpCategory.Class; }57 }58 59 #endregion60 61 /// <summary>62 /// Override SearchHelp to find a class module with help matching a pattern.63 /// </summary>64 /// <param name="helpRequest">Help request.</param>65 /// <param name="searchOnlyContent">Not used.</param>66 /// <returns></returns>67 internal override IEnumerable<HelpInfo> SearchHelp(HelpRequest helpRequest, bool searchOnlyContent)68 {69 Debug.Assert(helpRequest != null, "helpRequest cannot be null.");70 71 string target = helpRequest.Target;72 Collection<string> patternList = new Collection<string>();73 74 bool decoratedSearch = !WildcardPattern.ContainsWildcardCharacters(helpRequest.Target);75 76 if (decoratedSearch)77 {78 patternList.Add("*" + target + "*");79 }80 else81 patternList.Add(target);82 83 foreach (string pattern in patternList)84 {85 PSClassSearcher searcher = new PSClassSearcher(pattern, useWildCards: true, _context);86 87 foreach (var helpInfo in GetHelpInfo(searcher))88 {89 if (helpInfo != null)90 yield return helpInfo;91 }92 }93 }94 95 /// <summary>96 /// Override ExactMatchHelp to find the matching class module matching help request.97 /// </summary>98 /// <param name="helpRequest">Help Request for the search.</param>99 /// <returns>Enumerable of HelpInfo objects.</returns>100 internal override IEnumerable<HelpInfo> ExactMatchHelp(HelpRequest helpRequest)101 {102 Debug.Assert(helpRequest != null, "helpRequest cannot be null.");103 104 if ((helpRequest.HelpCategory & Automation.HelpCategory.Class) == 0)105 {106 yield return null;107 }108 109 PSClassSearcher searcher = new PSClassSearcher(helpRequest.Target, useWildCards: false, _context);110 111 foreach (var helpInfo in GetHelpInfo(searcher))112 {113 if (helpInfo != null)114 {115 yield return helpInfo;116 }117 }118 }119 120 /// <summary>121 /// Get the help in for the PS Class Info. ///122 /// </summary>123 /// <param name="searcher">Searcher for PS Classes.</param>124 /// <returns>Next HelpInfo object.</returns>125 private IEnumerable<HelpInfo> GetHelpInfo(PSClassSearcher searcher)126 {127 while (searcher.MoveNext())128 {129 PSClassInfo current = ((IEnumerator<PSClassInfo>)searcher).Current;130 131 string moduleName = current.Module.Name;132 string moduleDir = current.Module.ModuleBase;133 134 if (!string.IsNullOrEmpty(moduleName) && !string.IsNullOrEmpty(moduleDir))135 {136 string helpFileToFind = moduleName + "-Help.xml";137 138 string helpFileName = null;139 140 Collection<string> searchPaths = new Collection<string>();141 searchPaths.Add(moduleDir);142 143 string externalHelpFile = current.HelpFile;144 145 if (!string.IsNullOrEmpty(externalHelpFile))146 {147 FileInfo helpFileInfo = new FileInfo(externalHelpFile);148 DirectoryInfo dirToSearch = helpFileInfo.Directory;149 150 if (dirToSearch.Exists)151 {152 searchPaths.Add(dirToSearch.FullName);153 helpFileToFind = helpFileInfo.Name; // If external help file is specified. Then use it.154 }155 }156 157 HelpInfo helpInfo = GetHelpInfoFromHelpFile(current, helpFileToFind, searchPaths, true, out helpFileName);158 159 if (helpInfo != null)160 {161 yield return helpInfo;162 }163 }164 }165 }166 167 /// <summary>168 /// Check whether a HelpItems node indicates that the help content is169 /// authored using maml schema.170 ///171 /// This covers two cases:172 /// a. If the help file has an extension .maml.173 /// b. If HelpItems node (which should be the top node of any command help file)174 /// has an attribute "schema" with value "maml", its content is in maml175 /// schema.176 /// </summary>177 /// <param name="helpFile">File name.</param>178 /// <param name="helpItemsNode">Nodes to check.</param>179 /// <returns></returns>180 internal static bool IsMamlHelp(string helpFile, XmlNode helpItemsNode)181 {182 Debug.Assert(!string.IsNullOrEmpty(helpFile), "helpFile cannot be null.");183 184 if (helpFile.EndsWith(".maml", StringComparison.OrdinalIgnoreCase))185 return true;186 187 if (helpItemsNode.Attributes == null)188 return false;189 190 foreach (XmlNode attribute in helpItemsNode.Attributes)191 {192 if (attribute.Name.Equals("schema", StringComparison.OrdinalIgnoreCase)193 && attribute.Value.Equals("maml", StringComparison.OrdinalIgnoreCase))194 {195 return true;196 }197 }198 199 return false;200 }201 202 #region private methods203 204 private HelpInfo GetHelpInfoFromHelpFile(PSClassInfo classInfo, string helpFileToFind, Collection<string> searchPaths, bool reportErrors, out string helpFile)205 {206 Dbg.Assert(classInfo != null, "Caller should verify that classInfo != null");207 Dbg.Assert(helpFileToFind != null, "Caller should verify that helpFileToFind != null");208 209 helpFile = MUIFileSearcher.LocateFile(helpFileToFind, searchPaths);210 211 if (!File.Exists(helpFile))212 return null;213 214 if (!string.IsNullOrEmpty(helpFile))215 {216 // Load the help file only once. Then use it from the cache.217 if (!_helpFiles.Contains(helpFile))218 {219 LoadHelpFile(helpFile, helpFile, classInfo.Name, reportErrors);220 }221 222 return GetFromPSClassHelpCache(helpFile, Automation.HelpCategory.Class);223 }224 225 return null;226 }227 228 /// <summary>229 /// Gets the HelpInfo object corresponding to the command.230 /// </summary>231 /// <param name="helpFileIdentifier">Help file identifier (either name of PSSnapIn or simply full path to help file).</param>232 /// <param name="helpCategory">Help Category for search.</param>233 /// <returns>HelpInfo object.</returns>234 private HelpInfo GetFromPSClassHelpCache(string helpFileIdentifier, HelpCategory helpCategory)235 {236 Debug.Assert(!string.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier should not be null or empty.");237 238 HelpInfo result = GetCache(helpFileIdentifier);239 240 if (result != null)241 {242 MamlClassHelpInfo original = (MamlClassHelpInfo)result;243 result = original.Copy(helpCategory);244 }245 246 return result;247 }248 249 private void LoadHelpFile(string helpFile, string helpFileIdentifier, string commandName, bool reportErrors)250 {251 Exception e = null;252 try253 {254 LoadHelpFile(helpFile, helpFileIdentifier);255 }256 catch (IOException ioException)257 {258 e = ioException;259 }260 catch (System.Security.SecurityException securityException)261 {262 e = securityException;263 }264 catch (XmlException xmlException)265 {266 e = xmlException;267 }268 catch (NotSupportedException notSupportedException)269 {270 e = notSupportedException;271 }272 catch (UnauthorizedAccessException unauthorizedAccessException)273 {274 e = unauthorizedAccessException;275 }276 catch (InvalidOperationException invalidOperationException)277 {278 e = invalidOperationException;279 }280 281 if (e != null)282 s_tracer.WriteLine("Error occurred in PSClassHelpProvider {0}", e.Message);283 284 if (reportErrors && (e != null))285 {286 ReportHelpFileError(e, commandName, helpFile);287 }288 }289 290 /// <summary>291 /// Load help file for HelpInfo objects. The HelpInfo objects will be292 /// put into help cache.293 /// </summary>294 /// <remarks>295 /// 1. Needs to pay special attention about error handling in this function.296 /// Common errors include: file not found and invalid xml. None of these error297 /// should cause help search to stop.298 /// 2. a helpfile cache is used to avoid same file got loaded again and again.299 /// </remarks>300 private void LoadHelpFile(string helpFile, string helpFileIdentifier)301 {302 Dbg.Assert(!string.IsNullOrEmpty(helpFile), "HelpFile cannot be null or empty.");303 Dbg.Assert(!string.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier cannot be null or empty.");304 305 XmlDocument doc = InternalDeserializer.LoadUnsafeXmlDocument(306 new FileInfo(helpFile),307 false, /* ignore whitespace, comments, etc. */308 null); /* default maxCharactersInDocument */309 310 // Add this file into _helpFiles hashtable to prevent it to be loaded again.311 _helpFiles[helpFile] = 0;312 313 XmlNode helpItemsNode = null;314 315 if (doc.HasChildNodes)316 {317 for (int i = 0; i < doc.ChildNodes.Count; i++)318 {319 XmlNode node = doc.ChildNodes[i];320 if (node.NodeType == XmlNodeType.Element && string.Equals(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase))321 {322 helpItemsNode = node;323 break;324 }325 }326 }327 328 if (helpItemsNode == null)329 {330 s_tracer.WriteLine("Unable to find 'helpItems' element in file {0}", helpFile);331 return;332 }333 334 bool isMaml = IsMamlHelp(helpFile, helpItemsNode);335 336 using (this.HelpSystem.Trace(helpFile))337 {338 if (helpItemsNode.HasChildNodes)339 {340 for (int i = 0; i < helpItemsNode.ChildNodes.Count; i++)341 {342 XmlNode node = helpItemsNode.ChildNodes[i];343 344 string nodeLocalName = node.LocalName;345 346 bool isClass = (string.Equals(nodeLocalName, "class", StringComparison.OrdinalIgnoreCase));347 348 if (node.NodeType == XmlNodeType.Element && isClass)349 {350 MamlClassHelpInfo helpInfo = null;351 352 if (isMaml)353 {354 if (isClass)355 helpInfo = MamlClassHelpInfo.Load(node, HelpCategory.Class);356 }357 358 if (helpInfo != null)359 {360 this.HelpSystem.TraceErrors(helpInfo.Errors);361 AddCache(helpFileIdentifier, helpInfo);362 }363 }364 }365 }366 }367 }368 369 #endregion370 }371}372 