mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-14 21:59:20 +00:00
486 lines
17 KiB
C#
486 lines
17 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
/// <summary>
|
|
/// Class BaseCommandHelpInfo provides common functionality for
|
|
/// extracting information from FullHelp property.
|
|
/// </summary>
|
|
internal abstract class BaseCommandHelpInfo : HelpInfo
|
|
{
|
|
internal BaseCommandHelpInfo(HelpCategory helpCategory)
|
|
: base()
|
|
{
|
|
HelpCategory = helpCategory;
|
|
}
|
|
|
|
#region Basic Help Properties
|
|
|
|
internal PSObject Details
|
|
{
|
|
get
|
|
{
|
|
if (this.FullHelp == null)
|
|
return null;
|
|
|
|
if (this.FullHelp.Properties["Details"] == null ||
|
|
this.FullHelp.Properties["Details"].Value == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return PSObject.AsPSObject(this.FullHelp.Properties["Details"].Value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Name of command.
|
|
/// </summary>
|
|
/// <value>Name of command</value>
|
|
internal override string Name
|
|
{
|
|
get
|
|
{
|
|
PSObject commandDetails = this.Details;
|
|
if (commandDetails == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
if (commandDetails.Properties["Name"] == null ||
|
|
commandDetails.Properties["Name"].Value == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
string name = commandDetails.Properties["Name"].Value.ToString();
|
|
if (name == null)
|
|
return string.Empty;
|
|
|
|
return name.Trim();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Synopsis for this command help.
|
|
/// </summary>
|
|
/// <value>Synopsis for this command help</value>
|
|
internal override string Synopsis
|
|
{
|
|
get
|
|
{
|
|
PSObject commandDetails = this.Details;
|
|
if (commandDetails == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
if (commandDetails.Properties["Description"] == null ||
|
|
commandDetails.Properties["Description"].Value == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
object[] synopsisItems = (object[])LanguagePrimitives.ConvertTo(
|
|
commandDetails.Properties["Description"].Value,
|
|
typeof(object[]),
|
|
CultureInfo.InvariantCulture);
|
|
if (synopsisItems == null || synopsisItems.Length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
PSObject firstSynopsisItem = synopsisItems[0] == null ? null : PSObject.AsPSObject(synopsisItems[0]);
|
|
if (firstSynopsisItem == null ||
|
|
firstSynopsisItem.Properties["Text"] == null ||
|
|
firstSynopsisItem.Properties["Text"].Value == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
string synopsis = firstSynopsisItem.Properties["Text"].Value.ToString();
|
|
if (synopsis == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return synopsis.Trim();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Help category for this command help, which is constantly HelpCategory.Command.
|
|
/// </summary>
|
|
/// <value>Help category for this command help</value>
|
|
internal override HelpCategory HelpCategory { get; }
|
|
|
|
/// <summary>
|
|
/// Returns the Uri used by get-help cmdlet to show help
|
|
/// online. Returns only the first uri found under
|
|
/// RelatedLinks.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Null if no Uri is specified by the helpinfo or a
|
|
/// valid Uri.
|
|
/// </returns>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Specified Uri is not valid.
|
|
/// </exception>
|
|
internal override Uri GetUriForOnlineHelp()
|
|
{
|
|
Uri result = null;
|
|
UriFormatException uriFormatException = null;
|
|
|
|
try
|
|
{
|
|
result = GetUriFromCommandPSObject(this.FullHelp);
|
|
if (result != null)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
catch (UriFormatException urie)
|
|
{
|
|
uriFormatException = urie;
|
|
}
|
|
// else get uri from CommandInfo HelpUri attribute
|
|
result = this.LookupUriFromCommandInfo();
|
|
if (result != null)
|
|
{
|
|
return result;
|
|
}
|
|
else if (uriFormatException != null)
|
|
{
|
|
throw uriFormatException;
|
|
}
|
|
|
|
return base.GetUriForOnlineHelp();
|
|
}
|
|
|
|
internal Uri LookupUriFromCommandInfo()
|
|
{
|
|
CommandTypes cmdTypesToLookFor = CommandTypes.Cmdlet;
|
|
switch (this.HelpCategory)
|
|
{
|
|
case Automation.HelpCategory.Cmdlet:
|
|
cmdTypesToLookFor = CommandTypes.Cmdlet;
|
|
break;
|
|
|
|
case Automation.HelpCategory.Function:
|
|
cmdTypesToLookFor = CommandTypes.Function;
|
|
break;
|
|
|
|
case Automation.HelpCategory.ScriptCommand:
|
|
cmdTypesToLookFor = CommandTypes.Script;
|
|
break;
|
|
|
|
case Automation.HelpCategory.ExternalScript:
|
|
cmdTypesToLookFor = CommandTypes.ExternalScript;
|
|
break;
|
|
|
|
case Automation.HelpCategory.Filter:
|
|
cmdTypesToLookFor = CommandTypes.Filter;
|
|
break;
|
|
|
|
case Automation.HelpCategory.Configuration:
|
|
cmdTypesToLookFor = CommandTypes.Configuration;
|
|
break;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
string commandName = this.Name;
|
|
string moduleName = string.Empty;
|
|
if (this.FullHelp.Properties["ModuleName"] != null)
|
|
{
|
|
PSNoteProperty moduleNameNP = this.FullHelp.Properties["ModuleName"] as PSNoteProperty;
|
|
if (moduleNameNP != null)
|
|
{
|
|
LanguagePrimitives.TryConvertTo<string>(moduleNameNP.Value, CultureInfo.InvariantCulture,
|
|
out moduleName);
|
|
}
|
|
}
|
|
|
|
string commandToSearch = commandName;
|
|
if (!string.IsNullOrEmpty(moduleName))
|
|
{
|
|
commandToSearch = string.Format(CultureInfo.InvariantCulture,
|
|
"{0}\\{1}", moduleName, commandName);
|
|
}
|
|
|
|
ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();
|
|
if (context == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
CommandInfo cmdInfo = null;
|
|
|
|
if (cmdTypesToLookFor == CommandTypes.Cmdlet)
|
|
{
|
|
cmdInfo = context.SessionState.InvokeCommand.GetCmdlet(commandToSearch);
|
|
}
|
|
else
|
|
{
|
|
cmdInfo = context.SessionState.InvokeCommand.GetCommands(commandToSearch, cmdTypesToLookFor, false).FirstOrDefault();
|
|
}
|
|
|
|
if ((cmdInfo == null) || (cmdInfo.CommandMetadata == null))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
string uriString = cmdInfo.CommandMetadata.HelpUri;
|
|
if (!string.IsNullOrEmpty(uriString))
|
|
{
|
|
if (!System.Uri.IsWellFormedUriString(uriString, UriKind.RelativeOrAbsolute))
|
|
{
|
|
// WinBlue: 545315 Online help links are broken with localized help
|
|
// Example: https://go.microsoft.com/fwlink/?LinkID=113324 (moglicherwei se auf Englisch)
|
|
// Split the string based on <s> (space). We decided to go with this approach as
|
|
// UX localization authors use spaces. Correctly extracting only the wellformed URI
|
|
// is out-of-scope for this fix.
|
|
string[] tempUriSplitArray = uriString.Split(Utils.Separators.Space);
|
|
uriString = tempUriSplitArray[0];
|
|
}
|
|
|
|
try
|
|
{
|
|
return new System.Uri(uriString);
|
|
// return only the first Uri (ignore other uris)
|
|
}
|
|
catch (UriFormatException)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(HelpErrors.InvalidURI,
|
|
cmdInfo.CommandMetadata.HelpUri);
|
|
}
|
|
}
|
|
}
|
|
catch (CommandNotFoundException)
|
|
{
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static Uri GetUriFromCommandPSObject(PSObject commandFullHelp)
|
|
{
|
|
// this object knows Maml format...
|
|
// So retrieve Uri information as per the format..
|
|
if ((commandFullHelp == null) ||
|
|
(commandFullHelp.Properties["relatedLinks"] == null) ||
|
|
(commandFullHelp.Properties["relatedLinks"].Value == null))
|
|
{
|
|
// return the default..
|
|
return null;
|
|
}
|
|
|
|
PSObject relatedLinks = PSObject.AsPSObject(commandFullHelp.Properties["relatedLinks"].Value);
|
|
if (relatedLinks.Properties["navigationLink"] == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
object[] navigationLinks = (object[])LanguagePrimitives.ConvertTo(
|
|
relatedLinks.Properties["navigationLink"].Value,
|
|
typeof(object[]),
|
|
CultureInfo.InvariantCulture);
|
|
foreach (object navigationLinkAsObject in navigationLinks)
|
|
{
|
|
if (navigationLinkAsObject == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PSObject navigationLink = PSObject.AsPSObject(navigationLinkAsObject);
|
|
PSNoteProperty uriNP = navigationLink.Properties["uri"] as PSNoteProperty;
|
|
if (uriNP != null)
|
|
{
|
|
string uriString = string.Empty;
|
|
LanguagePrimitives.TryConvertTo<string>(uriNP.Value, CultureInfo.InvariantCulture, out uriString);
|
|
if (!string.IsNullOrEmpty(uriString))
|
|
{
|
|
if (!System.Uri.IsWellFormedUriString(uriString, UriKind.RelativeOrAbsolute))
|
|
{
|
|
// WinBlue: 545315 Online help links are broken with localized help
|
|
// Example: https://go.microsoft.com/fwlink/?LinkID=113324 (moglicherwei se auf Englisch)
|
|
// Split the string based on <s> (space). We decided to go with this approach as
|
|
// UX localization authors use spaces. Correctly extracting only the wellformed URI
|
|
// is out-of-scope for this fix.
|
|
string[] tempUriSplitArray = uriString.Split(Utils.Separators.Space);
|
|
uriString = tempUriSplitArray[0];
|
|
}
|
|
|
|
try
|
|
{
|
|
return new System.Uri(uriString);
|
|
// return only the first Uri (ignore other uris)
|
|
}
|
|
catch (UriFormatException)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(HelpErrors.InvalidURI, uriString);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if help content in help info matches the
|
|
/// pattern contained in <paramref name="pattern"/>.
|
|
/// The underlying code will usually run pattern.IsMatch() on
|
|
/// content it wants to search.
|
|
/// Cmdlet help info looks for pattern in Synopsis and
|
|
/// DetailedDescription
|
|
/// </summary>
|
|
/// <param name="pattern"></param>
|
|
/// <returns></returns>
|
|
internal override bool MatchPatternInContent(WildcardPattern pattern)
|
|
{
|
|
Dbg.Assert(pattern != null, "pattern cannot be null");
|
|
|
|
string synopsis = Synopsis;
|
|
string detailedDescription = DetailedDescription;
|
|
|
|
if (synopsis == null)
|
|
{
|
|
synopsis = string.Empty;
|
|
}
|
|
|
|
if (detailedDescription == null)
|
|
{
|
|
detailedDescription = string.Empty;
|
|
}
|
|
|
|
return pattern.IsMatch(synopsis) || pattern.IsMatch(detailedDescription);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns help information for a parameter(s) identified by pattern
|
|
/// </summary>
|
|
/// <param name="pattern">pattern to search for parameters</param>
|
|
/// <returns>A collection of parameters that match pattern</returns>
|
|
internal override PSObject[] GetParameter(string pattern)
|
|
{
|
|
// this object knows Maml format...
|
|
// So retrieve parameter information as per the format..
|
|
if ((this.FullHelp == null) ||
|
|
(this.FullHelp.Properties["parameters"] == null) ||
|
|
(this.FullHelp.Properties["parameters"].Value == null))
|
|
{
|
|
// return the default..
|
|
return base.GetParameter(pattern);
|
|
}
|
|
|
|
PSObject prmts = PSObject.AsPSObject(this.FullHelp.Properties["parameters"].Value);
|
|
|
|
if (prmts.Properties["parameter"] == null)
|
|
{
|
|
return base.GetParameter(pattern);
|
|
}
|
|
|
|
PSObject[] prmtArray = (PSObject[])LanguagePrimitives.ConvertTo(
|
|
prmts.Properties["parameter"].Value,
|
|
typeof(PSObject[]),
|
|
CultureInfo.InvariantCulture);
|
|
|
|
if (string.IsNullOrEmpty(pattern))
|
|
{
|
|
return prmtArray;
|
|
}
|
|
|
|
List<PSObject> returnList = new List<PSObject>();
|
|
WildcardPattern matcher = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase);
|
|
foreach (PSObject prmtr in prmtArray)
|
|
{
|
|
if ((prmtr.Properties["name"] == null) || (prmtr.Properties["name"].Value == null))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string prmName = prmtr.Properties["name"].Value.ToString();
|
|
if (matcher.IsMatch(prmName))
|
|
{
|
|
returnList.Add(prmtr);
|
|
}
|
|
}
|
|
|
|
return returnList.ToArray();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Cmdlet Help specific Properties
|
|
|
|
/// <summary>
|
|
/// Detailed Description string of this cmdlet help info.
|
|
/// </summary>
|
|
internal string DetailedDescription
|
|
{
|
|
get
|
|
{
|
|
if (this.FullHelp == null)
|
|
return string.Empty;
|
|
|
|
if (this.FullHelp.Properties["Description"] == null ||
|
|
this.FullHelp.Properties["Description"].Value == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
object[] descriptionItems = (object[])LanguagePrimitives.ConvertTo(
|
|
this.FullHelp.Properties["Description"].Value,
|
|
typeof(object[]),
|
|
CultureInfo.InvariantCulture);
|
|
if (descriptionItems == null || descriptionItems.Length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
// I think every cmdlet description should atleast have 400 characters...
|
|
// so starting with this assumption..I did an average of all the cmdlet
|
|
// help content available at the time of writing this code and came up
|
|
// with this number.
|
|
StringBuilder result = new StringBuilder(400);
|
|
foreach (object descriptionItem in descriptionItems)
|
|
{
|
|
if (descriptionItem == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PSObject descriptionObject = PSObject.AsPSObject(descriptionItem);
|
|
if ((descriptionObject == null) ||
|
|
(descriptionObject.Properties["Text"] == null) ||
|
|
(descriptionObject.Properties["Text"].Value == null))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string text = descriptionObject.Properties["Text"].Value.ToString();
|
|
result.Append(text);
|
|
result.Append(Environment.NewLine);
|
|
}
|
|
|
|
return result.ToString().Trim();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|