PageRenderTime 82ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/src/System.Management.Automation/help/DscResourceHelpProvider.cs

https://gitlab.com/unofficial-mirrors/PowerShell
C# | 372 lines | 241 code | 61 blank | 70 comment | 53 complexity | 352c8d5db4e4d63386f5835afadbca23 MD5 | raw file
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License.
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Collections.ObjectModel;
  6. using System.Diagnostics;
  7. using System.IO;
  8. using System.Xml;
  9. using Dbg = System.Management.Automation.Diagnostics;
  10. namespace System.Management.Automation
  11. {
  12. internal class DscResourceHelpProvider : HelpProviderWithCache
  13. {
  14. /// <summary>
  15. /// Constructor for DscResourceHelpProvider
  16. /// </summary>
  17. internal DscResourceHelpProvider(HelpSystem helpSystem)
  18. : base(helpSystem)
  19. {
  20. _context = helpSystem.ExecutionContext;
  21. }
  22. /// <summary>
  23. /// Execution context of the HelpSystem
  24. /// </summary>
  25. private readonly ExecutionContext _context;
  26. /// <summary>
  27. /// This is a hashtable to track which help files are loaded already.
  28. ///
  29. /// This will avoid one help file getting loaded again and again.
  30. ///
  31. /// </summary>
  32. private readonly Hashtable _helpFiles = new Hashtable();
  33. [TraceSource("DscResourceHelpProvider", "DscResourceHelpProvider")]
  34. private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("DscResourceHelpProvider", "DscResourceHelpProvider");
  35. #region common properties
  36. /// <summary>
  37. /// Name of the Help Provider.
  38. /// </summary>
  39. internal override string Name
  40. {
  41. get { return "Dsc Resource Help Provider"; }
  42. }
  43. /// <summary>
  44. /// Supported Help Categories
  45. /// </summary>
  46. internal override HelpCategory HelpCategory
  47. {
  48. get { return Automation.HelpCategory.DscResource; }
  49. }
  50. #endregion
  51. /// <summary>
  52. /// Override SearchHelp to find a dsc resource help matching a pattern.
  53. /// </summary>
  54. /// <param name="helpRequest">Help request.</param>
  55. /// <param name="searchOnlyContent">Not used.</param>
  56. /// <returns></returns>
  57. internal override IEnumerable<HelpInfo> SearchHelp(HelpRequest helpRequest, bool searchOnlyContent)
  58. {
  59. Debug.Assert(helpRequest != null, "helpRequest cannot be null.");
  60. string target = helpRequest.Target;
  61. Collection<string> patternList = new Collection<string>();
  62. bool decoratedSearch = !WildcardPattern.ContainsWildcardCharacters(helpRequest.Target);
  63. if (decoratedSearch)
  64. {
  65. patternList.Add("*" + target + "*");
  66. }
  67. else
  68. patternList.Add(target);
  69. foreach (string pattern in patternList)
  70. {
  71. DscResourceSearcher searcher = new DscResourceSearcher(pattern, _context);
  72. foreach (var helpInfo in GetHelpInfo(searcher))
  73. {
  74. if (helpInfo != null)
  75. yield return helpInfo;
  76. }
  77. }
  78. }
  79. /// <summary>
  80. /// Override ExactMatchHelp to find the matching DscResource matching help request.
  81. /// </summary>
  82. /// <param name="helpRequest">Help Request for the search.</param>
  83. /// <returns>Enumerable of HelpInfo objects.</returns>
  84. internal override IEnumerable<HelpInfo> ExactMatchHelp(HelpRequest helpRequest)
  85. {
  86. Debug.Assert(helpRequest != null, "helpRequest cannot be null.");
  87. if ((helpRequest.HelpCategory & Automation.HelpCategory.DscResource) == 0)
  88. {
  89. yield return null;
  90. }
  91. string target = helpRequest.Target;
  92. DscResourceSearcher searcher = new DscResourceSearcher(target, _context);
  93. foreach (var helpInfo in GetHelpInfo(searcher))
  94. {
  95. if (helpInfo != null)
  96. {
  97. yield return helpInfo;
  98. }
  99. }
  100. }
  101. /// <summary>
  102. /// Get the help in for the DscResource Info. ///
  103. /// </summary>
  104. /// <param name="searcher">Searcher for DscResources.</param>
  105. /// <returns>Next HelpInfo object.</returns>
  106. private IEnumerable<HelpInfo> GetHelpInfo(DscResourceSearcher searcher)
  107. {
  108. while (searcher.MoveNext())
  109. {
  110. DscResourceInfo current = ((IEnumerator<DscResourceInfo>)searcher).Current;
  111. string moduleName = null;
  112. string moduleDir = current.ParentPath;
  113. // for binary modules, current.Module is empty.
  114. // in such cases use the leaf folder of ParentPath as filename.
  115. if (current.Module != null)
  116. {
  117. moduleName = current.Module.Name;
  118. }
  119. else if (!String.IsNullOrEmpty(moduleDir))
  120. {
  121. string[] splitPath = moduleDir.Split(Utils.Separators.Backslash);
  122. moduleName = splitPath[splitPath.Length - 1];
  123. }
  124. if (!String.IsNullOrEmpty(moduleName) && !String.IsNullOrEmpty(moduleDir))
  125. {
  126. string helpFileToFind = moduleName + "-Help.xml";
  127. string helpFileName = null;
  128. Collection<string> searchPaths = new Collection<string>();
  129. searchPaths.Add(moduleDir);
  130. HelpInfo helpInfo = GetHelpInfoFromHelpFile(current, helpFileToFind, searchPaths, true, out helpFileName);
  131. if (helpInfo != null)
  132. {
  133. yield return helpInfo;
  134. }
  135. }
  136. }
  137. }
  138. /// <summary>
  139. /// Check whether a HelpItems node indicates that the help content is
  140. /// authored using maml schema.
  141. ///
  142. /// This covers two cases:
  143. /// a. If the help file has an extension .maml.
  144. /// b. If HelpItems node (which should be the top node of any command help file)
  145. /// has an attribute "schema" with value "maml", its content is in maml
  146. /// schema
  147. ///
  148. /// </summary>
  149. /// <param name="helpFile">File name.</param>
  150. /// <param name="helpItemsNode">Nodes to check.</param>
  151. /// <returns></returns>
  152. internal static bool IsMamlHelp(string helpFile, XmlNode helpItemsNode)
  153. {
  154. Debug.Assert(!String.IsNullOrEmpty(helpFile), "helpFile cannot be null.");
  155. if (helpFile.EndsWith(".maml", StringComparison.CurrentCultureIgnoreCase))
  156. return true;
  157. if (helpItemsNode.Attributes == null)
  158. return false;
  159. foreach (XmlNode attribute in helpItemsNode.Attributes)
  160. {
  161. if (attribute.Name.Equals("schema", StringComparison.OrdinalIgnoreCase)
  162. && attribute.Value.Equals("maml", StringComparison.OrdinalIgnoreCase))
  163. {
  164. return true;
  165. }
  166. }
  167. return false;
  168. }
  169. #region private methods
  170. private HelpInfo GetHelpInfoFromHelpFile(DscResourceInfo resourceInfo, string helpFileToFind, Collection<string> searchPaths, bool reportErrors, out string helpFile)
  171. {
  172. Dbg.Assert(resourceInfo != null, "Caller should verify that resourceInfo != null");
  173. Dbg.Assert(helpFileToFind != null, "Caller should verify that helpFileToFind != null");
  174. helpFile = MUIFileSearcher.LocateFile(helpFileToFind, searchPaths);
  175. if (!File.Exists(helpFile))
  176. return null;
  177. if (!String.IsNullOrEmpty(helpFile))
  178. {
  179. //Load the help file only once. Then use it from the cache.
  180. if (!_helpFiles.Contains(helpFile))
  181. {
  182. LoadHelpFile(helpFile, helpFile, resourceInfo.Name, reportErrors);
  183. }
  184. return GetFromResourceHelpCache(helpFile, Automation.HelpCategory.DscResource);
  185. }
  186. return null;
  187. }
  188. /// <summary>
  189. /// Gets the HelpInfo object corresponding to the command.
  190. /// </summary>
  191. /// <param name="helpFileIdentifier">help file identifier (either name of PSSnapIn or simply full path to help file)</param>
  192. /// <param name="helpCategory">Help Category for search.</param>
  193. /// <returns>HelpInfo object.</returns>
  194. private HelpInfo GetFromResourceHelpCache(string helpFileIdentifier, HelpCategory helpCategory)
  195. {
  196. Debug.Assert(!string.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier should not be null or empty.");
  197. HelpInfo result = GetCache(helpFileIdentifier);
  198. if (result != null)
  199. {
  200. MamlCommandHelpInfo original = (MamlCommandHelpInfo)result;
  201. result = original.Copy(helpCategory);
  202. }
  203. return result;
  204. }
  205. private void LoadHelpFile(string helpFile, string helpFileIdentifier, string commandName, bool reportErrors)
  206. {
  207. Exception e = null;
  208. try
  209. {
  210. LoadHelpFile(helpFile, helpFileIdentifier);
  211. }
  212. catch (IOException ioException)
  213. {
  214. e = ioException;
  215. }
  216. catch (System.Security.SecurityException securityException)
  217. {
  218. e = securityException;
  219. }
  220. catch (XmlException xmlException)
  221. {
  222. e = xmlException;
  223. }
  224. catch (NotSupportedException notSupportedException)
  225. {
  226. e = notSupportedException;
  227. }
  228. catch (UnauthorizedAccessException unauthorizedAccessException)
  229. {
  230. e = unauthorizedAccessException;
  231. }
  232. catch (InvalidOperationException invalidOperationException)
  233. {
  234. e = invalidOperationException;
  235. }
  236. if (e != null)
  237. s_tracer.WriteLine("Error occured in DscResourceHelpProvider {0}", e.Message);
  238. if (reportErrors && (e != null))
  239. {
  240. ReportHelpFileError(e, commandName, helpFile);
  241. }
  242. }
  243. /// <summary>
  244. /// Load help file for HelpInfo objects. The HelpInfo objects will be
  245. /// put into help cache.
  246. /// </summary>
  247. /// <remarks>
  248. /// 1. Needs to pay special attention about error handling in this function.
  249. /// Common errors include: file not found and invalid xml. None of these error
  250. /// should cause help search to stop.
  251. /// 2. a helpfile cache is used to avoid same file got loaded again and again.
  252. /// </remarks>
  253. private void LoadHelpFile(string helpFile, string helpFileIdentifier)
  254. {
  255. Dbg.Assert(!String.IsNullOrEmpty(helpFile), "HelpFile cannot be null or empty.");
  256. Dbg.Assert(!String.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier cannot be null or empty.");
  257. XmlDocument doc = InternalDeserializer.LoadUnsafeXmlDocument(
  258. new FileInfo(helpFile),
  259. false, /* ignore whitespace, comments, etc. */
  260. null); /* default maxCharactersInDocument */
  261. // Add this file into _helpFiles hashtable to prevent it to be loaded again.
  262. _helpFiles[helpFile] = 0;
  263. XmlNode helpItemsNode = null;
  264. if (doc.HasChildNodes)
  265. {
  266. for (int i = 0; i < doc.ChildNodes.Count; i++)
  267. {
  268. XmlNode node = doc.ChildNodes[i];
  269. if (node.NodeType == XmlNodeType.Element && String.Compare(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase) == 0)
  270. {
  271. helpItemsNode = node;
  272. break;
  273. }
  274. }
  275. }
  276. if (helpItemsNode == null)
  277. {
  278. s_tracer.WriteLine("Unable to find 'helpItems' element in file {0}", helpFile);
  279. return;
  280. }
  281. bool isMaml = IsMamlHelp(helpFile, helpItemsNode);
  282. using (this.HelpSystem.Trace(helpFile))
  283. {
  284. if (helpItemsNode.HasChildNodes)
  285. {
  286. for (int i = 0; i < helpItemsNode.ChildNodes.Count; i++)
  287. {
  288. XmlNode node = helpItemsNode.ChildNodes[i];
  289. string nodeLocalName = node.LocalName;
  290. bool isDscResource = (String.Compare(nodeLocalName, "dscResource", StringComparison.OrdinalIgnoreCase) == 0);
  291. if (node.NodeType == XmlNodeType.Element && isDscResource)
  292. {
  293. MamlCommandHelpInfo helpInfo = null;
  294. if (isMaml)
  295. {
  296. if (isDscResource)
  297. helpInfo = MamlCommandHelpInfo.Load(node, HelpCategory.DscResource);
  298. }
  299. if (helpInfo != null)
  300. {
  301. this.HelpSystem.TraceErrors(helpInfo.Errors);
  302. AddCache(helpFileIdentifier, helpInfo);
  303. }
  304. }
  305. }
  306. }
  307. }
  308. }
  309. #endregion
  310. }
  311. }