mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-15 22:29:08 +00:00
371 lines
13 KiB
C#
371 lines
13 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Xml;
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
internal class DscResourceHelpProvider : HelpProviderWithCache
|
|
{
|
|
/// <summary>
|
|
/// Constructor for DscResourceHelpProvider.
|
|
/// </summary>
|
|
internal DscResourceHelpProvider(HelpSystem helpSystem)
|
|
: base(helpSystem)
|
|
{
|
|
_context = helpSystem.ExecutionContext;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execution context of the HelpSystem.
|
|
/// </summary>
|
|
private readonly ExecutionContext _context;
|
|
|
|
/// <summary>
|
|
/// This is a hashtable to track which help files are loaded already.
|
|
///
|
|
/// This will avoid one help file getting loaded again and again.
|
|
/// </summary>
|
|
private readonly Hashtable _helpFiles = new Hashtable();
|
|
|
|
[TraceSource("DscResourceHelpProvider", "DscResourceHelpProvider")]
|
|
private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("DscResourceHelpProvider", "DscResourceHelpProvider");
|
|
|
|
#region common properties
|
|
|
|
/// <summary>
|
|
/// Name of the Help Provider.
|
|
/// </summary>
|
|
internal override string Name
|
|
{
|
|
get { return "Dsc Resource Help Provider"; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Supported Help Categories.
|
|
/// </summary>
|
|
internal override HelpCategory HelpCategory
|
|
{
|
|
get { return Automation.HelpCategory.DscResource; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Override SearchHelp to find a dsc resource help matching a pattern.
|
|
/// </summary>
|
|
/// <param name="helpRequest">Help request.</param>
|
|
/// <param name="searchOnlyContent">Not used.</param>
|
|
/// <returns></returns>
|
|
internal override IEnumerable<HelpInfo> SearchHelp(HelpRequest helpRequest, bool searchOnlyContent)
|
|
{
|
|
Debug.Assert(helpRequest != null, "helpRequest cannot be null.");
|
|
|
|
string target = helpRequest.Target;
|
|
Collection<string> patternList = new Collection<string>();
|
|
|
|
bool decoratedSearch = !WildcardPattern.ContainsWildcardCharacters(helpRequest.Target);
|
|
|
|
if (decoratedSearch)
|
|
{
|
|
patternList.Add("*" + target + "*");
|
|
}
|
|
else
|
|
patternList.Add(target);
|
|
|
|
foreach (string pattern in patternList)
|
|
{
|
|
DscResourceSearcher searcher = new DscResourceSearcher(pattern, _context);
|
|
|
|
foreach (var helpInfo in GetHelpInfo(searcher))
|
|
{
|
|
if (helpInfo != null)
|
|
yield return helpInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override ExactMatchHelp to find the matching DscResource matching help request.
|
|
/// </summary>
|
|
/// <param name="helpRequest">Help Request for the search.</param>
|
|
/// <returns>Enumerable of HelpInfo objects.</returns>
|
|
internal override IEnumerable<HelpInfo> ExactMatchHelp(HelpRequest helpRequest)
|
|
{
|
|
Debug.Assert(helpRequest != null, "helpRequest cannot be null.");
|
|
|
|
if ((helpRequest.HelpCategory & Automation.HelpCategory.DscResource) == 0)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
string target = helpRequest.Target;
|
|
|
|
DscResourceSearcher searcher = new DscResourceSearcher(target, _context);
|
|
|
|
foreach (var helpInfo in GetHelpInfo(searcher))
|
|
{
|
|
if (helpInfo != null)
|
|
{
|
|
yield return helpInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the help in for the DscResource Info. ///
|
|
/// </summary>
|
|
/// <param name="searcher">Searcher for DscResources.</param>
|
|
/// <returns>Next HelpInfo object.</returns>
|
|
private IEnumerable<HelpInfo> GetHelpInfo(DscResourceSearcher searcher)
|
|
{
|
|
while (searcher.MoveNext())
|
|
{
|
|
DscResourceInfo current = ((IEnumerator<DscResourceInfo>)searcher).Current;
|
|
|
|
string moduleName = null;
|
|
string moduleDir = current.ParentPath;
|
|
|
|
// for binary modules, current.Module is empty.
|
|
// in such cases use the leaf folder of ParentPath as filename.
|
|
if (current.Module != null)
|
|
{
|
|
moduleName = current.Module.Name;
|
|
}
|
|
else if (!string.IsNullOrEmpty(moduleDir))
|
|
{
|
|
string[] splitPath = moduleDir.Split(Utils.Separators.Backslash);
|
|
moduleName = splitPath[splitPath.Length - 1];
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(moduleName) && !string.IsNullOrEmpty(moduleDir))
|
|
{
|
|
string helpFileToFind = moduleName + "-Help.xml";
|
|
|
|
string helpFileName = null;
|
|
|
|
Collection<string> searchPaths = new Collection<string>();
|
|
searchPaths.Add(moduleDir);
|
|
|
|
HelpInfo helpInfo = GetHelpInfoFromHelpFile(current, helpFileToFind, searchPaths, true, out helpFileName);
|
|
|
|
if (helpInfo != null)
|
|
{
|
|
yield return helpInfo;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check whether a HelpItems node indicates that the help content is
|
|
/// authored using maml schema.
|
|
///
|
|
/// This covers two cases:
|
|
/// a. If the help file has an extension .maml.
|
|
/// b. If HelpItems node (which should be the top node of any command help file)
|
|
/// has an attribute "schema" with value "maml", its content is in maml
|
|
/// schema.
|
|
/// </summary>
|
|
/// <param name="helpFile">File name.</param>
|
|
/// <param name="helpItemsNode">Nodes to check.</param>
|
|
/// <returns></returns>
|
|
internal static bool IsMamlHelp(string helpFile, XmlNode helpItemsNode)
|
|
{
|
|
Debug.Assert(!string.IsNullOrEmpty(helpFile), "helpFile cannot be null.");
|
|
|
|
if (helpFile.EndsWith(".maml", StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
|
|
if (helpItemsNode.Attributes == null)
|
|
return false;
|
|
|
|
foreach (XmlNode attribute in helpItemsNode.Attributes)
|
|
{
|
|
if (attribute.Name.Equals("schema", StringComparison.OrdinalIgnoreCase)
|
|
&& attribute.Value.Equals("maml", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#region private methods
|
|
|
|
private HelpInfo GetHelpInfoFromHelpFile(DscResourceInfo resourceInfo, string helpFileToFind, Collection<string> searchPaths, bool reportErrors, out string helpFile)
|
|
{
|
|
Dbg.Assert(resourceInfo != null, "Caller should verify that resourceInfo != null");
|
|
Dbg.Assert(helpFileToFind != null, "Caller should verify that helpFileToFind != null");
|
|
|
|
helpFile = MUIFileSearcher.LocateFile(helpFileToFind, searchPaths);
|
|
|
|
if (!File.Exists(helpFile))
|
|
return null;
|
|
|
|
if (!string.IsNullOrEmpty(helpFile))
|
|
{
|
|
// Load the help file only once. Then use it from the cache.
|
|
if (!_helpFiles.Contains(helpFile))
|
|
{
|
|
LoadHelpFile(helpFile, helpFile, resourceInfo.Name, reportErrors);
|
|
}
|
|
|
|
return GetFromResourceHelpCache(helpFile, Automation.HelpCategory.DscResource);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the HelpInfo object corresponding to the command.
|
|
/// </summary>
|
|
/// <param name="helpFileIdentifier">Help file identifier (either name of PSSnapIn or simply full path to help file).</param>
|
|
/// <param name="helpCategory">Help Category for search.</param>
|
|
/// <returns>HelpInfo object.</returns>
|
|
private HelpInfo GetFromResourceHelpCache(string helpFileIdentifier, HelpCategory helpCategory)
|
|
{
|
|
Debug.Assert(!string.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier should not be null or empty.");
|
|
|
|
HelpInfo result = GetCache(helpFileIdentifier);
|
|
|
|
if (result != null)
|
|
{
|
|
MamlCommandHelpInfo original = (MamlCommandHelpInfo)result;
|
|
result = original.Copy(helpCategory);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void LoadHelpFile(string helpFile, string helpFileIdentifier, string commandName, bool reportErrors)
|
|
{
|
|
Exception e = null;
|
|
try
|
|
{
|
|
LoadHelpFile(helpFile, helpFileIdentifier);
|
|
}
|
|
catch (IOException ioException)
|
|
{
|
|
e = ioException;
|
|
}
|
|
catch (System.Security.SecurityException securityException)
|
|
{
|
|
e = securityException;
|
|
}
|
|
catch (XmlException xmlException)
|
|
{
|
|
e = xmlException;
|
|
}
|
|
catch (NotSupportedException notSupportedException)
|
|
{
|
|
e = notSupportedException;
|
|
}
|
|
catch (UnauthorizedAccessException unauthorizedAccessException)
|
|
{
|
|
e = unauthorizedAccessException;
|
|
}
|
|
catch (InvalidOperationException invalidOperationException)
|
|
{
|
|
e = invalidOperationException;
|
|
}
|
|
|
|
if (e != null)
|
|
s_tracer.WriteLine("Error occured in DscResourceHelpProvider {0}", e.Message);
|
|
|
|
if (reportErrors && (e != null))
|
|
{
|
|
ReportHelpFileError(e, commandName, helpFile);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load help file for HelpInfo objects. The HelpInfo objects will be
|
|
/// put into help cache.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 1. Needs to pay special attention about error handling in this function.
|
|
/// Common errors include: file not found and invalid xml. None of these error
|
|
/// should cause help search to stop.
|
|
/// 2. a helpfile cache is used to avoid same file got loaded again and again.
|
|
/// </remarks>
|
|
private void LoadHelpFile(string helpFile, string helpFileIdentifier)
|
|
{
|
|
Dbg.Assert(!string.IsNullOrEmpty(helpFile), "HelpFile cannot be null or empty.");
|
|
Dbg.Assert(!string.IsNullOrEmpty(helpFileIdentifier), "helpFileIdentifier cannot be null or empty.");
|
|
|
|
XmlDocument doc = InternalDeserializer.LoadUnsafeXmlDocument(
|
|
new FileInfo(helpFile),
|
|
false, /* ignore whitespace, comments, etc. */
|
|
null); /* default maxCharactersInDocument */
|
|
|
|
// Add this file into _helpFiles hashtable to prevent it to be loaded again.
|
|
_helpFiles[helpFile] = 0;
|
|
|
|
XmlNode helpItemsNode = null;
|
|
|
|
if (doc.HasChildNodes)
|
|
{
|
|
for (int i = 0; i < doc.ChildNodes.Count; i++)
|
|
{
|
|
XmlNode node = doc.ChildNodes[i];
|
|
if (node.NodeType == XmlNodeType.Element && string.Compare(node.LocalName, "helpItems", StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
helpItemsNode = node;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (helpItemsNode == null)
|
|
{
|
|
s_tracer.WriteLine("Unable to find 'helpItems' element in file {0}", helpFile);
|
|
return;
|
|
}
|
|
|
|
bool isMaml = IsMamlHelp(helpFile, helpItemsNode);
|
|
|
|
using (this.HelpSystem.Trace(helpFile))
|
|
{
|
|
if (helpItemsNode.HasChildNodes)
|
|
{
|
|
for (int i = 0; i < helpItemsNode.ChildNodes.Count; i++)
|
|
{
|
|
XmlNode node = helpItemsNode.ChildNodes[i];
|
|
|
|
string nodeLocalName = node.LocalName;
|
|
|
|
bool isDscResource = (string.Compare(nodeLocalName, "dscResource", StringComparison.OrdinalIgnoreCase) == 0);
|
|
|
|
if (node.NodeType == XmlNodeType.Element && isDscResource)
|
|
{
|
|
MamlCommandHelpInfo helpInfo = null;
|
|
|
|
if (isMaml)
|
|
{
|
|
if (isDscResource)
|
|
helpInfo = MamlCommandHelpInfo.Load(node, HelpCategory.DscResource);
|
|
}
|
|
|
|
if (helpInfo != null)
|
|
{
|
|
this.HelpSystem.TraceErrors(helpInfo.Errors);
|
|
AddCache(helpFileIdentifier, helpInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|