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.Text.RegularExpressions;10 11namespace System.Management.Automation12{13 internal sealed class MUIFileSearcher14 {15 /// <summary>16 /// Constructor. It is private so that MUIFileSearcher is used only internal for this class.17 /// To access functionality in this class, static api should be used.18 /// </summary>19 /// <param name="target"></param>20 /// <param name="searchPaths"></param>21 /// <param name="searchMode"></param>22 private MUIFileSearcher(string target, Collection<string> searchPaths, SearchMode searchMode)23 {24 Target = target;25 SearchPaths = searchPaths;26 SearchMode = searchMode;27 }28 29 /// <summary>30 /// A constructor to make searchMode optional.31 /// </summary>32 /// <param name="target"></param>33 /// <param name="searchPaths"></param>34 private MUIFileSearcher(string target, Collection<string> searchPaths)35 : this(target, searchPaths, SearchMode.Unique)36 {37 }38 39 #region Basic Properties40 41 /// <summary>42 /// Search target. It can be43 /// 1. a file name44 /// 2. a search pattern45 /// It can also include a path, in that case,46 /// 1. the path will be searched first for the existence of the files.47 /// </summary>48 internal string Target { get; } = null;49 50 /// <summary>51 /// Search path as provided by user.52 /// </summary>53 internal Collection<string> SearchPaths { get; } = null;54 55 /// <summary>56 /// Search mode for this file search.57 /// </summary>58 internal SearchMode SearchMode { get; } = SearchMode.Unique;59 60 private static readonly System.IO.EnumerationOptions _enumerationOptions = new()61 {62 IgnoreInaccessible = false,63 AttributesToSkip = 0,64 MatchType = MatchType.Win32,65 MatchCasing = MatchCasing.CaseInsensitive,66 };67 68 private Collection<string> _result = null;69 70 /// <summary>71 /// Result of the search.72 /// </summary>73 internal Collection<string> Result74 {75 get76 {77 if (_result == null)78 {79 _result = new Collection<string>();80 81 // SearchForFiles will fill the result collection.82 SearchForFiles();83 }84 85 return _result;86 }87 }88 89 #endregion90 91 #region File Search92 93 /// <summary>94 /// _uniqueMatches is used to track matches already found during the search process.95 /// This is useful for ignoring duplicates in the case of unique search.96 /// </summary>97 private readonly Hashtable _uniqueMatches = new Hashtable(StringComparer.OrdinalIgnoreCase);98 99 /// <summary>100 /// Search for files using the target, searchPaths member of this class.101 /// </summary>102 private void SearchForFiles()103 {104 if (string.IsNullOrEmpty(this.Target))105 return;106 107 string pattern = Path.GetFileName(this.Target);108 if (string.IsNullOrEmpty(pattern))109 return;110 111 Collection<string> normalizedSearchPaths = NormalizeSearchPaths(this.Target, this.SearchPaths);112 113 foreach (string directory in normalizedSearchPaths)114 {115 SearchForFiles(pattern, directory);116 117 if (this.SearchMode == SearchMode.First && this.Result.Count > 0)118 {119 return;120 }121 }122 }123 124 private void AddFiles(string muiDirectory, string directory, string pattern)125 {126 if (Directory.Exists(muiDirectory))127 {128 foreach (string file in Directory.EnumerateFiles(muiDirectory, pattern, _enumerationOptions))129 {130 string path = Path.Combine(muiDirectory, file);131 132 switch (this.SearchMode)133 {134 case SearchMode.All:135 _result.Add(path);136 break;137 138 case SearchMode.Unique:139 // Construct a Unique filename for this directory.140 // Remember the file may belong to one of the sub-culture141 // directories. In this case we should not be returning142 // same files that are residing in 2 or more sub-culture143 // directories.144 string leafFileName = Path.GetFileName(file);145 string uniqueToDirectory = Path.Combine(directory, leafFileName);146 147 if (!_result.Contains(path) && !_uniqueMatches.Contains(uniqueToDirectory))148 {149 _result.Add(path);150 _uniqueMatches[uniqueToDirectory] = true;151 }152 153 break;154 155 case SearchMode.First:156 _result.Add(path);157 return;158 159 default:160 break;161 }162 }163 }164 }165 166 /// <summary>167 /// Search for files of a particular pattern under a particular directory.168 /// This will do MUI search in which appropriate language directories are169 /// searched in order.170 /// </summary>171 /// <param name="pattern"></param>172 /// <param name="directory"></param>173 private void SearchForFiles(string pattern, string directory)174 {175 List<string> cultureNameList = new List<string>();176 CultureInfo culture = CultureInfo.CurrentUICulture;177 178 while (culture != null && !string.IsNullOrEmpty(culture.Name))179 {180 cultureNameList.Add(culture.Name);181 culture = culture.Parent;182 }183 184 cultureNameList.Add(string.Empty);185 186 // Add en-US and en as fallback languages187 if (!cultureNameList.Contains("en-US"))188 {189 cultureNameList.Add("en-US");190 }191 192 if (!cultureNameList.Contains("en"))193 {194 cultureNameList.Add("en");195 }196 197 foreach (string name in cultureNameList)198 {199 string muiDirectory = Path.Combine(directory, name);200 201 AddFiles(muiDirectory, directory, pattern);202 203 if (this.SearchMode == SearchMode.First && this.Result.Count > 0)204 {205 return;206 }207 }208 209 return;210 }211 212 /// <summary>213 /// A help file is located in 3 steps214 /// 1. If file itself contains a path itself, try to locate the file215 /// from path. LocateFile will fail if this file doesn't exist.216 /// 2. Try to locate the file from searchPaths. Normally the searchPaths will217 /// contain the cmdlet/provider assembly directory if currently we are searching218 /// help for cmdlet and providers.219 /// 3. Try to locate the file in the default PowerShell installation directory.220 /// </summary>221 /// <param name="target"></param>222 /// <param name="searchPaths"></param>223 /// <returns></returns>224 private static Collection<string> NormalizeSearchPaths(string target, Collection<string> searchPaths)225 {226 Collection<string> result = new Collection<string>();227 228 // step 1: if target has path attached, directly locate229 // file from there.230 if (!string.IsNullOrEmpty(target) && !string.IsNullOrEmpty(Path.GetDirectoryName(target)))231 {232 string directory = Path.GetDirectoryName(target);233 234 if (Directory.Exists(directory))235 {236 result.Add(Path.GetFullPath(directory));237 }238 239 // user specifically wanted to search in a particular directory240 // so return..241 return result;242 }243 244 // step 2: add directories specified in to search path.245 if (searchPaths != null)246 {247 foreach (string directory in searchPaths)248 {249 if (!result.Contains(directory) && Directory.Exists(directory))250 {251 result.Add(directory);252 }253 }254 }255 256 // step 3: locate the file in the default PowerShell installation directory.257 string defaultPSPath = Utils.GetApplicationBase(Utils.DefaultPowerShellShellID);258 if (defaultPSPath != null &&259 !result.Contains(defaultPSPath) &&260 Directory.Exists(defaultPSPath))261 {262 result.Add(defaultPSPath);263 }264 265 return result;266 }267 268 #endregion269 270 #region Static API's271 272 /// <summary>273 /// Search for files in default search paths.274 /// </summary>275 /// <param name="pattern"></param>276 /// <returns></returns>277 internal static Collection<string> SearchFiles(string pattern)278 {279 return SearchFiles(pattern, new Collection<string>());280 }281 282 /// <summary>283 /// Search for files in specified search paths.284 /// </summary>285 /// <param name="pattern"></param>286 /// <param name="searchPaths"></param>287 /// <returns></returns>288 internal static Collection<string> SearchFiles(string pattern, Collection<string> searchPaths)289 {290 MUIFileSearcher searcher = new MUIFileSearcher(pattern, searchPaths);291 292 return searcher.Result;293 }294 295 /// <summary>296 /// Locate a file in default search paths.297 /// </summary>298 /// <param name="file"></param>299 /// <returns></returns>300 internal static string LocateFile(string file)301 {302 return LocateFile(file, new Collection<string>());303 }304 305 /// <summary>306 /// Get the file in different search paths corresponding to current culture.307 ///308 /// The file name to search is the filename part of path parameter. (Normally path309 /// parameter should contain only the filename part).310 /// </summary>311 /// <param name="file">This is the path to the file. If it has a path, we need to search under that path first.</param>312 /// <param name="searchPaths">Additional search paths.</param>313 /// <returns></returns>314 internal static string LocateFile(string file, Collection<string> searchPaths)315 {316 MUIFileSearcher searcher = new MUIFileSearcher(file, searchPaths, SearchMode.First);317 318 if (searcher.Result == null || searcher.Result.Count == 0)319 return null;320 321 return searcher.Result[0];322 }323 324 #endregion325 }326 327 /// <summary>328 /// This enum defines different search mode for the MUIFileSearcher.329 /// </summary>330 internal enum SearchMode331 {332 // return the first match333 First,334 335 // return all matches, with duplicates allowed336 All,337 338 // return all matches, with duplicates ignored339 Unique340 }341}342 