Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
UpdatableHelpCommandBase.cs917 linesDownload Raw Back to help
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics;8using System.Diagnostics.CodeAnalysis;9using System.Globalization;10using System.IO;11using System.Linq;12using System.Management.Automation;13using System.Management.Automation.Help;14using System.Management.Automation.Internal;15using System.Management.Automation.Runspaces;16using System.Management.Automation.Tracing;17using System.Net;18 19namespace Microsoft.PowerShell.Commands20{21    /// <summary>22    /// The base class of all updatable help system cmdlets (Update-Help, Save-Help)23    /// </summary>24    public class UpdatableHelpCommandBase : PSCmdlet25    {26        internal const string PathParameterSetName = "Path";27        internal const string LiteralPathParameterSetName = "LiteralPath";28 29        internal UpdatableHelpCommandType _commandType;30        internal UpdatableHelpSystem _helpSystem;31        internal bool _stopping;32 33        internal int activityId;34        private readonly Dictionary<string, UpdatableHelpExceptionContext> _exceptions;35 36        #region Parameters37 38        /// <summary>39        /// Specifies the languages to update.40        /// </summary>41        [Parameter(Position = 2)]42        [ValidateNotNull]43        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]44        public CultureInfo[] UICulture45        {46            get47            {48                CultureInfo[] result = null;49                if (_language != null)50                {51                    result = new CultureInfo[_language.Length];52                    for (int index = 0; index < _language.Length; index++)53                    {54                        result[index] = new CultureInfo(_language[index]);55                    }56                }57 58                return result;59            }60 61            set62            {63                if (value == null)64                {65                    return;66                }67 68                _language = new string[value.Length];69                for (int index = 0; index < value.Length; index++)70                {71                    _language[index] = value[index].Name;72                }73            }74        }75 76        internal string[] _language;77 78        /// <summary>79        /// Gets or sets the credential parameter.80        /// </summary>81        [Parameter]82        [Credential]83        public PSCredential Credential84        {85            get { return _credential; }86 87            set { _credential = value; }88        }89 90        internal PSCredential _credential;91 92        /// <summary>93        /// Directs System.Net.WebClient whether or not to use default credentials.94        /// </summary>95        [Parameter]96        public SwitchParameter UseDefaultCredentials97        {98            get99            {100                return _useDefaultCredentials;101            }102 103            set104            {105                _useDefaultCredentials = value;106            }107        }108 109        private bool _useDefaultCredentials = false;110 111        /// <summary>112        /// Forces the operation to complete.113        /// </summary>114        [Parameter]115        public SwitchParameter Force116        {117            get118            {119                return _force;120            }121 122            set123            {124                _force = value;125            }126        }127 128        internal bool _force;129 130        /// <summary>131        /// Sets the scope to which help is saved.132        /// </summary>133        [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)]134        public UpdateHelpScope Scope135        {136            get;137            set;138        }139 140        #endregion141 142        #region Events143 144        /// <summary>145        /// Handles help system progress events.146        /// </summary>147        /// <param name="sender">Event sender.</param>148        /// <param name="e">Event arguments.</param>149        private void HandleProgressChanged(object sender, UpdatableHelpProgressEventArgs e)150        {151            Debug.Assert(e.CommandType == UpdatableHelpCommandType.UpdateHelpCommand152                || e.CommandType == UpdatableHelpCommandType.SaveHelpCommand);153 154            string activity = (e.CommandType == UpdatableHelpCommandType.UpdateHelpCommand) ?155                HelpDisplayStrings.UpdateProgressActivityForModule : HelpDisplayStrings.SaveProgressActivityForModule;156 157            ProgressRecord progress = new ProgressRecord(activityId, StringUtil.Format(activity, e.ModuleName), e.ProgressStatus);158 159            progress.PercentComplete = e.ProgressPercent;160 161            WriteProgress(progress);162        }163 164        #endregion165 166        #region Constructor167 168        private static readonly Dictionary<string, string> s_metadataCache;169 170        /// <summary>171        /// Static constructor172        ///173        /// NOTE: HelpInfoUri for core PowerShell modules are needed since they get loaded as snapins in a Remoting Endpoint.174        /// When we moved to modules in V3, we were not able to make this change as it was a risky change to make at that time.175        /// </summary>176        static UpdatableHelpCommandBase()177        {178            s_metadataCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);179 180            // NOTE: The HelpInfoUri must be updated with each release.181 182            s_metadataCache.Add("Microsoft.PowerShell.Diagnostics", "https://aka.ms/powershell75-help");183            s_metadataCache.Add("Microsoft.PowerShell.Core", "https://aka.ms/powershell75-help");184            s_metadataCache.Add("Microsoft.PowerShell.Utility", "https://aka.ms/powershell75-help");185            s_metadataCache.Add("Microsoft.PowerShell.Host", "https://aka.ms/powershell75-help");186            s_metadataCache.Add("Microsoft.PowerShell.Management", "https://aka.ms/powershell75-help");187            s_metadataCache.Add("Microsoft.PowerShell.Security", "https://aka.ms/powershell75-help");188            s_metadataCache.Add("Microsoft.WSMan.Management", "https://aka.ms/powershell75-help");189        }190 191        /// <summary>192        /// Checks if a module is a system module, a module is a system module193        /// if it exists in the metadata cache.194        /// </summary>195        /// <param name="module">Module name.</param>196        /// <returns>True if system module, false if not.</returns>197        internal static bool IsSystemModule(string module)198        {199            return s_metadataCache.ContainsKey(module);200        }201 202        /// <summary>203        /// Class constructor.204        /// </summary>205        /// <param name="commandType">Command type.</param>206        internal UpdatableHelpCommandBase(UpdatableHelpCommandType commandType)207        {208            _commandType = commandType;209            _helpSystem = new UpdatableHelpSystem(this, _useDefaultCredentials);210            _exceptions = new Dictionary<string, UpdatableHelpExceptionContext>();211            _helpSystem.OnProgressChanged += HandleProgressChanged;212 213            activityId = Random.Shared.Next();214        }215 216        #endregion217 218        #region Implementation219 220        private void ProcessSingleModuleObject(PSModuleInfo module, ExecutionContext context, Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo> helpModules, bool noErrors)221        {222            if (InitialSessionState.IsEngineModule(module.Name) && !InitialSessionState.IsNestedEngineModule(module.Name))223            {224                WriteDebug(StringUtil.Format("Found engine module: {0}, {1}.", module.Name, module.Guid));225 226                var keyTuple = new Tuple<string, Version>(module.Name, module.Version);227                if (!helpModules.ContainsKey(keyTuple))228                {229                    helpModules.Add(keyTuple, new UpdatableHelpModuleInfo(module.Name, module.Guid,230                        Utils.GetApplicationBase(context.ShellID), s_metadataCache[module.Name]));231                }232 233                return;234            }235            else if (InitialSessionState.IsNestedEngineModule(module.Name))236            {237                return;238            }239 240            if (string.IsNullOrEmpty(module.HelpInfoUri))241            {242                if (!noErrors)243                {244                    ProcessException(module.Name, null, new UpdatableHelpSystemException(245                        "HelpInfoUriNotFound", StringUtil.Format(HelpDisplayStrings.HelpInfoUriNotFound),246                        ErrorCategory.NotSpecified, new Uri("HelpInfoUri", UriKind.Relative), null));247                }248 249                return;250            }251 252            if (!(module.HelpInfoUri.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || module.HelpInfoUri.StartsWith("https://", StringComparison.OrdinalIgnoreCase)))253            {254                if (!noErrors)255                {256                    ProcessException(module.Name, null, new UpdatableHelpSystemException(257                        "InvalidHelpInfoUriFormat", StringUtil.Format(HelpDisplayStrings.InvalidHelpInfoUriFormat, module.HelpInfoUri),258                        ErrorCategory.NotSpecified, new Uri("HelpInfoUri", UriKind.Relative), null));259                }260 261                return;262            }263 264            var keyTuple2 = new Tuple<string, Version>(module.Name, module.Version);265            if (!helpModules.ContainsKey(keyTuple2))266            {267                helpModules.Add(keyTuple2, new UpdatableHelpModuleInfo(module.Name, module.Guid, module.ModuleBase, module.HelpInfoUri));268            }269        }270 271        /// <summary>272        /// Gets a list of modules from the given pattern.273        /// </summary>274        /// <param name="context">Execution context.</param>275        /// <param name="pattern">Pattern to search.</param>276        /// <param name="fullyQualifiedName">Module Specification.</param>277        /// <param name="noErrors">Do not generate errors for modules without HelpInfoUri.</param>278        /// <returns>A list of modules.</returns>279        private Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo> GetModuleInfo(ExecutionContext context, string pattern, ModuleSpecification fullyQualifiedName, bool noErrors)280        {281            List<PSModuleInfo> modules = null;282            string moduleNamePattern = null;283 284            if (pattern != null)285            {286                moduleNamePattern = pattern;287                modules = Utils.GetModules(pattern, context);288            }289            else if (fullyQualifiedName != null)290            {291                moduleNamePattern = fullyQualifiedName.Name;292                modules = Utils.GetModules(fullyQualifiedName, context);293            }294 295            var helpModules = new Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo>();296            if (modules != null)297            {298                foreach (PSModuleInfo module in modules)299                {300                    ProcessSingleModuleObject(module, context, helpModules, noErrors);301                }302            }303 304            IEnumerable<WildcardPattern> patternList = SessionStateUtilities.CreateWildcardsFromStrings(305                globPatterns: new[] { moduleNamePattern },306                options: WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);307 308            foreach (KeyValuePair<string, string> name in s_metadataCache)309            {310                if (SessionStateUtilities.MatchesAnyWildcardPattern(name.Key, patternList, true))311                {312                    // For core snapin, there are no GUIDs. So, we need to construct the HelpInfo slightly differently313                    if (!name.Key.Equals(InitialSessionState.CoreSnapin, StringComparison.OrdinalIgnoreCase))314                    {315                        var keyTuple = new Tuple<string, Version>(name.Key, new Version("1.0"));316                        if (!helpModules.ContainsKey(keyTuple))317                        {318                            List<PSModuleInfo> availableModules = Utils.GetModules(name.Key, context);319                            if (availableModules != null)320                            {321                                foreach (PSModuleInfo module in availableModules)322                                {323                                    keyTuple = new Tuple<string, Version>(module.Name, module.Version);324                                    if (!helpModules.ContainsKey(keyTuple))325                                    {326                                        WriteDebug(StringUtil.Format("Found engine module: {0}, {1}.", module.Name, module.Guid));327 328                                        helpModules.Add(keyTuple, new UpdatableHelpModuleInfo(module.Name,329                                            module.Guid, Utils.GetApplicationBase(context.ShellID), s_metadataCache[module.Name]));330                                    }331                                }332                            }333                        }334                    }335                    else336                    {337                        var keyTuple2 = new Tuple<string, Version>(name.Key, new Version("1.0"));338                        if (!helpModules.ContainsKey(keyTuple2))339                        {340                            helpModules.Add(keyTuple2,341                                            new UpdatableHelpModuleInfo(name.Key, Guid.Empty,342                                                                        Utils.GetApplicationBase(context.ShellID),343                                                                        name.Value));344                        }345                    }346                }347            }348 349            return helpModules;350        }351 352        /// <summary>353        /// Handles Ctrl+C.354        /// </summary>355        protected override void StopProcessing()356        {357            _stopping = true;358            _helpSystem.CancelDownload();359        }360 361        /// <summary>362        /// End processing.363        /// </summary>364        protected override void EndProcessing()365        {366            foreach (UpdatableHelpExceptionContext exception in _exceptions.Values)367            {368                UpdatableHelpExceptionContext e = exception;369 370                if ((exception.Exception.FullyQualifiedErrorId == "HelpCultureNotSupported") &&371                    ((exception.Cultures != null && exception.Cultures.Count > 1) ||372                    (exception.Modules != null && exception.Modules.Count > 1)))373                {374                    // Win8: 744749 Rewriting the error message only in the case where either375                    // multiple cultures or multiple modules are involved.376                    e = new UpdatableHelpExceptionContext(new UpdatableHelpSystemException(377                        "HelpCultureNotSupported", StringUtil.Format(HelpDisplayStrings.CannotMatchUICulturePattern,378                        string.Join(", ", exception.Cultures)),379                        ErrorCategory.InvalidArgument, exception.Cultures, null));380                    e.Modules = exception.Modules;381                    e.Cultures = exception.Cultures;382                }383 384                WriteError(e.CreateErrorRecord(_commandType));385 386                LogContext context = MshLog.GetLogContext(Context, MyInvocation);387 388                context.Severity = "Error";389 390                PSEtwLog.LogOperationalError(PSEventId.Pipeline_Detail, PSOpcode.Exception, PSTask.ExecutePipeline,391                    context, e.GetExceptionMessage(_commandType));392            }393        }394 395        /// <summary>396        /// Main cmdlet logic for processing module names or fully qualified module names.397        /// </summary>398        /// <param name="moduleNames">Module names given by the user.</param>399        /// <param name="fullyQualifiedNames">FullyQualifiedNames.</param>400        internal void Process(IEnumerable<string> moduleNames, IEnumerable<ModuleSpecification> fullyQualifiedNames)401        {402            _helpSystem.UseDefaultCredentials = _useDefaultCredentials;403 404            if (moduleNames != null)405            {406                foreach (string name in moduleNames)407                {408                    if (_stopping)409                    {410                        break;411                    }412 413                    ProcessModuleWithGlobbing(name);414                }415            }416            else if (fullyQualifiedNames != null)417            {418                foreach (var fullyQualifiedName in fullyQualifiedNames)419                {420                    if (_stopping)421                    {422                        break;423                    }424 425                    ProcessModuleWithGlobbing(fullyQualifiedName);426                }427            }428            else429            {430                foreach (KeyValuePair<Tuple<string, Version>, UpdatableHelpModuleInfo> module in GetModuleInfo("*", null, true))431                {432                    if (_stopping)433                    {434                        break;435                    }436 437                    ProcessModule(module.Value);438                }439            }440        }441 442        /// <summary>443        /// Processing module objects for Save-Help.444        /// </summary>445        /// <param name="modules">Module objects given by the user.</param>446        internal void Process(IEnumerable<PSModuleInfo> modules)447        {448            if (modules == null || !modules.Any())449            {450                return;451            }452 453            var helpModules = new Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo>();454 455            foreach (PSModuleInfo module in modules)456            {457                ProcessSingleModuleObject(module, Context, helpModules, false);458            }459 460            foreach (KeyValuePair<Tuple<string, Version>, UpdatableHelpModuleInfo> helpModule in helpModules)461            {462                ProcessModule(helpModule.Value);463            }464        }465 466        /// <summary>467        /// Processes a module with potential globbing.468        /// </summary>469        /// <param name="name">Module name with globbing.</param>470        private void ProcessModuleWithGlobbing(string name)471        {472            if (string.IsNullOrEmpty(name))473            {474                PSArgumentException e = new PSArgumentException(StringUtil.Format(HelpDisplayStrings.ModuleNameNullOrEmpty));475                WriteError(e.ErrorRecord);476                return;477            }478 479            foreach (KeyValuePair<Tuple<string, Version>, UpdatableHelpModuleInfo> module in GetModuleInfo(name, null, false))480            {481                ProcessModule(module.Value);482            }483        }484 485        /// <summary>486        /// Processes a ModuleSpecification with potential globbing.487        /// </summary>488        /// <param name="fullyQualifiedName">ModuleSpecification.</param>489        private void ProcessModuleWithGlobbing(ModuleSpecification fullyQualifiedName)490        {491            foreach (KeyValuePair<Tuple<string, Version>, UpdatableHelpModuleInfo> module in GetModuleInfo(null, fullyQualifiedName, false))492            {493                ProcessModule(module.Value);494            }495        }496 497        /// <summary>498        /// Processes a single module with multiple cultures.499        /// </summary>500        /// <param name="module">Module to process.</param>501        private void ProcessModule(UpdatableHelpModuleInfo module)502        {503            _helpSystem.CurrentModule = module.ModuleName;504 505            if (this is UpdateHelpCommand && !Directory.Exists(module.ModuleBase))506            {507                ProcessException(module.ModuleName, null,508                    new UpdatableHelpSystemException("ModuleBaseMustExist",509                        StringUtil.Format(HelpDisplayStrings.ModuleBaseMustExist),510                        ErrorCategory.InvalidOperation, null, null));511                return;512            }513 514            // Win8: 572882 When the system locale is English and the UI is JPN,515            // running "update-help" still downs English help content.516            var cultures = _language ?? _helpSystem.GetCurrentUICulture();517            UpdatableHelpSystemException implicitCultureNotSupported = null;518 519            foreach (string culture in cultures)520            {521                bool installed = true;522 523                if (_stopping)524                {525                    break;526                }527 528                try529                {530                    ProcessModuleWithCulture(module, culture);531                }532                catch (IOException e)533                {534                    ProcessException(module.ModuleName, culture, new UpdatableHelpSystemException("FailedToCopyFile",535                        e.Message, ErrorCategory.InvalidOperation, null, e));536                }537                catch (UnauthorizedAccessException e)538                {539                    ProcessException(module.ModuleName, culture, new UpdatableHelpSystemException("AccessIsDenied",540                        e.Message, ErrorCategory.PermissionDenied, null, e));541                }542#if !CORECLR543                catch (WebException e)544                {545                    if (e.InnerException != null && e.InnerException is UnauthorizedAccessException)546                    {547                        ProcessException(module.ModuleName, culture, new UpdatableHelpSystemException("AccessIsDenied",548                            e.InnerException.Message, ErrorCategory.PermissionDenied, null, e));549                    }550                    else551                    {552                        ProcessException(module.ModuleName, culture, e);553                    }554                }555#endif556                catch (UpdatableHelpSystemException e)557                {558                    if (e.FullyQualifiedErrorId == "HelpCultureNotSupported"559                            || e.FullyQualifiedErrorId == "UnableToRetrieveHelpInfoXml")560                    {561                        installed = false;562 563                        if (_language != null)564                        {565                            // Display the error message only if we are not using the fallback chain566                            ProcessException(module.ModuleName, culture, e);567                        }568                        else569                        {570                            // Hold first exception, it will be displayed if fallback chain fails571                            WriteVerbose(StringUtil.Format(HelpDisplayStrings.HelpCultureNotSupportedFallback, e.Message));572                            implicitCultureNotSupported ??= e;573                        }574                    }575                    else576                    {577                        ProcessException(module.ModuleName, culture, e);578                    }579                }580                catch (Exception e)581                {582                    ProcessException(module.ModuleName, culture, e);583                }584                finally585                {586                    if (_helpSystem.Errors.Count != 0)587                    {588                        foreach (Exception error in _helpSystem.Errors)589                        {590                            ProcessException(module.ModuleName, culture, error);591                        }592 593                        _helpSystem.Errors.Clear();594                    }595                }596 597                // If -UICulture is not specified, we only install598                // one culture from the fallback chain599                if (_language == null && installed)600                {601                    return;602                }603            }604 605            // If the exception is not null and did not return early, then all of the fallback chain failed606            if (implicitCultureNotSupported != null)607            {608                ProcessException(module.ModuleName, cultures.First(), implicitCultureNotSupported);609            }610        }611 612        /// <summary>613        /// Process a single module with a given culture.614        /// </summary>615        /// <param name="module">Module to process.</param>616        /// <param name="culture">Culture to use.</param>617        /// <returns>True if the module has been processed, false if not.</returns>618        internal virtual bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, string culture)619        {620            return false;621        }622 623        #endregion624 625        #region Common methods626 627        /// <summary>628        /// Gets a list of modules from the given pattern or ModuleSpecification.629        /// </summary>630        /// <param name="pattern">Pattern to match.</param>631        /// <param name="fullyQualifiedName">ModuleSpecification.</param>632        /// <param name="noErrors">Skip errors.</param>633        /// <returns>A list of modules.</returns>634        internal Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo> GetModuleInfo(string pattern, ModuleSpecification fullyQualifiedName, bool noErrors)635        {636            Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo> modules = GetModuleInfo(Context, pattern, fullyQualifiedName, noErrors);637 638            if (modules.Count == 0 && _exceptions.Count == 0 && !noErrors)639            {640                var errorMessage = fullyQualifiedName != null ? StringUtil.Format(HelpDisplayStrings.ModuleNotFoundWithFullyQualifiedName, fullyQualifiedName)641                                                              : StringUtil.Format(HelpDisplayStrings.CannotMatchModulePattern, pattern);642 643                ErrorRecord errorRecord = new ErrorRecord(new Exception(errorMessage),644                    "ModuleNotFound", ErrorCategory.InvalidArgument, pattern);645 646                WriteError(errorRecord);647            }648 649            return modules;650        }651 652        /// <summary>653        /// Checks if it is necessary to update help.654        /// </summary>655        /// <param name="module">ModuleInfo.</param>656        /// <param name="currentHelpInfo">Current HelpInfo.xml.</param>657        /// <param name="newHelpInfo">New HelpInfo.xml.</param>658        /// <param name="culture">Current culture.</param>659        /// <param name="force">Force update.</param>660        /// <returns>True if it is necessary to update help, false if not.</returns>661        internal bool IsUpdateNecessary(UpdatableHelpModuleInfo module, UpdatableHelpInfo currentHelpInfo,662            UpdatableHelpInfo newHelpInfo, CultureInfo culture, bool force)663        {664            Debug.Assert(module != null);665 666            if (newHelpInfo == null)667            {668                throw new UpdatableHelpSystemException("UnableToRetrieveHelpInfoXml",669                    StringUtil.Format(HelpDisplayStrings.UnableToRetrieveHelpInfoXml, culture.Name), ErrorCategory.ResourceUnavailable,670                    null, null);671            }672 673            // Culture check674            if (!newHelpInfo.IsCultureSupported(culture.Name))675            {676                throw new UpdatableHelpSystemException("HelpCultureNotSupported",677                    StringUtil.Format(HelpDisplayStrings.HelpCultureNotSupported,678                    culture.Name, newHelpInfo.GetSupportedCultures()), ErrorCategory.InvalidOperation, null, null);679            }680 681            // Version check682            if (!force && currentHelpInfo != null && !currentHelpInfo.IsNewerVersion(newHelpInfo, culture))683            {684                return false;685            }686 687            return true;688        }689 690        /// <summary>691        /// Checks if the user has attempted to update more than once per day per module.692        /// </summary>693        /// <param name="moduleName">Module name.</param>694        /// <param name="path">Path to help info.</param>695        /// <param name="filename">Help info file name.</param>696        /// <param name="time">Current time (UTC).</param>697        /// <param name="force">If -Force is specified.</param>698        /// <returns>True if we are okay to update, false if not.</returns>699        internal bool CheckOncePerDayPerModule(string moduleName, string path, string filename, DateTime time, bool force)700        {701            // Update if -Force is specified702            if (force)703            {704                return true;705            }706 707            string helpInfoFilePath = SessionState.Path.Combine(path, filename);708 709            // No HelpInfo.xml710            if (!File.Exists(helpInfoFilePath))711            {712                return true;713            }714 715            DateTime lastModified = File.GetLastWriteTimeUtc(helpInfoFilePath);716            TimeSpan difference = time - lastModified;717 718            if (difference.Days >= 1)719            {720                return true;721            }722 723            if (_commandType == UpdatableHelpCommandType.UpdateHelpCommand)724            {725                WriteVerbose(StringUtil.Format(HelpDisplayStrings.UseForceToUpdateHelp, moduleName));726            }727            else if (_commandType == UpdatableHelpCommandType.SaveHelpCommand)728            {729                WriteVerbose(StringUtil.Format(HelpDisplayStrings.UseForceToSaveHelp, moduleName));730            }731 732            return false;733        }734 735        /// <summary>736        /// Resolves a given path to a list of directories.737        /// </summary>738        /// <param name="path">Path to resolve.</param>739        /// <param name="recurse">Resolve recursively?</param>740        /// <param name="isLiteralPath">Treat the path / start path as a literal path?</param>///741        /// <returns>A list of directories.</returns>742        internal IEnumerable<string> ResolvePath(string path, bool recurse, bool isLiteralPath)743        {744            List<string> resolvedPaths = new List<string>();745 746            if (isLiteralPath)747            {748                string newPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(path);749 750                if (!Directory.Exists(newPath))751                {752                    throw new UpdatableHelpSystemException("PathMustBeValidContainers",753                        StringUtil.Format(HelpDisplayStrings.PathMustBeValidContainers, path), ErrorCategory.InvalidArgument,754                        null, new ItemNotFoundException());755                }756 757                resolvedPaths.Add(newPath);758            }759            else760            {761                Collection<PathInfo> resolvedPathInfos = SessionState.Path.GetResolvedPSPathFromPSPath(path);762                foreach (PathInfo resolvedPath in resolvedPathInfos)763                {764                    ValidatePathProvider(resolvedPath);765 766                    resolvedPaths.Add(resolvedPath.ProviderPath);767                }768            }769 770            foreach (string resolvedPath in resolvedPaths)771            {772                if (recurse)773                {774                    foreach (string innerResolvedPath in RecursiveResolvePathHelper(resolvedPath))775                    {776                        yield return innerResolvedPath;777                    }778                }779                else780                {781                    // Win8: 566738782                    CmdletProviderContext context = new CmdletProviderContext(this.Context);783                    // resolvedPath is already resolved..so no need to expand wildcards anymore784                    context.SuppressWildcardExpansion = true;785                    if (isLiteralPath || InvokeProvider.Item.IsContainer(resolvedPath, context))786                    {787                        yield return resolvedPath;788                    }789                }790            }791 792            yield break;793        }794 795        /// <summary>796        /// Resolves a given path to a list of directories recursively.797        /// </summary>798        /// <param name="path">Path to resolve.</param>799        /// <returns>A list of directories.</returns>800        private static IEnumerable<string> RecursiveResolvePathHelper(string path)801        {802            if (System.IO.Directory.Exists(path))803            {804                yield return path;805 806                foreach (string subDirectory in Directory.EnumerateDirectories(path))807                {808                    foreach (string subDirectory2 in RecursiveResolvePathHelper(subDirectory))809                    {810                        yield return subDirectory2;811                    }812                }813            }814 815            yield break;816        }817 818        #endregion819 820        #region Static methods821 822        /// <summary>823        /// Validates the provider of the path, only FileSystem provider is accepted.824        /// </summary>825        /// <param name="path">Path to validate.</param>826        internal void ValidatePathProvider(PathInfo path)827        {828            if (path.Provider == null || path.Provider.Name != FileSystemProvider.ProviderName)829            {830                throw new PSArgumentException(StringUtil.Format(HelpDisplayStrings.ProviderIsNotFileSystem,831                    path.Path));832            }833        }834 835        #endregion836 837        #region Logging838 839        /// <summary>840        /// Logs a command message.841        /// </summary>842        /// <param name="message">Message to log.</param>843        internal void LogMessage(string message)844        {845            List<string> details = new List<string>() { message };846            PSEtwLog.LogPipelineExecutionDetailEvent(MshLog.GetLogContext(Context, Context.CurrentCommandProcessor.Command.MyInvocation), details);847        }848 849        #endregion850 851        #region Exception processing852 853        /// <summary>854        /// Processes an exception for help cmdlets.855        /// </summary>856        /// <param name="moduleName">Module name.</param>857        /// <param name="culture">Culture info.</param>858        /// <param name="e">Exception to check.</param>859        internal void ProcessException(string moduleName, string culture, Exception e)860        {861            UpdatableHelpSystemException except = null;862 863            if (e is UpdatableHelpSystemException)864            {865                except = (UpdatableHelpSystemException)e;866            }867#if !CORECLR868            else if (e is WebException)869            {870                except = new UpdatableHelpSystemException("UnableToConnect",871                    StringUtil.Format(HelpDisplayStrings.UnableToConnect), ErrorCategory.InvalidOperation, null, e);872            }873#endif874            else if (e is PSArgumentException)875            {876                except = new UpdatableHelpSystemException("InvalidArgument",877                    e.Message, ErrorCategory.InvalidArgument, null, e);878            }879            else880            {881                except = new UpdatableHelpSystemException("UnknownErrorId",882                    e.Message, ErrorCategory.InvalidOperation, null, e);883            }884 885            if (!_exceptions.ContainsKey(except.FullyQualifiedErrorId))886            {887                _exceptions.Add(except.FullyQualifiedErrorId, new UpdatableHelpExceptionContext(except));888            }889 890            _exceptions[except.FullyQualifiedErrorId].Modules.Add(moduleName);891 892            if (culture != null)893            {894                _exceptions[except.FullyQualifiedErrorId].Cultures.Add(culture);895            }896        }897 898        #endregion899    }900 901    /// <summary>902    /// Scope to which the help should be saved.903    /// </summary>904    public enum UpdateHelpScope905    {906        /// <summary>907        /// Save the help content to the user directory.908        /// </summary>909        CurrentUser,910 911        /// <summary>912        /// Save the help content to the module directory. This is the default behavior.913        /// </summary>914        AllUsers915    }916}917