Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
ModuleUtils.cs662 linesDownload Raw Back to Modules
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Globalization;6using System.IO;7using System.Management.Automation.Runspaces;8using System.Text;9 10using Dbg = System.Management.Automation.Diagnostics;11 12namespace System.Management.Automation.Internal13{14    internal static class ModuleUtils15    {16        // These are documented members FILE_ATTRIBUTE, they just have not yet been17        // added to System.IO.FileAttributes yet.18        private const int FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = 0x400000;19 20        private const int FILE_ATTRIBUTE_RECALL_ON_OPEN = 0x40000;21 22        // Default option for local file system enumeration:23        //  - Ignore files/directories when access is denied;24        //  - Search top directory only.25        private static readonly System.IO.EnumerationOptions s_defaultEnumerationOptions =26                                        new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributesToSkip };27 28        private static readonly FileAttributes FileAttributesToSkip;29 30        // Default option for UNC path enumeration. Same as above plus a large buffer size.31        // For network shares, a large buffer may result in better performance as more results can be batched over the wire.32        // The buffer size 16K is recommended in the comment of the 'BufferSize' property:33        //    "A "large" buffer, for example, would be 16K. Typical is 4K."34        private static readonly System.IO.EnumerationOptions s_uncPathEnumerationOptions =35                                        new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributesToSkip, BufferSize = 16384 };36 37        private static readonly string EnCulturePath = Path.DirectorySeparatorChar + "en";38        private static readonly string EnUsCulturePath = Path.DirectorySeparatorChar + "en-us";39 40        static ModuleUtils()41        {42            FileAttributesToSkip = FileAttributes.Hidden43                // Skip OneDrive files/directories that are not fully on disk.44                | FileAttributes.Offline45                | (FileAttributes)FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS46                | (FileAttributes)FILE_ATTRIBUTE_RECALL_ON_OPEN;47        }48 49        /// <summary>50        /// Check if a directory is likely a localized resources folder.51        /// </summary>52        /// <param name="dir">Directory to check if it is a possible resource folder.</param>53        /// <returns>True if the directory name matches a culture.</returns>54        internal static bool IsPossibleResourceDirectory(string dir)55        {56            // Assume locale directories do not contain modules.57            if (dir.EndsWith(EnCulturePath, StringComparison.OrdinalIgnoreCase) ||58                dir.EndsWith(EnUsCulturePath, StringComparison.OrdinalIgnoreCase))59            {60                return true;61            }62 63            dir = Path.GetFileName(dir);64 65            // Use some simple pattern matching to avoid the call into GetCultureInfo when we know it will fail (and throw).66            if ((dir.Length == 2 && char.IsLetter(dir[0]) && char.IsLetter(dir[1]))67                ||68                (dir.Length == 5 && char.IsLetter(dir[0]) && char.IsLetter(dir[1]) && (dir[2] == '-') && char.IsLetter(dir[3]) && char.IsLetter(dir[4])))69            {70                try71                {72                    // This might not throw on invalid culture still73                    // 4096 is considered the unknown locale - so assume that could be a module74                    var cultureInfo = new CultureInfo(dir);75                    return cultureInfo.LCID != 4096;76                }77                catch { }78            }79 80            return false;81        }82 83        /// <summary>84        /// Get all module files by searching the given directory recursively.85        /// All sub-directories that could be a module folder will be searched.86        /// </summary>87        internal static IEnumerable<string> GetAllAvailableModuleFiles(string topDirectoryToCheck)88        {89            if (!Directory.Exists(topDirectoryToCheck)) { yield break; }90 91            var options = Utils.PathIsUnc(topDirectoryToCheck) ? s_uncPathEnumerationOptions : s_defaultEnumerationOptions;92            Queue<string> directoriesToCheck = new Queue<string>();93            directoriesToCheck.Enqueue(topDirectoryToCheck);94 95            bool firstSubDirs = true;96            while (directoriesToCheck.Count > 0)97            {98                string directoryToCheck = directoriesToCheck.Dequeue();99                try100                {101                    foreach (string toAdd in Directory.EnumerateDirectories(directoryToCheck, "*", options))102                    {103                        if (firstSubDirs || !IsPossibleResourceDirectory(toAdd))104                        {105                            directoriesToCheck.Enqueue(toAdd);106                        }107                    }108                }109                catch (IOException) { }110                catch (UnauthorizedAccessException) { }111 112                firstSubDirs = false;113                foreach (string moduleFile in Directory.EnumerateFiles(directoryToCheck, "*", options))114                {115                    foreach (string ext in ModuleIntrinsics.PSModuleExtensions)116                    {117                        if (moduleFile.EndsWith(ext, StringComparison.OrdinalIgnoreCase))118                        {119                            yield return moduleFile;120                            break; // one file can have only one extension121                        }122                    }123                }124            }125        }126 127        /// <summary>128        /// Check if the CompatiblePSEditions field of a given module129        /// declares compatibility with the running PowerShell edition.130        /// </summary>131        /// <param name="moduleManifestPath">The path to the module manifest being checked.</param>132        /// <param name="compatiblePSEditions">The value of the CompatiblePSEditions field of the module manifest.</param>133        /// <returns>True if the module is compatible with the running PowerShell edition, false otherwise.</returns>134        internal static bool IsPSEditionCompatible(135            string moduleManifestPath,136            IEnumerable<string> compatiblePSEditions)137        {138#if UNIX139            return true;140#else141            if (!IsOnSystem32ModulePath(moduleManifestPath))142            {143                return true;144            }145 146            return Utils.IsPSEditionSupported(compatiblePSEditions);147#endif148        }149 150        internal static IEnumerable<string> GetDefaultAvailableModuleFiles(bool isForAutoDiscovery, ExecutionContext context)151        {152            HashSet<string> uniqueModuleFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);153 154            foreach (string directory in ModuleIntrinsics.GetModulePath(isForAutoDiscovery, context))155            {156                var needWriteProgressCompleted = false;157                ProgressRecord analysisProgress = null;158 159                // Write a progress message for UNC paths, so that users know what is happening160                try161                {162                    if ((context.CurrentCommandProcessor != null) && Utils.PathIsUnc(directory))163                    {164                        analysisProgress = new ProgressRecord(0,165                            Modules.DeterminingAvailableModules,166                            string.Format(CultureInfo.InvariantCulture, Modules.SearchingUncShare, directory))167                        {168                            RecordType = ProgressRecordType.Processing169                        };170 171                        context.CurrentCommandProcessor.CommandRuntime.WriteProgress(analysisProgress);172                        needWriteProgressCompleted = true;173                    }174                }175                catch (InvalidOperationException)176                {177                    // This may be called when we are not allowed to write progress,178                    // So eat the invalid operation179                }180 181                try182                {183                    foreach (string moduleFile in ModuleUtils.GetDefaultAvailableModuleFiles(directory))184                    {185                        if (uniqueModuleFiles.Add(moduleFile))186                        {187                            yield return moduleFile;188                        }189                    }190                }191                finally192                {193                    if (needWriteProgressCompleted)194                    {195                        analysisProgress.RecordType = ProgressRecordType.Completed;196                        context.CurrentCommandProcessor.CommandRuntime.WriteProgress(analysisProgress);197                    }198                }199            }200        }201 202        /// <summary>203        /// Get a list of module files from the given directory without recursively searching all sub-directories.204        /// This method assumes the given directory is a module folder or a version sub-directory of a module folder.205        /// </summary>206        internal static List<string> GetModuleFilesFromAbsolutePath(string directory)207        {208            List<string> result = new List<string>();209            string fileName = Path.GetFileName(directory);210 211            // If the given directory doesn't exist or it's the root folder, then return an empty list.212            if (!Directory.Exists(directory) || string.IsNullOrEmpty(fileName)) { return result; }213 214            // If the user give the module path including version, the module name could be the parent folder name.215            if (Version.TryParse(fileName, out Version ver))216            {217                string parentDirPath = Path.GetDirectoryName(directory);218                string parentDirName = Path.GetFileName(parentDirPath);219 220                // If the parent directory is NOT a root folder, then it could be the module folder.221                if (!string.IsNullOrEmpty(parentDirName))222                {223                    string manifestPath = Path.Combine(directory, parentDirName);224                    manifestPath += StringLiterals.PowerShellDataFileExtension;225                    if (File.Exists(manifestPath) && ver.Equals(ModuleIntrinsics.GetManifestModuleVersion(manifestPath)))226                    {227                        result.Add(manifestPath);228                        return result;229                    }230                }231            }232 233            // If we reach here, then use the given directory as the module folder.234            foreach (Version version in GetModuleVersionSubfolders(directory))235            {236                string manifestPath = Path.Combine(directory, version.ToString(), fileName);237                manifestPath += StringLiterals.PowerShellDataFileExtension;238                if (File.Exists(manifestPath) && version.Equals(ModuleIntrinsics.GetManifestModuleVersion(manifestPath)))239                {240                    result.Add(manifestPath);241                }242            }243 244            foreach (string ext in ModuleIntrinsics.PSModuleExtensions)245            {246                string moduleFile = Path.Combine(directory, fileName) + ext;247                if (File.Exists(moduleFile))248                {249                    result.Add(moduleFile);250 251                    // when finding the default modules we stop when the first252                    // match is hit - searching in order .psd1, .psm1, .dll,253                    // if a file is found but is not readable then it is an error.254                    break;255                }256            }257 258            return result;259        }260 261        /// <summary>262        /// Get a list of the available module files from the given directory.263        /// Search all module folders under the specified directory, but do not search sub-directories under a module folder.264        /// </summary>265        internal static IEnumerable<string> GetDefaultAvailableModuleFiles(string topDirectoryToCheck)266        {267            if (!Directory.Exists(topDirectoryToCheck)) { yield break; }268 269            var options = Utils.PathIsUnc(topDirectoryToCheck) ? s_uncPathEnumerationOptions : s_defaultEnumerationOptions;270            List<Version> versionDirectories = new List<Version>();271            LinkedList<string> directoriesToCheck = new LinkedList<string>();272            directoriesToCheck.AddLast(topDirectoryToCheck);273 274            while (directoriesToCheck.Count > 0)275            {276                versionDirectories.Clear();277                string[] subdirectories;278                string directoryToCheck = directoriesToCheck.First.Value;279                directoriesToCheck.RemoveFirst();280                try281                {282                    subdirectories = Directory.GetDirectories(directoryToCheck, "*", options);283                    ProcessPossibleVersionSubdirectories(subdirectories, versionDirectories);284                }285                catch (IOException) { subdirectories = Array.Empty<string>(); }286                catch (UnauthorizedAccessException) { subdirectories = Array.Empty<string>(); }287 288                bool isModuleDirectory = false;289                string proposedModuleName = Path.GetFileName(directoryToCheck);290                foreach (Version version in versionDirectories)291                {292                    string manifestPath = Path.Combine(directoryToCheck, version.ToString(), proposedModuleName);293                    manifestPath += StringLiterals.PowerShellDataFileExtension;294                    if (File.Exists(manifestPath))295                    {296                        if (HasSkippedFileAttribute(manifestPath))297                        {298                            continue;299                        }300 301                        isModuleDirectory = true;302                        yield return manifestPath;303                    }304                }305 306                if (!isModuleDirectory)307                {308                    foreach (string ext in ModuleIntrinsics.PSModuleExtensions)309                    {310                        string moduleFile = Path.Combine(directoryToCheck, proposedModuleName) + ext;311                        if (File.Exists(moduleFile))312                        {313                            if (HasSkippedFileAttribute(moduleFile))314                            {315                                continue;316                            }317 318                            isModuleDirectory = true;319                            yield return moduleFile;320 321                            // when finding the default modules we stop when the first322                            // match is hit - searching in order .psd1, .psm1, .dll, .exe323                            // if a file is found but is not readable then it is an324                            // error325                            break;326                        }327                    }328                }329 330                if (!isModuleDirectory)331                {332                    foreach (var subdirectory in subdirectories)333                    {334                        if (subdirectory.EndsWith("Microsoft.PowerShell.Management", StringComparison.OrdinalIgnoreCase) ||335                            subdirectory.EndsWith("Microsoft.PowerShell.Utility", StringComparison.OrdinalIgnoreCase))336                        {337                            directoriesToCheck.AddFirst(subdirectory);338                        }339                        else340                        {341                            directoriesToCheck.AddLast(subdirectory);342                        }343                    }344                }345            }346        }347 348        /// <summary>349        /// Gets the list of versions under the specified module base path in descending sorted order.350        /// </summary>351        /// <param name="moduleBase">Module base path.</param>352        /// <returns>Sorted list of versions.</returns>353        internal static List<Version> GetModuleVersionSubfolders(string moduleBase)354        {355            var versionFolders = new List<Version>();356 357            if (!string.IsNullOrWhiteSpace(moduleBase) && Directory.Exists(moduleBase))358            {359                var options = Utils.PathIsUnc(moduleBase) ? s_uncPathEnumerationOptions : s_defaultEnumerationOptions;360                IEnumerable<string> subdirectories = Directory.EnumerateDirectories(moduleBase, "*", options);361                ProcessPossibleVersionSubdirectories(subdirectories, versionFolders);362            }363 364            return versionFolders;365        }366 367        private static bool HasSkippedFileAttribute(string path)368        {369            try370            {371                FileAttributes attributes = File.GetAttributes(path);372                if ((attributes & FileAttributesToSkip) is not 0)373                {374                    return true;375                }376            }377            catch378            {379                // Ignore failures so that we keep the current behavior of failing380                // later in the search.381            }382 383            return false;384        }385 386        private static void ProcessPossibleVersionSubdirectories(IEnumerable<string> subdirectories, List<Version> versionFolders)387        {388            foreach (string subdir in subdirectories)389            {390                string subdirName = Path.GetFileName(subdir);391                if (Version.TryParse(subdirName, out Version version))392                {393                    versionFolders.Add(version);394                }395            }396 397            if (versionFolders.Count > 1)398            {399                versionFolders.Sort(static (x, y) => y.CompareTo(x));400            }401        }402 403        internal static bool IsModuleInVersionSubdirectory(string modulePath, out Version version)404        {405            version = null;406            string folderName = Path.GetDirectoryName(modulePath);407            if (folderName != null)408            {409                folderName = Path.GetFileName(folderName);410                return Version.TryParse(folderName, out version);411            }412 413            return false;414        }415 416        internal static bool IsOnSystem32ModulePath(string path)417        {418#if UNIX419            return false;420#else421            Dbg.Assert(!string.IsNullOrEmpty(path), $"Caller to verify that {nameof(path)} is not null or empty");422 423            string windowsPowerShellPSHomePath = ModuleIntrinsics.GetWindowsPowerShellPSHomeModulePath();424            return path.StartsWith(windowsPowerShellPSHomePath, StringComparison.OrdinalIgnoreCase);425#endif426        }427 428        /// <summary>429        /// Gets a list of fuzzy matching commands and their scores.430        /// </summary>431        /// <param name="pattern">Command pattern.</param>432        /// <param name="context">Execution context.</param>433        /// <param name="commandOrigin">Command origin.</param>434        /// <param name="fuzzyMatcher">Fuzzy matcher to use.</param>435        /// <param name="rediscoverImportedModules">If true, rediscovers imported modules.</param>436        /// <param name="moduleVersionRequired">Specific module version to be required.</param>437        /// <returns>IEnumerable tuple containing the CommandInfo and the match score.</returns>438        internal static IEnumerable<CommandScore> GetFuzzyMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, FuzzyMatcher fuzzyMatcher, bool rediscoverImportedModules = false, bool moduleVersionRequired = false)439        {440            foreach (CommandInfo command in GetMatchingCommands(pattern, context, commandOrigin, rediscoverImportedModules, moduleVersionRequired, fuzzyMatcher: fuzzyMatcher))441            {442                if (fuzzyMatcher.IsFuzzyMatch(command.Name, pattern, out int score))443                {444                    yield return new CommandScore(command, score);445                }446            }447        }448 449        /// <summary>450        /// Gets a list of matching commands.451        /// </summary>452        /// <param name="pattern">Command pattern.</param>453        /// <param name="context">Execution context.</param>454        /// <param name="commandOrigin">Command origin.</param>455        /// <param name="rediscoverImportedModules">If true, rediscovers imported modules.</param>456        /// <param name="moduleVersionRequired">Specific module version to be required.</param>457        /// <param name="fuzzyMatcher">Fuzzy matcher for fuzzy searching.</param>458        /// <param name="useAbbreviationExpansion">Use abbreviation expansion for matching.</param>459        /// <returns>Returns matching CommandInfo IEnumerable.</returns>460        internal static IEnumerable<CommandInfo> GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false, FuzzyMatcher fuzzyMatcher = null, bool useAbbreviationExpansion = false)461        {462            // Otherwise, if it had wildcards, just return the "AvailableCommand"463            // type of command info.464            WildcardPattern commandPattern = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase);465 466            CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Get-Module");467            PSModuleAutoLoadingPreference moduleAutoLoadingPreference = CommandDiscovery.GetCommandDiscoveryPreference(context, SpecialVariables.PSModuleAutoLoadingPreferenceVarPath, "PSModuleAutoLoadingPreference");468 469            if ((moduleAutoLoadingPreference != PSModuleAutoLoadingPreference.None) &&470                ((commandOrigin == CommandOrigin.Internal) || ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public))))471            {472                foreach (string modulePath in GetDefaultAvailableModuleFiles(isForAutoDiscovery: false, context))473                {474                    // Skip modules that have already been loaded so that we don't expose private commands.475                    string moduleName = Path.GetFileNameWithoutExtension(modulePath);476                    List<PSModuleInfo> modules = context.Modules.GetExactMatchModules(moduleName, all: false, exactMatch: true);477                    PSModuleInfo tempModuleInfo = null;478 479                    if (modules.Count != 0)480                    {481                        // 1. We continue to the next module path if we don't want to re-discover those imported modules482                        // 2. If we want to re-discover the imported modules, but one or more commands from the module were made private,483                        //    then we don't do re-discovery484                        if (!rediscoverImportedModules || modules.Exists(static module => module.ModuleHasPrivateMembers))485                        {486                            continue;487                        }488 489                        if (modules.Count == 1)490                        {491                            PSModuleInfo psModule = modules[0];492                            tempModuleInfo = new PSModuleInfo(psModule.Name, psModule.Path, context: null, sessionState: null);493                            tempModuleInfo.SetModuleBase(psModule.ModuleBase);494 495                            foreach (KeyValuePair<string, CommandInfo> entry in psModule.ExportedCommands)496                            {497                                if (commandPattern.IsMatch(entry.Value.Name) ||498                                    (fuzzyMatcher is not null && fuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)) ||499                                    (useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(entry.Value.Name), StringComparison.OrdinalIgnoreCase)))500                                {501                                    CommandInfo current = null;502                                    switch (entry.Value.CommandType)503                                    {504                                        case CommandTypes.Alias:505                                            current = new AliasInfo(entry.Value.Name, definition: null, context);506                                            break;507                                        case CommandTypes.Function:508                                            current = new FunctionInfo(entry.Value.Name, ScriptBlock.EmptyScriptBlock, context);509                                            break;510                                        case CommandTypes.Filter:511                                            current = new FilterInfo(entry.Value.Name, ScriptBlock.EmptyScriptBlock, context);512                                            break;513                                        case CommandTypes.Configuration:514                                            current = new ConfigurationInfo(entry.Value.Name, ScriptBlock.EmptyScriptBlock, context);515                                            break;516                                        case CommandTypes.Cmdlet:517                                            current = new CmdletInfo(entry.Value.Name, implementingType: null, helpFile: null, PSSnapin: null, context);518                                            break;519                                        default:520                                            Dbg.Assert(false, "cannot be hit");521                                            break;522                                    }523 524                                    current.Module = tempModuleInfo;525                                    yield return current;526                                }527                            }528 529                            continue;530                        }531                    }532 533                    string moduleShortName = Path.GetFileNameWithoutExtension(modulePath);534 535                    IDictionary<string, CommandTypes> exportedCommands = AnalysisCache.GetExportedCommands(modulePath, testOnly: false, context);536 537                    if (exportedCommands == null) { continue; }538 539                    tempModuleInfo = new PSModuleInfo(moduleShortName, modulePath, sessionState: null, context: null);540                    if (InitialSessionState.IsEngineModule(moduleShortName))541                    {542                        tempModuleInfo.SetModuleBase(Utils.DefaultPowerShellAppBase);543                    }544 545                    // moduleVersionRequired is bypassed by FullyQualifiedModule from calling method. This is the only place where guid will be involved.546                    if (moduleVersionRequired && modulePath.EndsWith(StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase))547                    {548                        tempModuleInfo.SetVersion(ModuleIntrinsics.GetManifestModuleVersion(modulePath));549                        tempModuleInfo.SetGuid(ModuleIntrinsics.GetManifestGuid(modulePath));550                    }551 552                    foreach (KeyValuePair<string, CommandTypes> pair in exportedCommands)553                    {554                        string commandName = pair.Key;555                        CommandTypes commandTypes = pair.Value;556 557                        if (commandPattern.IsMatch(commandName) ||558                            (fuzzyMatcher is not null && fuzzyMatcher.IsFuzzyMatch(commandName, pattern)) ||559                            (useAbbreviationExpansion && string.Equals(pattern, AbbreviateName(commandName), StringComparison.OrdinalIgnoreCase)))560                        {561                            bool shouldExportCommand = true;562 563                            // Verify that we don't already have it represented in the initial session state.564                            if ((context.InitialSessionState != null) && (commandOrigin == CommandOrigin.Runspace))565                            {566                                foreach (SessionStateCommandEntry commandEntry in context.InitialSessionState.Commands[commandName])567                                {568                                    string moduleCompareName = null;569 570                                    if (commandEntry.Module != null)571                                    {572                                        moduleCompareName = commandEntry.Module.Name;573                                    }574                                    else if (commandEntry.PSSnapIn != null)575                                    {576                                        moduleCompareName = commandEntry.PSSnapIn.Name;577                                    }578 579                                    if (string.Equals(moduleShortName, moduleCompareName, StringComparison.OrdinalIgnoreCase))580                                    {581                                        if (commandEntry.Visibility == SessionStateEntryVisibility.Private)582                                        {583                                            shouldExportCommand = false;584                                        }585                                    }586                                }587                            }588 589                            if (shouldExportCommand)590                            {591                                if ((commandTypes & CommandTypes.Alias) == CommandTypes.Alias)592                                {593                                    yield return new AliasInfo(commandName, null, context)594                                    {595                                        Module = tempModuleInfo596                                    };597                                }598 599                                if ((commandTypes & CommandTypes.Cmdlet) == CommandTypes.Cmdlet)600                                {601                                    yield return new CmdletInfo(commandName, implementingType: null, helpFile: null, PSSnapin: null, context: context)602                                    {603                                        Module = tempModuleInfo604                                    };605                                }606 607                                if ((commandTypes & CommandTypes.Function) == CommandTypes.Function)608                                {609                                    yield return new FunctionInfo(commandName, ScriptBlock.EmptyScriptBlock, context)610                                    {611                                        Module = tempModuleInfo612                                    };613                                }614 615                                if ((commandTypes & CommandTypes.Configuration) == CommandTypes.Configuration)616                                {617                                    yield return new ConfigurationInfo(commandName, ScriptBlock.EmptyScriptBlock, context)618                                    {619                                        Module = tempModuleInfo620                                    };621                                }622                            }623                        }624                    }625                }626            }627        }628 629        /// <summary>630        /// Returns abbreviated version of a command name.631        /// </summary>632        /// <param name="commandName">Name of the command to transform.</param>633        /// <returns>Abbreviated version of the command name.</returns>634        internal static string AbbreviateName(string commandName)635        {636            // Use default size of 6 which represents expected average abbreviation length637            StringBuilder abbreviation = new StringBuilder(6);638            foreach (char c in commandName)639            {640                if (char.IsUpper(c) || c == '-')641                {642                    abbreviation.Append(c);643                }644            }645 646            return abbreviation.ToString();647        }648    }649 650    internal struct CommandScore651    {652        public CommandScore(CommandInfo command, int score)653        {654            Command = command;655            Score = score;656        }657 658        public CommandInfo Command;659        public int Score;660    }661}662