mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-15 14:20:03 +00:00
875 lines
34 KiB
C#
875 lines
34 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
using System;
|
|
using System.Linq;
|
|
using System.Management.Automation;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Management.Automation.Help;
|
|
using System.Net;
|
|
using System.IO;
|
|
using System.Globalization;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Management.Automation.Tracing;
|
|
|
|
namespace Microsoft.PowerShell.Commands
|
|
{
|
|
/// <summary>
|
|
/// The base class of all updatable help system cmdlets (Update-Help, Save-Help)
|
|
/// </summary>
|
|
public class UpdatableHelpCommandBase : PSCmdlet
|
|
{
|
|
internal const string PathParameterSetName = "Path";
|
|
internal const string LiteralPathParameterSetName = "LiteralPath";
|
|
|
|
internal UpdatableHelpCommandType _commandType;
|
|
internal UpdatableHelpSystem _helpSystem;
|
|
internal bool _stopping;
|
|
|
|
internal int activityId;
|
|
private Dictionary<string, UpdatableHelpExceptionContext> exceptions;
|
|
|
|
#region Parameters
|
|
|
|
/// <summary>
|
|
/// Specifies the languages to update
|
|
/// </summary>
|
|
[Parameter(Position = 2)]
|
|
[ValidateNotNull]
|
|
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
|
public CultureInfo[] UICulture
|
|
{
|
|
get
|
|
{
|
|
CultureInfo[] result = null;
|
|
if (_language != null)
|
|
{
|
|
result = new CultureInfo[_language.Length];
|
|
for(int index = 0; index < _language.Length; index++)
|
|
{
|
|
result[index] = new CultureInfo(_language[index]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
set
|
|
{
|
|
if (value == null) return;
|
|
_language = new string[value.Length];
|
|
for(int index = 0; index < value.Length; index++)
|
|
{
|
|
_language[index] = value[index].Name;
|
|
}
|
|
}
|
|
}
|
|
internal string[] _language;
|
|
|
|
/// <summary>
|
|
/// Gets or sets the credential parameter
|
|
/// </summary>
|
|
///
|
|
[Parameter()]
|
|
[Credential()]
|
|
public PSCredential Credential
|
|
{
|
|
get { return _credential; }
|
|
set { _credential = value; }
|
|
}
|
|
internal PSCredential _credential;
|
|
|
|
/// <summary>
|
|
/// Directs System.Net.WebClient whether or not to use default credentials
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter UseDefaultCredentials
|
|
{
|
|
get
|
|
{
|
|
return _useDefaultCredentials;
|
|
}
|
|
set
|
|
{
|
|
_useDefaultCredentials = value;
|
|
}
|
|
}
|
|
internal bool _useDefaultCredentials = false;
|
|
|
|
/// <summary>
|
|
/// Forces the operation to complete
|
|
/// </summary>
|
|
[Parameter]
|
|
public SwitchParameter Force
|
|
{
|
|
get
|
|
{
|
|
return _force;
|
|
}
|
|
set
|
|
{
|
|
_force = value;
|
|
}
|
|
}
|
|
internal bool _force;
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
/// <summary>
|
|
/// Handles help system progress events
|
|
/// </summary>
|
|
/// <param name="sender">event sender</param>
|
|
/// <param name="e">event arguments</param>
|
|
private void HandleProgressChanged(object sender, UpdatableHelpProgressEventArgs e)
|
|
{
|
|
Debug.Assert(e.CommandType == UpdatableHelpCommandType.UpdateHelpCommand
|
|
|| e.CommandType == UpdatableHelpCommandType.SaveHelpCommand);
|
|
|
|
string activity = (e.CommandType == UpdatableHelpCommandType.UpdateHelpCommand) ?
|
|
HelpDisplayStrings.UpdateProgressActivityForModule : HelpDisplayStrings.SaveProgressActivityForModule;
|
|
|
|
ProgressRecord progress = new ProgressRecord(activityId, StringUtil.Format(activity, e.ModuleName), e.ProgressStatus);
|
|
|
|
progress.PercentComplete = e.ProgressPercent;
|
|
|
|
WriteProgress(progress);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
private static Dictionary<string, string> metadataCache;
|
|
|
|
/// <summary>
|
|
/// Static constructor
|
|
///
|
|
/// NOTE: FWLinks for core PowerShell modules are needed since they get loaded as snapins in a Remoting Endpoint.
|
|
/// 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.
|
|
///
|
|
/// </summary>
|
|
static UpdatableHelpCommandBase()
|
|
{
|
|
metadataCache = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
// TODO: assign real TechNet addresses
|
|
|
|
metadataCache.Add("Microsoft.PowerShell.Diagnostics", "http://go.microsoft.com/fwlink/?linkid=390783");
|
|
metadataCache.Add("Microsoft.PowerShell.Core", "http://go.microsoft.com/fwlink/?linkid=390782");
|
|
metadataCache.Add("Microsoft.PowerShell.Utility", "http://go.microsoft.com/fwlink/?linkid=390787");
|
|
metadataCache.Add("Microsoft.PowerShell.Host", "http://go.microsoft.com/fwlink/?linkid=390784");
|
|
metadataCache.Add("Microsoft.PowerShell.Management", " http://go.microsoft.com/fwlink/?linkid=390785");
|
|
metadataCache.Add("Microsoft.PowerShell.Security", " http://go.microsoft.com/fwlink/?linkid=390786");
|
|
metadataCache.Add("Microsoft.WSMan.Management", "http://go.microsoft.com/fwlink/?linkid=390788");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a module is a system module, a module is a system module
|
|
/// if it exists in the metadata cache.
|
|
/// </summary>
|
|
/// <param name="module">module name</param>
|
|
/// <returns>true if system module, false if not</returns>
|
|
internal static bool IsSystemModule(string module)
|
|
{
|
|
return metadataCache.ContainsKey(module);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Class constructor
|
|
/// </summary>
|
|
/// <param name="commandType">command type</param>
|
|
internal UpdatableHelpCommandBase(UpdatableHelpCommandType commandType)
|
|
{
|
|
_commandType = commandType;
|
|
_helpSystem = new UpdatableHelpSystem(this, _useDefaultCredentials);
|
|
exceptions = new Dictionary<string, UpdatableHelpExceptionContext>();
|
|
_helpSystem.OnProgressChanged += new EventHandler<UpdatableHelpProgressEventArgs>(HandleProgressChanged);
|
|
|
|
Random rand = new Random();
|
|
|
|
activityId = rand.Next();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Implementation
|
|
|
|
private void ProcessSingleModuleObject(PSModuleInfo module, ExecutionContext context, Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo> helpModules, bool noErrors)
|
|
{
|
|
if (InitialSessionState.IsEngineModule(module.Name) && !InitialSessionState.IsNestedEngineModule(module.Name))
|
|
{
|
|
WriteDebug(StringUtil.Format("Found engine module: {0}, {1}.", module.Name, module.Guid));
|
|
|
|
var keyTuple = new Tuple<string, Version>(module.Name, module.Version);
|
|
if (!helpModules.ContainsKey(keyTuple))
|
|
{
|
|
helpModules.Add(keyTuple, new UpdatableHelpModuleInfo(module.Name, module.Guid,
|
|
Utils.GetApplicationBase(context.ShellID), metadataCache[module.Name]));
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if (InitialSessionState.IsNestedEngineModule(module.Name))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(module.HelpInfoUri))
|
|
{
|
|
if (!noErrors)
|
|
{
|
|
ProcessException(module.Name, null, new UpdatableHelpSystemException(
|
|
"HelpInfoUriNotFound", StringUtil.Format(HelpDisplayStrings.HelpInfoUriNotFound),
|
|
ErrorCategory.NotSpecified, new Uri("HelpInfoUri", UriKind.Relative), null));
|
|
}
|
|
return;
|
|
}
|
|
if (!module.HelpInfoUri.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (!noErrors)
|
|
{
|
|
ProcessException(module.Name, null, new UpdatableHelpSystemException(
|
|
"InvalidHelpInfoUriFormat", StringUtil.Format(HelpDisplayStrings.InvalidHelpInfoUriFormat, module.HelpInfoUri),
|
|
ErrorCategory.NotSpecified, new Uri("HelpInfoUri", UriKind.Relative), null));
|
|
}
|
|
return;
|
|
}
|
|
|
|
var keyTuple2 = new Tuple<string, Version>(module.Name, module.Version);
|
|
if (!helpModules.ContainsKey(keyTuple2))
|
|
{
|
|
helpModules.Add(keyTuple2, new UpdatableHelpModuleInfo(module.Name, module.Guid, module.ModuleBase, module.HelpInfoUri));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a list of modules from the given pattern
|
|
/// </summary>
|
|
/// <param name="context">execution context</param>
|
|
/// <param name="pattern">pattern to search</param>
|
|
/// <param name="fullyQualifiedName">Module Specification</param>
|
|
/// <param name="noErrors">do not generate errors for modules without HelpInfoUri</param>
|
|
/// <returns>a list of modules</returns>
|
|
private Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo> GetModuleInfo(ExecutionContext context, string pattern, ModuleSpecification fullyQualifiedName, bool noErrors)
|
|
{
|
|
List<PSModuleInfo> modules = null;
|
|
string moduleNamePattern = null;
|
|
|
|
if (pattern != null)
|
|
{
|
|
moduleNamePattern = pattern;
|
|
modules = Utils.GetModules(pattern, context);
|
|
}
|
|
else if (fullyQualifiedName != null)
|
|
{
|
|
moduleNamePattern = fullyQualifiedName.Name;
|
|
modules = Utils.GetModules(fullyQualifiedName, context);
|
|
}
|
|
|
|
var helpModules = new Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo>();
|
|
if (modules != null)
|
|
{
|
|
foreach (PSModuleInfo module in modules)
|
|
{
|
|
ProcessSingleModuleObject(module, context, helpModules, noErrors);
|
|
}
|
|
}
|
|
|
|
// Match wildcards
|
|
WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant;
|
|
IEnumerable<WildcardPattern> patternList = SessionStateUtilities.CreateWildcardsFromStrings(new string[1] { moduleNamePattern }, wildcardOptions);
|
|
|
|
foreach (KeyValuePair<string, string> name in metadataCache)
|
|
{
|
|
if (SessionStateUtilities.MatchesAnyWildcardPattern(name.Key, patternList, true))
|
|
{
|
|
// For core snapin, there are no GUIDs. So, we need to construct the HelpInfo slightly differently
|
|
if (!name.Key.Equals(InitialSessionState.CoreSnapin, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var keyTuple = new Tuple<string, Version>(name.Key, new Version("1.0"));
|
|
if (!helpModules.ContainsKey(keyTuple))
|
|
{
|
|
List<PSModuleInfo> availableModules = Utils.GetModules(name.Key, context);
|
|
if (null != availableModules)
|
|
{
|
|
foreach (PSModuleInfo module in availableModules)
|
|
{
|
|
keyTuple = new Tuple<string, Version>(module.Name, module.Version);
|
|
if (!helpModules.ContainsKey(keyTuple))
|
|
{
|
|
WriteDebug(StringUtil.Format("Found engine module: {0}, {1}.", module.Name, module.Guid));
|
|
|
|
helpModules.Add(keyTuple, new UpdatableHelpModuleInfo(module.Name,
|
|
module.Guid, Utils.GetApplicationBase(context.ShellID), metadataCache[module.Name]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var keyTuple2 = new Tuple<string, Version>(name.Key, new Version("1.0"));
|
|
if (!helpModules.ContainsKey(keyTuple2))
|
|
{
|
|
helpModules.Add(keyTuple2,
|
|
new UpdatableHelpModuleInfo(name.Key, Guid.Empty,
|
|
Utils.GetApplicationBase(context.ShellID),
|
|
name.Value));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return helpModules;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles Ctrl+C
|
|
/// </summary>
|
|
protected override void StopProcessing()
|
|
{
|
|
_stopping = true;
|
|
_helpSystem.CancelDownload();
|
|
}
|
|
|
|
/// <summary>
|
|
/// End processing
|
|
/// </summary>
|
|
protected override void EndProcessing()
|
|
{
|
|
foreach (UpdatableHelpExceptionContext exception in exceptions.Values)
|
|
{
|
|
UpdatableHelpExceptionContext e = exception;
|
|
|
|
if ((exception.Exception.FullyQualifiedErrorId == "HelpCultureNotSupported") &&
|
|
((exception.Cultures != null && exception.Cultures.Count > 1) ||
|
|
(exception.Modules != null && exception.Modules.Count > 1) ))
|
|
{
|
|
// Win8: 744749 Rewriting the error message only in the case where either
|
|
// multiple cultures or multiple modules are involved.
|
|
e = new UpdatableHelpExceptionContext(new UpdatableHelpSystemException(
|
|
"HelpCultureNotSupported", StringUtil.Format(HelpDisplayStrings.CannotMatchUICulturePattern,
|
|
String.Join(", ", exception.Cultures)),
|
|
ErrorCategory.InvalidArgument, exception.Cultures, null));
|
|
e.Modules = exception.Modules;
|
|
e.Cultures = exception.Cultures;
|
|
}
|
|
|
|
WriteError(e.CreateErrorRecord(_commandType));
|
|
|
|
LogContext context = MshLog.GetLogContext(Context, MyInvocation);
|
|
|
|
context.Severity = "Error";
|
|
|
|
PSEtwLog.LogOperationalError(PSEventId.Pipeline_Detail, PSOpcode.Exception, PSTask.ExecutePipeline,
|
|
context, e.GetExceptionMessage(_commandType));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main cmdlet logic for processing module names or fully qualified module names
|
|
/// </summary>
|
|
/// <param name="moduleNames">module names given by the user</param>
|
|
/// <param name="fullyQualifiedNames">fullyQualifiedNames</param>
|
|
internal void Process(IEnumerable<string> moduleNames, IEnumerable<ModuleSpecification> fullyQualifiedNames)
|
|
{
|
|
_helpSystem.WebClient.UseDefaultCredentials = _useDefaultCredentials;
|
|
|
|
if (moduleNames != null)
|
|
{
|
|
foreach (string name in moduleNames)
|
|
{
|
|
if (_stopping)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ProcessModuleWithGlobbing(name);
|
|
}
|
|
}
|
|
else if (fullyQualifiedNames != null)
|
|
{
|
|
foreach (var fullyQualifiedName in fullyQualifiedNames)
|
|
{
|
|
if (_stopping)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ProcessModuleWithGlobbing(fullyQualifiedName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (KeyValuePair<Tuple<string, Version>, UpdatableHelpModuleInfo> module in GetModuleInfo("*", null, true))
|
|
{
|
|
if (_stopping)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ProcessModule(module.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processing module objects for Save-Help
|
|
/// </summary>
|
|
/// <param name="modules">module objects given by the user</param>
|
|
internal void Process(IEnumerable<PSModuleInfo> modules)
|
|
{
|
|
if (modules == null || !modules.Any()) { return; }
|
|
|
|
var helpModules = new Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo>();
|
|
|
|
foreach (PSModuleInfo module in modules)
|
|
{
|
|
ProcessSingleModuleObject(module, Context, helpModules, false);
|
|
}
|
|
|
|
foreach (KeyValuePair<Tuple<string, Version>, UpdatableHelpModuleInfo> helpModule in helpModules)
|
|
{
|
|
ProcessModule(helpModule.Value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes a module with potential globbing
|
|
/// </summary>
|
|
/// <param name="name">module name with globbing</param>
|
|
private void ProcessModuleWithGlobbing(string name)
|
|
{
|
|
if (String.IsNullOrEmpty(name))
|
|
{
|
|
PSArgumentException e = new PSArgumentException(StringUtil.Format(HelpDisplayStrings.ModuleNameNullOrEmpty));
|
|
WriteError(e.ErrorRecord);
|
|
return;
|
|
}
|
|
|
|
foreach (KeyValuePair<Tuple<string, Version>, UpdatableHelpModuleInfo> module in GetModuleInfo(name, null, false))
|
|
{
|
|
ProcessModule(module.Value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes a ModuleSpecification with potential globbing
|
|
/// </summary>
|
|
/// <param name="fullyQualifiedName">ModuleSpecification</param>
|
|
private void ProcessModuleWithGlobbing(ModuleSpecification fullyQualifiedName)
|
|
{
|
|
foreach (KeyValuePair<Tuple<string, Version>, UpdatableHelpModuleInfo> module in GetModuleInfo(null, fullyQualifiedName, false))
|
|
{
|
|
ProcessModule(module.Value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes a single module with multiple cultures
|
|
/// </summary>
|
|
/// <param name="module">module to process</param>
|
|
private void ProcessModule(UpdatableHelpModuleInfo module)
|
|
{
|
|
_helpSystem.CurrentModule = module.ModuleName;
|
|
|
|
if (this is UpdateHelpCommand && !Directory.Exists(module.ModuleBase))
|
|
{
|
|
ProcessException(module.ModuleName, null,
|
|
new UpdatableHelpSystemException("ModuleBaseMustExist",
|
|
StringUtil.Format(HelpDisplayStrings.ModuleBaseMustExist),
|
|
ErrorCategory.InvalidOperation, null, null));
|
|
return;
|
|
}
|
|
|
|
IEnumerable<string> cultures;
|
|
|
|
if (_language == null)
|
|
{
|
|
// Win8: 572882 When the system locale is English and the UI is JPN,
|
|
// running "update-help" still downs English help content.
|
|
cultures = _helpSystem.GetCurrentUICulture();
|
|
}
|
|
else
|
|
{
|
|
cultures = _language;
|
|
}
|
|
|
|
foreach (string culture in cultures)
|
|
{
|
|
bool installed = true;
|
|
|
|
if (_stopping)
|
|
{
|
|
break;
|
|
}
|
|
|
|
try
|
|
{
|
|
ProcessModuleWithCulture(module, culture);
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
ProcessException(module.ModuleName, culture, new UpdatableHelpSystemException("FailedToCopyFile",
|
|
e.Message, ErrorCategory.InvalidOperation, null, e));
|
|
}
|
|
catch (UnauthorizedAccessException e)
|
|
{
|
|
ProcessException(module.ModuleName, culture, new UpdatableHelpSystemException("AccessIsDenied",
|
|
e.Message, ErrorCategory.PermissionDenied, null, e));
|
|
}
|
|
#if !CORECLR
|
|
catch (WebException e)
|
|
{
|
|
if (e.InnerException != null && e.InnerException is UnauthorizedAccessException)
|
|
{
|
|
ProcessException(module.ModuleName, culture, new UpdatableHelpSystemException("AccessIsDenied",
|
|
e.InnerException.Message, ErrorCategory.PermissionDenied, null, e));
|
|
}
|
|
else
|
|
{
|
|
ProcessException(module.ModuleName, culture, e);
|
|
}
|
|
}
|
|
#endif
|
|
catch (UpdatableHelpSystemException e)
|
|
{
|
|
if (e.FullyQualifiedErrorId == "HelpCultureNotSupported")
|
|
{
|
|
installed = false;
|
|
|
|
if (_language != null)
|
|
{
|
|
// Display the error message only if we are not using the fallback chain
|
|
ProcessException(module.ModuleName, culture, e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ProcessException(module.ModuleName, culture, e);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
ProcessException(module.ModuleName, culture, e);
|
|
}
|
|
finally
|
|
{
|
|
if (_helpSystem.Errors.Count != 0)
|
|
{
|
|
foreach (Exception error in _helpSystem.Errors)
|
|
{
|
|
ProcessException(module.ModuleName, culture, error);
|
|
}
|
|
|
|
_helpSystem.Errors.Clear();
|
|
}
|
|
}
|
|
|
|
// If -Language is not specified, we only install
|
|
// one culture from the fallback chain
|
|
if (_language == null && installed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Process a single module with a given culture
|
|
/// </summary>
|
|
/// <param name="module">module to process</param>
|
|
/// <param name="culture">culture to use</param>
|
|
/// <returns>true if the module has been processed, false if not</returns>
|
|
internal virtual bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, string culture)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Common methods
|
|
|
|
/// <summary>
|
|
/// Gets a list of modules from the given pattern or ModuleSpecification
|
|
/// </summary>
|
|
/// <param name="pattern">pattern to match</param>
|
|
/// <param name="fullyQualifiedName">ModuleSpecification</param>
|
|
/// <param name="noErrors">skip errors</param>
|
|
/// <returns>a list of modules</returns>
|
|
internal Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo> GetModuleInfo(string pattern, ModuleSpecification fullyQualifiedName, bool noErrors)
|
|
{
|
|
Dictionary<Tuple<string, Version>, UpdatableHelpModuleInfo> modules = GetModuleInfo(Context, pattern, fullyQualifiedName, noErrors);
|
|
|
|
if (modules.Count == 0 && exceptions.Count == 0 && !noErrors)
|
|
{
|
|
var errorMessage = fullyQualifiedName != null ? StringUtil.Format(HelpDisplayStrings.ModuleNotFoundWithFullyQualifiedName, fullyQualifiedName)
|
|
: StringUtil.Format(HelpDisplayStrings.CannotMatchModulePattern, pattern);
|
|
|
|
ErrorRecord errorRecord = new ErrorRecord(new Exception(errorMessage),
|
|
"ModuleNotFound", ErrorCategory.InvalidArgument, pattern);
|
|
|
|
WriteError(errorRecord);
|
|
}
|
|
|
|
return modules;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if it is necessary to update help
|
|
/// </summary>
|
|
/// <param name="module">ModuleInfo</param>
|
|
/// <param name="currentHelpInfo">current HelpInfo.xml</param>
|
|
/// <param name="newHelpInfo">new HelpInfo.xml</param>
|
|
/// <param name="culture">current culture</param>
|
|
/// <param name="force">force update</param>
|
|
/// <returns>true if it is necessary to update help, false if not</returns>
|
|
internal bool IsUpdateNecessary(UpdatableHelpModuleInfo module, UpdatableHelpInfo currentHelpInfo,
|
|
UpdatableHelpInfo newHelpInfo, CultureInfo culture, bool force)
|
|
{
|
|
Debug.Assert(module != null);
|
|
|
|
if (newHelpInfo == null)
|
|
{
|
|
throw new UpdatableHelpSystemException("UnableToRetrieveHelpInfoXml",
|
|
StringUtil.Format(HelpDisplayStrings.UnableToRetrieveHelpInfoXml, culture.Name), ErrorCategory.ResourceUnavailable,
|
|
null, null);
|
|
}
|
|
|
|
// Culture check
|
|
if (!newHelpInfo.IsCultureSupported(culture))
|
|
{
|
|
throw new UpdatableHelpSystemException("HelpCultureNotSupported",
|
|
StringUtil.Format(HelpDisplayStrings.HelpCultureNotSupported,
|
|
culture.Name, newHelpInfo.GetSupportedCultures()), ErrorCategory.InvalidOperation, null, null);
|
|
}
|
|
|
|
// Version check
|
|
if (!force && currentHelpInfo != null && !currentHelpInfo.IsNewerVersion(newHelpInfo, culture))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the user has attempted to update more than once per day per module
|
|
/// </summary>
|
|
/// <param name="moduleName">module name</param>
|
|
/// <param name="path">path to help info</param>
|
|
/// <param name="filename">help info file name</param>
|
|
/// <param name="time">current time (UTC)</param>
|
|
/// <param name="force">if -Force is specified</param>
|
|
/// <returns>true if we are okay to update, false if not</returns>
|
|
internal bool CheckOncePerDayPerModule(string moduleName, string path, string filename, DateTime time, bool force)
|
|
{
|
|
// Update if -Force is specified
|
|
if(force)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
string helpInfoFilePath = SessionState.Path.Combine(path, filename);
|
|
|
|
// No HelpInfo.xml
|
|
if (!File.Exists(helpInfoFilePath))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
DateTime lastModified = File.GetLastWriteTimeUtc(helpInfoFilePath);
|
|
TimeSpan difference = time - lastModified;
|
|
|
|
if (difference.Days >= 1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (_commandType == UpdatableHelpCommandType.UpdateHelpCommand)
|
|
{
|
|
WriteVerbose(StringUtil.Format(HelpDisplayStrings.UseForceToUpdateHelp, moduleName));
|
|
}
|
|
else if (_commandType == UpdatableHelpCommandType.SaveHelpCommand)
|
|
{
|
|
WriteVerbose(StringUtil.Format(HelpDisplayStrings.UseForceToSaveHelp, moduleName));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves a given path to a list of directories
|
|
/// </summary>
|
|
/// <param name="path">path to resolve</param>
|
|
/// <param name="recurse">resolve recursively?</param>
|
|
/// <param name="isLiteralPath">Treat the path / start path as a literal path?</param>///
|
|
/// <returns>a list of directories</returns>
|
|
internal IEnumerable<string> ResolvePath(string path, bool recurse, bool isLiteralPath)
|
|
{
|
|
List<string> resolvedPaths = new List<string>();
|
|
|
|
if (isLiteralPath)
|
|
{
|
|
string newPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(path);
|
|
|
|
if (!Directory.Exists(newPath))
|
|
{
|
|
throw new UpdatableHelpSystemException("PathMustBeValidContainers",
|
|
StringUtil.Format(HelpDisplayStrings.PathMustBeValidContainers, path), ErrorCategory.InvalidArgument,
|
|
null, new ItemNotFoundException());
|
|
}
|
|
|
|
resolvedPaths.Add(newPath);
|
|
}
|
|
else
|
|
{
|
|
Collection<PathInfo> resolvedPathInfos = SessionState.Path.GetResolvedPSPathFromPSPath(path);
|
|
foreach (PathInfo resolvedPath in resolvedPathInfos)
|
|
{
|
|
ValidatePathProvider(resolvedPath);
|
|
|
|
resolvedPaths.Add(resolvedPath.ProviderPath);
|
|
}
|
|
}
|
|
|
|
foreach (string resolvedPath in resolvedPaths)
|
|
{
|
|
if (recurse)
|
|
{
|
|
foreach (string innerResolvedPath in RecursiveResolvePathHelper(resolvedPath))
|
|
{
|
|
yield return innerResolvedPath;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Win8: 566738
|
|
CmdletProviderContext context = new CmdletProviderContext(this.Context);
|
|
// resolvedPath is already resolved..so no need to expand wildcards anymore
|
|
context.SuppressWildcardExpansion = true;
|
|
if (isLiteralPath || InvokeProvider.Item.IsContainer(resolvedPath, context))
|
|
{
|
|
yield return resolvedPath;
|
|
}
|
|
}
|
|
}
|
|
|
|
yield break;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves a given path to a list of directories recursively
|
|
/// </summary>
|
|
/// <param name="path">path to resolve</param>
|
|
/// <returns>a list of directories</returns>
|
|
private IEnumerable<string> RecursiveResolvePathHelper(string path)
|
|
{
|
|
if (System.IO.Directory.Exists(path))
|
|
{
|
|
yield return path;
|
|
|
|
foreach (string subDirectory in Directory.GetDirectories(path))
|
|
{
|
|
foreach (string subDirectory2 in RecursiveResolvePathHelper(subDirectory))
|
|
{
|
|
yield return subDirectory2;
|
|
}
|
|
}
|
|
}
|
|
|
|
yield break;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Static methods
|
|
|
|
/// <summary>
|
|
/// Validates the provider of the path, only FileSystem provider is accepted.
|
|
/// </summary>
|
|
/// <param name="path">path to validate</param>
|
|
internal void ValidatePathProvider(PathInfo path)
|
|
{
|
|
if (path.Provider == null || path.Provider.Name != FileSystemProvider.ProviderName)
|
|
{
|
|
throw new PSArgumentException(StringUtil.Format(HelpDisplayStrings.ProviderIsNotFileSystem,
|
|
path.Path));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Logging
|
|
|
|
/// <summary>
|
|
/// Loggs a command message
|
|
/// </summary>
|
|
/// <param name="message">message to log</param>
|
|
internal void LogMessage(string message)
|
|
{
|
|
List<string> details = new List<string>();
|
|
|
|
details.Add(message);
|
|
#if !CORECLR // TODO:CORECLR Uncomment when we add PSEtwLog support
|
|
PSEtwLog.LogPipelineExecutionDetailEvent(MshLog.GetLogContext(Context, Context.CurrentCommandProcessor.Command.MyInvocation), details);
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Exception processing
|
|
|
|
/// <summary>
|
|
/// Processes an exception for help cmdlets
|
|
/// </summary>
|
|
/// <param name="moduleName">module name</param>
|
|
/// <param name="culture">culture info</param>
|
|
/// <param name="e">exception to check</param>
|
|
internal void ProcessException(string moduleName, string culture, Exception e)
|
|
{
|
|
UpdatableHelpSystemException except = null;
|
|
|
|
if (e is UpdatableHelpSystemException)
|
|
{
|
|
except = (UpdatableHelpSystemException)e;
|
|
}
|
|
#if !CORECLR
|
|
else if(e is WebException)
|
|
{
|
|
except = new UpdatableHelpSystemException("UnableToConnect",
|
|
StringUtil.Format(HelpDisplayStrings.UnableToConnect), ErrorCategory.InvalidOperation, null, e);
|
|
}
|
|
#endif
|
|
else if (e is PSArgumentException)
|
|
{
|
|
except = new UpdatableHelpSystemException("InvalidArgument",
|
|
e.Message, ErrorCategory.InvalidArgument, null, e);
|
|
}
|
|
else
|
|
{
|
|
except = new UpdatableHelpSystemException("UnknownErrorId",
|
|
e.Message, ErrorCategory.InvalidOperation, null, e);
|
|
}
|
|
|
|
if (!exceptions.ContainsKey(except.FullyQualifiedErrorId))
|
|
{
|
|
exceptions.Add(except.FullyQualifiedErrorId, new UpdatableHelpExceptionContext(except));
|
|
}
|
|
|
|
exceptions[except.FullyQualifiedErrorId].Modules.Add(moduleName);
|
|
|
|
if (culture != null)
|
|
{
|
|
exceptions[except.FullyQualifiedErrorId].Cultures.Add(culture);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|