MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Globalization;6using System.Linq;7using System.Management.Automation.Runspaces;8using System.Text;9 10using Dbg = System.Management.Automation.Diagnostics;11 12namespace System.Management.Automation13{14 /// <summary>15 /// Class BaseCommandHelpInfo provides common functionality for16 /// extracting information from FullHelp property.17 /// </summary>18 internal abstract class BaseCommandHelpInfo : HelpInfo19 {20 internal BaseCommandHelpInfo(HelpCategory helpCategory)21 : base()22 {23 HelpCategory = helpCategory;24 }25 26 #region Basic Help Properties27 28 internal PSObject Details29 {30 get31 {32 if (this.FullHelp == null)33 return null;34 35 if (this.FullHelp.Properties["Details"] == null ||36 this.FullHelp.Properties["Details"].Value == null)37 {38 return null;39 }40 41 return PSObject.AsPSObject(this.FullHelp.Properties["Details"].Value);42 }43 }44 45 /// <summary>46 /// Name of command.47 /// </summary>48 /// <value>Name of command</value>49 internal override string Name50 {51 get52 {53 PSObject commandDetails = this.Details;54 if (commandDetails == null)55 {56 return string.Empty;57 }58 59 if (commandDetails.Properties["Name"] == null ||60 commandDetails.Properties["Name"].Value == null)61 {62 return string.Empty;63 }64 65 string name = commandDetails.Properties["Name"].Value.ToString();66 if (name == null)67 return string.Empty;68 69 return name.Trim();70 }71 }72 73 /// <summary>74 /// Synopsis for this command help.75 /// </summary>76 /// <value>Synopsis for this command help</value>77 internal override string Synopsis78 {79 get80 {81 PSObject commandDetails = this.Details;82 if (commandDetails == null)83 {84 return string.Empty;85 }86 87 if (commandDetails.Properties["Description"] == null ||88 commandDetails.Properties["Description"].Value == null)89 {90 return string.Empty;91 }92 93 object[] synopsisItems = (object[])LanguagePrimitives.ConvertTo(94 commandDetails.Properties["Description"].Value,95 typeof(object[]),96 CultureInfo.InvariantCulture);97 if (synopsisItems == null || synopsisItems.Length == 0)98 {99 return string.Empty;100 }101 102 PSObject firstSynopsisItem = synopsisItems[0] == null ? null : PSObject.AsPSObject(synopsisItems[0]);103 if (firstSynopsisItem == null ||104 firstSynopsisItem.Properties["Text"] == null ||105 firstSynopsisItem.Properties["Text"].Value == null)106 {107 return string.Empty;108 }109 110 string synopsis = firstSynopsisItem.Properties["Text"].Value.ToString();111 if (synopsis == null)112 {113 return string.Empty;114 }115 116 return synopsis.Trim();117 }118 }119 120 /// <summary>121 /// Help category for this command help, which is constantly HelpCategory.Command.122 /// </summary>123 /// <value>Help category for this command help</value>124 internal override HelpCategory HelpCategory { get; }125 126 /// <summary>127 /// Returns the Uri used by get-help cmdlet to show help128 /// online. Returns only the first uri found under129 /// RelatedLinks.130 /// </summary>131 /// <returns>132 /// Null if no Uri is specified by the helpinfo or a133 /// valid Uri.134 /// </returns>135 /// <exception cref="InvalidOperationException">136 /// Specified Uri is not valid.137 /// </exception>138 internal override Uri GetUriForOnlineHelp()139 {140 Uri result = null;141 UriFormatException uriFormatException = null;142 143 try144 {145 result = GetUriFromCommandPSObject(this.FullHelp);146 if (result != null)147 {148 return result;149 }150 }151 catch (UriFormatException urie)152 {153 uriFormatException = urie;154 }155 // else get uri from CommandInfo HelpUri attribute156 result = this.LookupUriFromCommandInfo();157 if (result != null)158 {159 return result;160 }161 else if (uriFormatException != null)162 {163 throw uriFormatException;164 }165 166 return base.GetUriForOnlineHelp();167 }168 169 internal Uri LookupUriFromCommandInfo()170 {171 CommandTypes cmdTypesToLookFor = CommandTypes.Cmdlet;172 switch (this.HelpCategory)173 {174 case Automation.HelpCategory.Cmdlet:175 cmdTypesToLookFor = CommandTypes.Cmdlet;176 break;177 178 case Automation.HelpCategory.Function:179 cmdTypesToLookFor = CommandTypes.Function;180 break;181 182 case Automation.HelpCategory.ScriptCommand:183 cmdTypesToLookFor = CommandTypes.Script;184 break;185 186 case Automation.HelpCategory.ExternalScript:187 cmdTypesToLookFor = CommandTypes.ExternalScript;188 break;189 190 case Automation.HelpCategory.Filter:191 cmdTypesToLookFor = CommandTypes.Filter;192 break;193 194 case Automation.HelpCategory.Configuration:195 cmdTypesToLookFor = CommandTypes.Configuration;196 break;197 198 default:199 return null;200 }201 202 string commandName = this.Name;203 string moduleName = string.Empty;204 if (this.FullHelp.Properties["ModuleName"] != null)205 {206 PSNoteProperty moduleNameNP = this.FullHelp.Properties["ModuleName"] as PSNoteProperty;207 if (moduleNameNP != null)208 {209 LanguagePrimitives.TryConvertTo<string>(moduleNameNP.Value, CultureInfo.InvariantCulture,210 out moduleName);211 }212 }213 214 string commandToSearch = commandName;215 if (!string.IsNullOrEmpty(moduleName))216 {217 commandToSearch = string.Create(CultureInfo.InvariantCulture, $"{moduleName}\\{commandName}");218 }219 220 ExecutionContext context = LocalPipeline.GetExecutionContextFromTLS();221 if (context == null)222 {223 return null;224 }225 226 try227 {228 CommandInfo cmdInfo = null;229 230 if (cmdTypesToLookFor == CommandTypes.Cmdlet)231 {232 cmdInfo = context.SessionState.InvokeCommand.GetCmdlet(commandToSearch);233 }234 else235 {236 cmdInfo = context.SessionState.InvokeCommand.GetCommands(commandToSearch, cmdTypesToLookFor, false).FirstOrDefault();237 }238 239 if ((cmdInfo == null) || (cmdInfo.CommandMetadata == null))240 {241 return null;242 }243 244 string uriString = cmdInfo.CommandMetadata.HelpUri;245 if (!string.IsNullOrEmpty(uriString))246 {247 if (!System.Uri.IsWellFormedUriString(uriString, UriKind.RelativeOrAbsolute))248 {249 // WinBlue: 545315 Online help links are broken with localized help250 // Example: https://go.microsoft.com/fwlink/?LinkID=113324 (moglicherwei se auf Englisch)251 // Split the string based on <s> (space). We decided to go with this approach as252 // UX localization authors use spaces. Correctly extracting only the wellformed URI253 // is out-of-scope for this fix.254 string[] tempUriSplitArray = uriString.Split(' ');255 uriString = tempUriSplitArray[0];256 }257 258 try259 {260 return new System.Uri(uriString);261 // return only the first Uri (ignore other uris)262 }263 catch (UriFormatException)264 {265 throw PSTraceSource.NewInvalidOperationException(HelpErrors.InvalidURI,266 cmdInfo.CommandMetadata.HelpUri);267 }268 }269 }270 catch (CommandNotFoundException)271 {272 }273 274 return null;275 }276 277 internal static Uri GetUriFromCommandPSObject(PSObject commandFullHelp)278 {279 // this object knows Maml format...280 // So retrieve Uri information as per the format..281 if ((commandFullHelp == null) ||282 (commandFullHelp.Properties["relatedLinks"] == null) ||283 (commandFullHelp.Properties["relatedLinks"].Value == null))284 {285 // return the default..286 return null;287 }288 289 PSObject relatedLinks = PSObject.AsPSObject(commandFullHelp.Properties["relatedLinks"].Value);290 if (relatedLinks.Properties["navigationLink"] == null)291 {292 return null;293 }294 295 object[] navigationLinks = (object[])LanguagePrimitives.ConvertTo(296 relatedLinks.Properties["navigationLink"].Value,297 typeof(object[]),298 CultureInfo.InvariantCulture);299 foreach (object navigationLinkAsObject in navigationLinks)300 {301 if (navigationLinkAsObject == null)302 {303 continue;304 }305 306 PSObject navigationLink = PSObject.AsPSObject(navigationLinkAsObject);307 PSNoteProperty uriNP = navigationLink.Properties["uri"] as PSNoteProperty;308 if (uriNP != null)309 {310 string uriString = string.Empty;311 LanguagePrimitives.TryConvertTo<string>(uriNP.Value, CultureInfo.InvariantCulture, out uriString);312 if (!string.IsNullOrEmpty(uriString))313 {314 if (!System.Uri.IsWellFormedUriString(uriString, UriKind.RelativeOrAbsolute))315 {316 // WinBlue: 545315 Online help links are broken with localized help317 // Example: https://go.microsoft.com/fwlink/?LinkID=113324 (moglicherwei se auf Englisch)318 // Split the string based on <s> (space). We decided to go with this approach as319 // UX localization authors use spaces. Correctly extracting only the wellformed URI320 // is out-of-scope for this fix.321 string[] tempUriSplitArray = uriString.Split(' ');322 uriString = tempUriSplitArray[0];323 }324 325 try326 {327 return new System.Uri(uriString);328 // return only the first Uri (ignore other uris)329 }330 catch (UriFormatException)331 {332 throw PSTraceSource.NewInvalidOperationException(HelpErrors.InvalidURI, uriString);333 }334 }335 }336 }337 338 return null;339 }340 341 /// <summary>342 /// Returns true if help content in help info matches the343 /// pattern contained in <paramref name="pattern"/>.344 /// The underlying code will usually run pattern.IsMatch() on345 /// content it wants to search.346 /// Cmdlet help info looks for pattern in Synopsis and347 /// DetailedDescription.348 /// </summary>349 /// <param name="pattern"></param>350 /// <returns></returns>351 internal override bool MatchPatternInContent(WildcardPattern pattern)352 {353 Dbg.Assert(pattern != null, "pattern cannot be null");354 355 string synopsis = Synopsis;356 string detailedDescription = DetailedDescription;357 358 synopsis ??= string.Empty;359 360 detailedDescription ??= string.Empty;361 362 return pattern.IsMatch(synopsis) || pattern.IsMatch(detailedDescription);363 }364 365 /// <summary>366 /// Returns help information for a parameter(s) identified by pattern.367 /// </summary>368 /// <param name="pattern">Pattern to search for parameters.</param>369 /// <returns>A collection of parameters that match pattern.</returns>370 internal override PSObject[] GetParameter(string pattern)371 {372 // this object knows Maml format...373 // So retrieve parameter information as per the format..374 if ((this.FullHelp == null) ||375 (this.FullHelp.Properties["parameters"] == null) ||376 (this.FullHelp.Properties["parameters"].Value == null))377 {378 // return the default..379 return base.GetParameter(pattern);380 }381 382 PSObject prmts = PSObject.AsPSObject(this.FullHelp.Properties["parameters"].Value);383 384 if (prmts.Properties["parameter"] == null)385 {386 return base.GetParameter(pattern);387 }388 389 // The Maml format simplifies array fields containing only one object390 // by transforming them into the objects themselves. To ensure the consistency391 // of the help command result we change it back into an array.392 var param = prmts.Properties["parameter"].Value;393 PSObject[] paramAsPSObjArray = new PSObject[1];394 395 if (param is PSObject paramPSObj)396 {397 paramAsPSObjArray[0] = paramPSObj;398 }399 400 PSObject[] prmtArray = (PSObject[])LanguagePrimitives.ConvertTo(401 paramAsPSObjArray[0] != null ? paramAsPSObjArray : param,402 typeof(PSObject[]),403 CultureInfo.InvariantCulture);404 405 if (string.IsNullOrEmpty(pattern))406 {407 return prmtArray;408 }409 410 List<PSObject> returnList = new List<PSObject>();411 WildcardPattern matcher = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase);412 foreach (PSObject prmtr in prmtArray)413 {414 if ((prmtr.Properties["name"] == null) || (prmtr.Properties["name"].Value == null))415 {416 continue;417 }418 419 string prmName = prmtr.Properties["name"].Value.ToString();420 if (matcher.IsMatch(prmName))421 {422 returnList.Add(prmtr);423 }424 }425 426 return returnList.ToArray();427 }428 429 #endregion430 431 #region Cmdlet Help specific Properties432 433 /// <summary>434 /// Detailed Description string of this cmdlet help info.435 /// </summary>436 internal string DetailedDescription437 {438 get439 {440 if (this.FullHelp == null)441 return string.Empty;442 443 if (this.FullHelp.Properties["Description"] == null ||444 this.FullHelp.Properties["Description"].Value == null)445 {446 return string.Empty;447 }448 449 object[] descriptionItems = (object[])LanguagePrimitives.ConvertTo(450 this.FullHelp.Properties["Description"].Value,451 typeof(object[]),452 CultureInfo.InvariantCulture);453 if (descriptionItems == null || descriptionItems.Length == 0)454 {455 return string.Empty;456 }457 458 // I think every cmdlet description should at least have 400 characters...459 // so starting with this assumption..I did an average of all the cmdlet460 // help content available at the time of writing this code and came up461 // with this number.462 StringBuilder result = new StringBuilder(400);463 foreach (object descriptionItem in descriptionItems)464 {465 if (descriptionItem == null)466 {467 continue;468 }469 470 PSObject descriptionObject = PSObject.AsPSObject(descriptionItem);471 if ((descriptionObject == null) ||472 (descriptionObject.Properties["Text"] == null) ||473 (descriptionObject.Properties["Text"].Value == null))474 {475 continue;476 }477 478 string text = descriptionObject.Properties["Text"].Value.ToString();479 result.Append(text);480 result.Append(Environment.NewLine);481 }482 483 return result.ToString().Trim();484 }485 }486 487 #endregion488 }489}490 