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.Globalization;8using System.IO;9using System.Reflection;10using System.Xml;11 12namespace System.Management.Automation13{14 /// <summary>15 /// Class ProviderHelpProvider implement the help provider for commands.16 /// </summary>17 /// <remarks>18 /// Provider Help information are stored in 'help.xml' files. Location of these files19 /// can be found from CommandDiscovery.20 /// </remarks>21 internal class ProviderHelpProvider : HelpProviderWithCache22 {23 /// <summary>24 /// Constructor for HelpProvider.25 /// </summary>26 internal ProviderHelpProvider(HelpSystem helpSystem) : base(helpSystem)27 {28 _sessionState = helpSystem.ExecutionContext.SessionState;29 }30 31 private readonly SessionState _sessionState;32 33 #region Common Properties34 35 /// <summary>36 /// Name of this help provider.37 /// </summary>38 /// <value>Name of this help provider.</value>39 internal override string Name40 {41 get42 {43 return "Provider Help Provider";44 }45 }46 47 /// <summary>48 /// Help category of this provider.49 /// </summary>50 /// <value>Help category of this provider</value>51 internal override HelpCategory HelpCategory52 {53 get54 {55 return HelpCategory.Provider;56 }57 }58 59 #endregion60 61 #region Help Provider Interface62 63 /// <summary>64 /// Do exact match help based on the target.65 /// </summary>66 /// <param name="helpRequest">Help request object.</param>67 internal override IEnumerable<HelpInfo> ExactMatchHelp(HelpRequest helpRequest)68 {69 Collection<ProviderInfo> matchingProviders = null;70 71 try72 {73 matchingProviders = _sessionState.Provider.Get(helpRequest.Target);74 }75 catch (ProviderNotFoundException e)76 {77 // We distinguish two cases here,78 // a. If the "Provider" is the only category to search for in this case,79 // an error will be written.80 // b. Otherwise, no errors will be written since in end user's mind,81 // he may mean to search for provider help.82 if (this.HelpSystem.LastHelpCategory == HelpCategory.Provider)83 {84 ErrorRecord errorRecord = new ErrorRecord(e, "ProviderLoadError", ErrorCategory.ResourceUnavailable, null);85 errorRecord.ErrorDetails = new ErrorDetails(typeof(ProviderHelpProvider).Assembly, "HelpErrors", "ProviderLoadError", helpRequest.Target, e.Message);86 this.HelpSystem.LastErrors.Add(errorRecord);87 }88 }89 90 if (matchingProviders != null)91 {92 foreach (ProviderInfo providerInfo in matchingProviders)93 {94 try95 {96 LoadHelpFile(providerInfo);97 }98 catch (IOException ioException)99 {100 ReportHelpFileError(ioException, helpRequest.Target, providerInfo.HelpFile);101 }102 catch (System.Security.SecurityException securityException)103 {104 ReportHelpFileError(securityException, helpRequest.Target, providerInfo.HelpFile);105 }106 catch (XmlException xmlException)107 {108 ReportHelpFileError(xmlException, helpRequest.Target, providerInfo.HelpFile);109 }110 111 HelpInfo helpInfo = GetCache(providerInfo.PSSnapInName + "\\" + providerInfo.Name);112 113 if (helpInfo != null)114 {115 yield return helpInfo;116 }117 }118 }119 }120 121 private static string GetProviderAssemblyPath(ProviderInfo providerInfo)122 {123 if (providerInfo == null)124 return null;125 126 if (providerInfo.ImplementingType == null)127 return null;128 129 return Path.GetDirectoryName(providerInfo.ImplementingType.Assembly.Location);130 }131 132 /// <summary>133 /// This is a hashtable to track which help files are loaded already.134 ///135 /// This will avoid one help file getting loaded again and again.136 /// (Which should not happen unless some provider is pointing137 /// to a help file that actually doesn't contain the help for it).138 /// </summary>139 private readonly Hashtable _helpFiles = new Hashtable();140 141 /// <summary>142 /// Load help file provided.143 /// </summary>144 /// <remarks>145 /// This will load providerHelpInfo from help file into help cache.146 /// </remarks>147 /// <param name="providerInfo">ProviderInfo for which to locate help.</param>148 private void LoadHelpFile(ProviderInfo providerInfo)149 {150 if (providerInfo == null)151 {152 throw PSTraceSource.NewArgumentNullException(nameof(providerInfo));153 }154 155 string helpFile = providerInfo.HelpFile;156 157 if (string.IsNullOrEmpty(helpFile) || _helpFiles.Contains(helpFile))158 {159 return;160 }161 162 string helpFileToLoad = helpFile;163 164 // Get the mshsnapinfo object for this cmdlet.165 PSSnapInInfo mshSnapInInfo = providerInfo.PSSnapIn;166 167 // Search fallback168 // 1. If PSSnapInInfo exists, then always look in the application base169 // of the mshsnapin170 // Otherwise,171 // Look in the default search path and cmdlet assembly path172 Collection<string> searchPaths = new Collection<string>();173 if (mshSnapInInfo != null)174 {175 Diagnostics.Assert(!string.IsNullOrEmpty(mshSnapInInfo.ApplicationBase),176 "Application Base is null or empty.");177 // not minishell case..178 // we have to search only in the application base for a mshsnapin...179 // if you create an absolute path for helpfile, then MUIFileSearcher180 // will look only in that path.181 helpFileToLoad = Path.Combine(mshSnapInInfo.ApplicationBase, helpFile);182 }183 else if ((providerInfo.Module != null) && (!string.IsNullOrEmpty(providerInfo.Module.Path)))184 {185 helpFileToLoad = Path.Combine(providerInfo.Module.ModuleBase, helpFile);186 }187 else188 {189 searchPaths.Add(GetDefaultShellSearchPath());190 searchPaths.Add(GetProviderAssemblyPath(providerInfo));191 }192 193 string location = MUIFileSearcher.LocateFile(helpFileToLoad, searchPaths);194 if (string.IsNullOrEmpty(location))195 throw new FileNotFoundException(helpFile);196 197 XmlDocument doc = InternalDeserializer.LoadUnsafeXmlDocument(198 new FileInfo(location),199 false, /* ignore whitespace, comments, etc. */200 null); /* default maxCharactersInDocument */201 202 // Add this file into _helpFiles hashtable to prevent it to be loaded again.203 _helpFiles[helpFile] = 0;204 205 XmlNode helpItemsNode = null;206 207 if (doc.HasChildNodes)208 {209 for (int i = 0; i < doc.ChildNodes.Count; i++)210 {211 XmlNode node = doc.ChildNodes[i];212 if (node.NodeType == XmlNodeType.Element && string.Equals(node.Name, "helpItems", StringComparison.OrdinalIgnoreCase))213 {214 helpItemsNode = node;215 break;216 }217 }218 }219 220 if (helpItemsNode == null)221 return;222 223 using (this.HelpSystem.Trace(location))224 {225 if (helpItemsNode.HasChildNodes)226 {227 for (int i = 0; i < helpItemsNode.ChildNodes.Count; i++)228 {229 XmlNode node = helpItemsNode.ChildNodes[i];230 if (node.NodeType == XmlNodeType.Element && string.Equals(node.Name, "providerHelp", StringComparison.OrdinalIgnoreCase))231 {232 HelpInfo helpInfo = ProviderHelpInfo.Load(node);233 234 if (helpInfo != null)235 {236 this.HelpSystem.TraceErrors(helpInfo.Errors);237 // Add snapin qualified type name for this command..238 // this will enable customizations of the help object.239 helpInfo.FullHelp.TypeNames.Insert(240 index: 0,241 string.Create(242 CultureInfo.InvariantCulture,243 $"ProviderHelpInfo#{providerInfo.PSSnapInName}#{helpInfo.Name}"));244 245 if (!string.IsNullOrEmpty(providerInfo.PSSnapInName))246 {247 helpInfo.FullHelp.Properties.Add(new PSNoteProperty("PSSnapIn", providerInfo.PSSnapIn));248 helpInfo.FullHelp.TypeNames.Insert(249 index: 1,250 string.Create(251 CultureInfo.InvariantCulture,252 $"ProviderHelpInfo#{providerInfo.PSSnapInName}"));253 }254 255 AddCache(providerInfo.PSSnapInName + "\\" + helpInfo.Name, helpInfo);256 }257 }258 }259 }260 }261 }262 263 /// <summary>264 /// Search for provider help based on a search target.265 /// </summary>266 /// <param name="helpRequest">Help request object.</param>267 /// <param name="searchOnlyContent">268 /// If true, searches for pattern in the help content. Individual269 /// provider can decide which content to search in.270 ///271 /// If false, searches for pattern in the command names.272 /// </param>273 /// <returns></returns>274 internal override IEnumerable<HelpInfo> SearchHelp(HelpRequest helpRequest, bool searchOnlyContent)275 {276 int countOfHelpInfoObjectsFound = 0;277 string target = helpRequest.Target;278 string pattern = target;279 // this will be used only when searchOnlyContent == true280 WildcardPattern wildCardPattern = null;281 282 bool decoratedSearch = !WildcardPattern.ContainsWildcardCharacters(target);283 284 if (!searchOnlyContent)285 {286 if (decoratedSearch)287 {288 pattern += "*";289 }290 }291 else292 {293 string searchTarget = helpRequest.Target;294 if (decoratedSearch)295 {296 searchTarget = "*" + helpRequest.Target + "*";297 }298 299 wildCardPattern = WildcardPattern.Get(searchTarget, WildcardOptions.Compiled | WildcardOptions.IgnoreCase);300 // search in all providers301 pattern = "*";302 }303 304 PSSnapinQualifiedName snapinQualifiedNameForPattern =305 PSSnapinQualifiedName.GetInstance(pattern);306 307 if (snapinQualifiedNameForPattern == null)308 {309 yield break;310 }311 312 foreach (ProviderInfo providerInfo in _sessionState.Provider.GetAll())313 {314 if (providerInfo.IsMatch(pattern))315 {316 try317 {318 LoadHelpFile(providerInfo);319 }320 catch (IOException ioException)321 {322 if (!decoratedSearch)323 {324 ReportHelpFileError(ioException, providerInfo.Name, providerInfo.HelpFile);325 }326 }327 catch (System.Security.SecurityException securityException)328 {329 if (!decoratedSearch)330 {331 ReportHelpFileError(securityException, providerInfo.Name, providerInfo.HelpFile);332 }333 }334 catch (XmlException xmlException)335 {336 if (!decoratedSearch)337 {338 ReportHelpFileError(xmlException, providerInfo.Name, providerInfo.HelpFile);339 }340 }341 342 HelpInfo helpInfo = GetCache(providerInfo.PSSnapInName + "\\" + providerInfo.Name);343 344 if (helpInfo != null)345 {346 if (searchOnlyContent)347 {348 // ignore help objects that do not have pattern in its help349 // content.350 if (!helpInfo.MatchPatternInContent(wildCardPattern))351 {352 continue;353 }354 }355 356 countOfHelpInfoObjectsFound++;357 yield return helpInfo;358 359 if (countOfHelpInfoObjectsFound >= helpRequest.MaxResults && helpRequest.MaxResults > 0)360 yield break;361 }362 }363 }364 }365 366 internal override IEnumerable<HelpInfo> ProcessForwardedHelp(HelpInfo helpInfo, HelpRequest helpRequest)367 {368 ProviderCommandHelpInfo providerCommandHelpInfo = new ProviderCommandHelpInfo(369 helpInfo, helpRequest.ProviderContext);370 yield return providerCommandHelpInfo;371 }372#if V2373 /// <summary>374 /// Process a helpInfo forwarded from other providers (normally commandHelpProvider)375 /// </summary>376 /// <remarks>377 /// For command help info, this will378 /// 1. check whether provider-specific commandlet help exists.379 /// 2. merge found provider-specific help with commandlet help provided.380 /// </remarks>381 /// <param name="helpInfo">HelpInfo forwarded in.</param>382 /// <param name="helpRequest">Help request object.</param>383 /// <returns>The help info object after processing.</returns>384 override internal HelpInfo ProcessForwardedHelp(HelpInfo helpInfo, HelpRequest helpRequest)385 {386 if (helpInfo == null)387 return null;388 389 if (helpInfo.HelpCategory != HelpCategory.Command)390 {391 return helpInfo;392 }393 394 string providerName = helpRequest.Provider;395 if (string.IsNullOrEmpty(providerName))396 {397 providerName = this._sessionState.Path.CurrentLocation.Provider.Name;398 }399 400 HelpRequest providerHelpRequest = helpRequest.Clone();401 providerHelpRequest.Target = providerName;402 403 ProviderHelpInfo providerHelpInfo = (ProviderHelpInfo)this.ExactMatchHelp(providerHelpRequest);404 405 if (providerHelpInfo == null)406 return null;407 408 CommandHelpInfo commandHelpInfo = (CommandHelpInfo)helpInfo;409 410 CommandHelpInfo result = commandHelpInfo.MergeProviderSpecificHelp(providerHelpInfo.GetCmdletHelp(commandHelpInfo.Name), providerHelpInfo.GetDynamicParameterHelp(helpRequest.DynamicParameters));411 412 // Reset ForwardHelpCategory for the helpinfo to be returned so that it will not be forwarded back again.413 result.ForwardHelpCategory = HelpCategory.None;414 415 return result;416 }417#endif418 419 /// <summary>420 /// This will reset the help cache. Normally this corresponds to a421 /// help culture change.422 /// </summary>423 internal override void Reset()424 {425 base.Reset();426 427 _helpFiles.Clear();428 }429 430 #endregion431 }432}433 