mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
1743 lines
78 KiB
C#
1743 lines
78 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Management.Automation.Configuration;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Language;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
using Microsoft.PowerShell.Commands;
|
|
using Microsoft.Win32;
|
|
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace System.Management.Automation
|
|
{
|
|
internal static class Constants
|
|
{
|
|
public const string PSModulePathEnvVar = "PSModulePath";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encapsulates the basic module operations for a PowerShell engine instance...
|
|
/// </summary>
|
|
public class ModuleIntrinsics
|
|
{
|
|
/// <summary>
|
|
/// Tracer for module analysis.
|
|
/// </summary>
|
|
[TraceSource("Modules", "Module loading and analysis")]
|
|
internal static readonly PSTraceSource Tracer = PSTraceSource.GetTracer("Modules", "Module loading and analysis");
|
|
|
|
// The %WINDIR%\System32\WindowsPowerShell\v1.0\Modules module path,
|
|
// to load forward compatible Windows PowerShell modules from
|
|
private static readonly string s_windowsPowerShellPSHomeModulePath =
|
|
Path.Combine(System.Environment.SystemDirectory, "WindowsPowerShell", "v1.0", "Modules");
|
|
|
|
internal ModuleIntrinsics(ExecutionContext context)
|
|
{
|
|
_context = context;
|
|
|
|
// And initialize the module path...
|
|
SetModulePath();
|
|
}
|
|
|
|
private readonly ExecutionContext _context;
|
|
|
|
// Holds the module collection...
|
|
internal Dictionary<string, PSModuleInfo> ModuleTable { get; } = new Dictionary<string, PSModuleInfo>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
private const int MaxModuleNestingDepth = 10;
|
|
|
|
/// <summary>
|
|
/// Gets and sets boolean that indicates when an implicit remoting module is loaded.
|
|
/// </summary>
|
|
internal bool IsImplicitRemotingModuleLoaded
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
internal void IncrementModuleNestingDepth(PSCmdlet cmdlet, string path)
|
|
{
|
|
if (++ModuleNestingDepth > MaxModuleNestingDepth)
|
|
{
|
|
string message = StringUtil.Format(Modules.ModuleTooDeeplyNested, path, MaxModuleNestingDepth);
|
|
InvalidOperationException ioe = new InvalidOperationException(message);
|
|
ErrorRecord er = new ErrorRecord(ioe, "Modules_ModuleTooDeeplyNested",
|
|
ErrorCategory.InvalidOperation, path);
|
|
// NOTE: this call will throw
|
|
cmdlet.ThrowTerminatingError(er);
|
|
}
|
|
}
|
|
|
|
internal void DecrementModuleNestingCount()
|
|
{
|
|
--ModuleNestingDepth;
|
|
}
|
|
|
|
internal int ModuleNestingDepth { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Create a new module object from a scriptblock specifying the path to set for the module.
|
|
/// </summary>
|
|
/// <param name="name">The name of the module.</param>
|
|
/// <param name="path">The path where the module is rooted.</param>
|
|
/// <param name="scriptBlock">
|
|
/// ScriptBlock that is executed to initialize the module...
|
|
/// </param>
|
|
/// <param name="arguments">
|
|
/// The arguments to pass to the scriptblock used to initialize the module
|
|
/// </param>
|
|
/// <param name="ss">The session state instance to use for this module - may be null.</param>
|
|
/// <param name="results">The results produced from evaluating the scriptblock.</param>
|
|
/// <returns>The newly created module info object.</returns>
|
|
internal PSModuleInfo CreateModule(string name, string path, ScriptBlock scriptBlock, SessionState ss, out List<object> results, params object[] arguments)
|
|
{
|
|
return CreateModuleImplementation(name, path, scriptBlock, null, ss, null, out results, arguments);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new module object from a ScriptInfo object.
|
|
/// </summary>
|
|
/// <param name="path">The path where the module is rooted.</param>
|
|
/// <param name="scriptInfo">The script info to use to create the module.</param>
|
|
/// <param name="scriptPosition">The position for the command that loaded this module.</param>
|
|
/// <param name="arguments">Optional arguments to pass to the script while executing.</param>
|
|
/// <param name="ss">The session state instance to use for this module - may be null.</param>
|
|
/// <param name="privateData">The private data to use for this module - may be null.</param>
|
|
/// <returns>The constructed module object.</returns>
|
|
internal PSModuleInfo CreateModule(string path, ExternalScriptInfo scriptInfo, IScriptExtent scriptPosition, SessionState ss, object privateData, params object[] arguments)
|
|
{
|
|
List<object> result;
|
|
return CreateModuleImplementation(ModuleIntrinsics.GetModuleName(path), path, scriptInfo, scriptPosition, ss, privateData, out result, arguments);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new module object from code specifying the path to set for the module.
|
|
/// </summary>
|
|
/// <param name="name">The name of the module.</param>
|
|
/// <param name="path">The path to use for the module root.</param>
|
|
/// <param name="moduleCode">
|
|
/// The code to use to create the module. This can be one of ScriptBlock, string
|
|
/// or ExternalScriptInfo
|
|
/// </param>
|
|
/// <param name="arguments">
|
|
/// Arguments to pass to the module scriptblock during evaluation.
|
|
/// </param>
|
|
/// <param name="result">
|
|
/// The results of the evaluation of the scriptblock.
|
|
/// </param>
|
|
/// <param name="scriptPosition">
|
|
/// The position of the caller of this function so you can tell where the call
|
|
/// to Import-Module (or whatever) occurred. This can be null.
|
|
/// </param>
|
|
/// <param name="ss">The session state instance to use for this module - may be null.</param>
|
|
/// <param name="privateData">The private data to use for this module - may be null.</param>
|
|
/// <returns>The created module.</returns>
|
|
private PSModuleInfo CreateModuleImplementation(string name, string path, object moduleCode, IScriptExtent scriptPosition, SessionState ss, object privateData, out List<object> result, params object[] arguments)
|
|
{
|
|
ScriptBlock sb;
|
|
|
|
// By default the top-level scope in a session state object is the global scope for the instance.
|
|
// For modules, we need to set its global scope to be another scope object and, chain the top
|
|
// level scope for this sessionstate instance to be the parent. The top level scope for this ss is the
|
|
// script scope for the ss.
|
|
|
|
// Allocate the session state instance for this module.
|
|
if (ss == null)
|
|
{
|
|
ss = new SessionState(_context, true, true);
|
|
}
|
|
|
|
// Now set up the module's session state to be the current session state
|
|
SessionStateInternal oldSessionState = _context.EngineSessionState;
|
|
PSModuleInfo module = new PSModuleInfo(name, path, _context, ss);
|
|
ss.Internal.Module = module;
|
|
module.PrivateData = privateData;
|
|
|
|
bool setExitCode = false;
|
|
int exitCode = 0;
|
|
|
|
try
|
|
{
|
|
_context.EngineSessionState = ss.Internal;
|
|
|
|
// Build the scriptblock at this point so the references to the module
|
|
// context are correct...
|
|
ExternalScriptInfo scriptInfo = moduleCode as ExternalScriptInfo;
|
|
if (scriptInfo != null)
|
|
{
|
|
sb = scriptInfo.ScriptBlock;
|
|
|
|
_context.Debugger.RegisterScriptFile(scriptInfo);
|
|
}
|
|
else
|
|
{
|
|
sb = moduleCode as ScriptBlock;
|
|
if (sb != null)
|
|
{
|
|
PSLanguageMode? moduleLanguageMode = sb.LanguageMode;
|
|
sb = sb.Clone();
|
|
sb.LanguageMode = moduleLanguageMode;
|
|
|
|
sb.SessionState = ss;
|
|
}
|
|
else
|
|
{
|
|
var sbText = moduleCode as string;
|
|
if (sbText != null)
|
|
sb = ScriptBlock.Create(_context, sbText);
|
|
}
|
|
}
|
|
|
|
if (sb == null)
|
|
throw PSTraceSource.NewInvalidOperationException();
|
|
|
|
sb.SessionStateInternal = ss.Internal;
|
|
module.LanguageMode = sb.LanguageMode;
|
|
|
|
InvocationInfo invocationInfo = new InvocationInfo(scriptInfo, scriptPosition);
|
|
|
|
// Save the module string
|
|
module._definitionExtent = sb.Ast.Extent;
|
|
var ast = sb.Ast;
|
|
while (ast.Parent != null)
|
|
{
|
|
ast = ast.Parent;
|
|
}
|
|
|
|
// The variables set in the interpreted case get set by InvokeWithPipe in the compiled case.
|
|
Diagnostics.Assert(_context.SessionState.Internal.CurrentScope.LocalsTuple == null,
|
|
"No locals tuple should have been created yet.");
|
|
|
|
List<object> resultList = new List<object>();
|
|
|
|
try
|
|
{
|
|
Pipe outputPipe = new Pipe(resultList);
|
|
|
|
// And run the scriptblock...
|
|
sb.InvokeWithPipe(
|
|
useLocalScope: false,
|
|
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
|
|
dollarUnder: AutomationNull.Value,
|
|
input: AutomationNull.Value,
|
|
scriptThis: AutomationNull.Value,
|
|
outputPipe: outputPipe,
|
|
invocationInfo: invocationInfo,
|
|
args: arguments ?? Array.Empty<object>());
|
|
}
|
|
catch (ExitException ee)
|
|
{
|
|
exitCode = (int)ee.Argument;
|
|
setExitCode = true;
|
|
}
|
|
|
|
result = resultList;
|
|
}
|
|
finally
|
|
{
|
|
_context.EngineSessionState = oldSessionState;
|
|
}
|
|
|
|
if (setExitCode)
|
|
{
|
|
_context.SetVariable(SpecialVariables.LastExitCodeVarPath, exitCode);
|
|
}
|
|
|
|
module.ImplementingAssembly = sb.AssemblyDefiningPSTypes;
|
|
// We force re-population of ExportedTypeDefinitions, now with the actual RuntimeTypes, created above.
|
|
module.CreateExportedTypeDefinitions(sb.Ast as ScriptBlockAst);
|
|
|
|
return module;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocate a new dynamic module then return a new scriptblock
|
|
/// bound to the module instance.
|
|
/// </summary>
|
|
/// <param name="context">Context to use to create bounded script.</param>
|
|
/// <param name="sb">The scriptblock to bind.</param>
|
|
/// <param name="linkToGlobal">Whether it should be linked to the global session state or not.</param>
|
|
/// <returns>A new scriptblock.</returns>
|
|
internal ScriptBlock CreateBoundScriptBlock(ExecutionContext context, ScriptBlock sb, bool linkToGlobal)
|
|
{
|
|
PSModuleInfo module = new PSModuleInfo(context, linkToGlobal);
|
|
return module.NewBoundScriptBlock(sb, context);
|
|
}
|
|
|
|
internal List<PSModuleInfo> GetModules(string[] patterns, bool all)
|
|
{
|
|
return GetModuleCore(patterns, all, false);
|
|
}
|
|
|
|
internal List<PSModuleInfo> GetExactMatchModules(string moduleName, bool all, bool exactMatch)
|
|
{
|
|
if (moduleName == null) { moduleName = string.Empty; }
|
|
|
|
return GetModuleCore(new string[] { moduleName }, all, exactMatch);
|
|
}
|
|
|
|
private List<PSModuleInfo> GetModuleCore(string[] patterns, bool all, bool exactMatch)
|
|
{
|
|
string targetModuleName = null;
|
|
List<WildcardPattern> wcpList = new List<WildcardPattern>();
|
|
|
|
if (exactMatch)
|
|
{
|
|
Dbg.Assert(patterns.Length == 1, "The 'patterns' should only contain one element when it is for an exact match");
|
|
targetModuleName = patterns[0];
|
|
}
|
|
else
|
|
{
|
|
if (patterns == null)
|
|
{
|
|
patterns = new string[] { "*" };
|
|
}
|
|
|
|
foreach (string pattern in patterns)
|
|
{
|
|
wcpList.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase));
|
|
}
|
|
}
|
|
|
|
List<PSModuleInfo> modulesMatched = new List<PSModuleInfo>();
|
|
|
|
if (all)
|
|
{
|
|
foreach (PSModuleInfo module in ModuleTable.Values)
|
|
{
|
|
// See if this is the requested module...
|
|
if ((exactMatch && module.Name.Equals(targetModuleName, StringComparison.OrdinalIgnoreCase)) ||
|
|
(!exactMatch && SessionStateUtilities.MatchesAnyWildcardPattern(module.Name, wcpList, false)))
|
|
{
|
|
modulesMatched.Add(module);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Create a joint list of local and global modules. Only report a module once.
|
|
// Local modules are reported before global modules...
|
|
Dictionary<string, bool> found = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var pair in _context.EngineSessionState.ModuleTable)
|
|
{
|
|
string path = pair.Key;
|
|
PSModuleInfo module = pair.Value;
|
|
// See if this is the requested module...
|
|
if ((exactMatch && module.Name.Equals(targetModuleName, StringComparison.OrdinalIgnoreCase)) ||
|
|
(!exactMatch && SessionStateUtilities.MatchesAnyWildcardPattern(module.Name, wcpList, false)))
|
|
{
|
|
modulesMatched.Add(module);
|
|
found[path] = true;
|
|
}
|
|
}
|
|
|
|
if (_context.EngineSessionState != _context.TopLevelSessionState)
|
|
{
|
|
foreach (var pair in _context.TopLevelSessionState.ModuleTable)
|
|
{
|
|
string path = pair.Key;
|
|
if (!found.ContainsKey(path))
|
|
{
|
|
PSModuleInfo module = pair.Value;
|
|
// See if this is the requested module...
|
|
if ((exactMatch && module.Name.Equals(targetModuleName, StringComparison.OrdinalIgnoreCase)) ||
|
|
(!exactMatch && SessionStateUtilities.MatchesAnyWildcardPattern(module.Name, wcpList, false)))
|
|
{
|
|
modulesMatched.Add(module);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return modulesMatched.OrderBy(static m => m.Name).ToList();
|
|
}
|
|
|
|
internal List<PSModuleInfo> GetModules(ModuleSpecification[] fullyQualifiedName, bool all)
|
|
{
|
|
List<PSModuleInfo> modulesMatched = new List<PSModuleInfo>();
|
|
|
|
if (all)
|
|
{
|
|
foreach (var moduleSpec in fullyQualifiedName)
|
|
{
|
|
foreach (PSModuleInfo module in ModuleTable.Values)
|
|
{
|
|
// See if this is the requested module...
|
|
if (IsModuleMatchingModuleSpec(module, moduleSpec))
|
|
{
|
|
modulesMatched.Add(module);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (var moduleSpec in fullyQualifiedName)
|
|
{
|
|
// Create a joint list of local and global modules. Only report a module once.
|
|
// Local modules are reported before global modules...
|
|
Dictionary<string, bool> found = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var pair in _context.EngineSessionState.ModuleTable)
|
|
{
|
|
string path = pair.Key;
|
|
PSModuleInfo module = pair.Value;
|
|
// See if this is the requested module...
|
|
if (IsModuleMatchingModuleSpec(module, moduleSpec))
|
|
{
|
|
modulesMatched.Add(module);
|
|
found[path] = true;
|
|
}
|
|
}
|
|
|
|
if (_context.EngineSessionState != _context.TopLevelSessionState)
|
|
{
|
|
foreach (var pair in _context.TopLevelSessionState.ModuleTable)
|
|
{
|
|
string path = pair.Key;
|
|
if (!found.ContainsKey(path))
|
|
{
|
|
PSModuleInfo module = pair.Value;
|
|
// See if this is the requested module...
|
|
if (IsModuleMatchingModuleSpec(module, moduleSpec))
|
|
{
|
|
modulesMatched.Add(module);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return modulesMatched.OrderBy(static m => m.Name).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a given module info object matches a given module specification.
|
|
/// </summary>
|
|
/// <param name="moduleInfo">The module info object to check.</param>
|
|
/// <param name="moduleSpec">The module specification to match the module info object against.</param>
|
|
/// <param name="skipNameCheck">True if we should skip the name check on the module specification.</param>
|
|
/// <returns>True if the module info object meets all the constraints on the module specification, false otherwise.</returns>
|
|
internal static bool IsModuleMatchingModuleSpec(
|
|
PSModuleInfo moduleInfo,
|
|
ModuleSpecification moduleSpec,
|
|
bool skipNameCheck = false)
|
|
{
|
|
return IsModuleMatchingModuleSpec(out ModuleMatchFailure matchFailureReason, moduleInfo, moduleSpec, skipNameCheck);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a given module info object matches a given module specification.
|
|
/// </summary>
|
|
/// <param name="matchFailureReason">The constraint that caused the match failure, if any.</param>
|
|
/// <param name="moduleInfo">The module info object to check.</param>
|
|
/// <param name="moduleSpec">The module specification to match the module info object against.</param>
|
|
/// <param name="skipNameCheck">True if we should skip the name check on the module specification.</param>
|
|
/// <returns>True if the module info object meets all the constraints on the module specification, false otherwise.</returns>
|
|
internal static bool IsModuleMatchingModuleSpec(
|
|
out ModuleMatchFailure matchFailureReason,
|
|
PSModuleInfo moduleInfo,
|
|
ModuleSpecification moduleSpec,
|
|
bool skipNameCheck = false)
|
|
{
|
|
if (moduleSpec == null)
|
|
{
|
|
matchFailureReason = ModuleMatchFailure.NullModuleSpecification;
|
|
return false;
|
|
}
|
|
|
|
return IsModuleMatchingConstraints(
|
|
out matchFailureReason,
|
|
moduleInfo,
|
|
skipNameCheck ? null : moduleSpec.Name,
|
|
moduleSpec.Guid,
|
|
moduleSpec.RequiredVersion,
|
|
moduleSpec.Version,
|
|
moduleSpec.MaximumVersion == null ? null : ModuleCmdletBase.GetMaximumVersion(moduleSpec.MaximumVersion));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a given module info object matches the given constraints.
|
|
/// Constraints given as null are ignored.
|
|
/// </summary>
|
|
/// <param name="moduleInfo">The module info object to check.</param>
|
|
/// <param name="name">The name or normalized absolute path of the expected module.</param>
|
|
/// <param name="guid">The guid of the expected module.</param>
|
|
/// <param name="requiredVersion">The required version of the expected module.</param>
|
|
/// <param name="minimumVersion">The minimum required version of the expected module.</param>
|
|
/// <param name="maximumVersion">The maximum required version of the expected module.</param>
|
|
/// <returns>True if the module info object matches all given constraints, false otherwise.</returns>
|
|
internal static bool IsModuleMatchingConstraints(
|
|
PSModuleInfo moduleInfo,
|
|
string name = null,
|
|
Guid? guid = null,
|
|
Version requiredVersion = null,
|
|
Version minimumVersion = null,
|
|
Version maximumVersion = null)
|
|
{
|
|
return IsModuleMatchingConstraints(
|
|
out ModuleMatchFailure matchFailureReason,
|
|
moduleInfo,
|
|
name,
|
|
guid,
|
|
requiredVersion,
|
|
minimumVersion,
|
|
maximumVersion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a given module info object matches the given constraints.
|
|
/// Constraints given as null are ignored.
|
|
/// </summary>
|
|
/// <param name="matchFailureReason">The reason for the module constraint match failing.</param>
|
|
/// <param name="moduleInfo">The module info object to check.</param>
|
|
/// <param name="name">The name or normalized absolute path of the expected module.</param>
|
|
/// <param name="guid">The guid of the expected module.</param>
|
|
/// <param name="requiredVersion">The required version of the expected module.</param>
|
|
/// <param name="minimumVersion">The minimum required version of the expected module.</param>
|
|
/// <param name="maximumVersion">The maximum required version of the expected module.</param>
|
|
/// <returns>True if the module info object matches all given constraints, false otherwise.</returns>
|
|
internal static bool IsModuleMatchingConstraints(
|
|
out ModuleMatchFailure matchFailureReason,
|
|
PSModuleInfo moduleInfo,
|
|
string name,
|
|
Guid? guid,
|
|
Version requiredVersion,
|
|
Version minimumVersion,
|
|
Version maximumVersion)
|
|
{
|
|
// Define that a null module does not meet any constraints
|
|
if (moduleInfo == null)
|
|
{
|
|
matchFailureReason = ModuleMatchFailure.NullModule;
|
|
return false;
|
|
}
|
|
|
|
return AreModuleFieldsMatchingConstraints(
|
|
out matchFailureReason,
|
|
moduleInfo.Name,
|
|
moduleInfo.Path,
|
|
moduleInfo.Guid,
|
|
moduleInfo.Version,
|
|
name,
|
|
guid,
|
|
requiredVersion,
|
|
minimumVersion,
|
|
maximumVersion
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check that given module fields meet any given constraints.
|
|
/// </summary>
|
|
/// <param name="moduleName">The name of the module to check.</param>
|
|
/// <param name="modulePath">The path of the module to check.</param>
|
|
/// <param name="moduleGuid">The GUID of the module to check.</param>
|
|
/// <param name="moduleVersion">The version of the module to check.</param>
|
|
/// <param name="requiredName">The name or normalized absolute path the module must have, if any.</param>
|
|
/// <param name="requiredGuid">The GUID the module must have, if any.</param>
|
|
/// <param name="requiredVersion">The exact version the module must have, if any.</param>
|
|
/// <param name="minimumRequiredVersion">The minimum version the module may have, if any.</param>
|
|
/// <param name="maximumRequiredVersion">The maximum version the module may have, if any.</param>
|
|
/// <returns>True if the module parameters match all given constraints, false otherwise.</returns>
|
|
internal static bool AreModuleFieldsMatchingConstraints(
|
|
string moduleName = null,
|
|
string modulePath = null,
|
|
Guid? moduleGuid = null,
|
|
Version moduleVersion = null,
|
|
string requiredName = null,
|
|
Guid? requiredGuid = null,
|
|
Version requiredVersion = null,
|
|
Version minimumRequiredVersion = null,
|
|
Version maximumRequiredVersion = null)
|
|
{
|
|
return AreModuleFieldsMatchingConstraints(
|
|
out ModuleMatchFailure matchFailureReason,
|
|
moduleName,
|
|
modulePath,
|
|
moduleGuid,
|
|
moduleVersion,
|
|
requiredName,
|
|
requiredGuid,
|
|
requiredVersion,
|
|
minimumRequiredVersion,
|
|
maximumRequiredVersion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check that given module fields meet any given constraints.
|
|
/// </summary>
|
|
/// <param name="matchFailureReason">The reason the match failed, if any.</param>
|
|
/// <param name="moduleName">The name of the module to check.</param>
|
|
/// <param name="modulePath">The path of the module to check.</param>
|
|
/// <param name="moduleGuid">The GUID of the module to check.</param>
|
|
/// <param name="moduleVersion">The version of the module to check.</param>
|
|
/// <param name="requiredName">The name or normalized absolute path the module must have, if any.</param>
|
|
/// <param name="requiredGuid">The GUID the module must have, if any.</param>
|
|
/// <param name="requiredVersion">The exact version the module must have, if any.</param>
|
|
/// <param name="minimumRequiredVersion">The minimum version the module may have, if any.</param>
|
|
/// <param name="maximumRequiredVersion">The maximum version the module may have, if any.</param>
|
|
/// <returns>True if the module parameters match all given constraints, false otherwise.</returns>
|
|
internal static bool AreModuleFieldsMatchingConstraints(
|
|
out ModuleMatchFailure matchFailureReason,
|
|
string moduleName,
|
|
string modulePath,
|
|
Guid? moduleGuid,
|
|
Version moduleVersion,
|
|
string requiredName,
|
|
Guid? requiredGuid,
|
|
Version requiredVersion,
|
|
Version minimumRequiredVersion,
|
|
Version maximumRequiredVersion)
|
|
{
|
|
// If a name is required, check that it matches.
|
|
// A required module name may also be an absolute path, so check it against the given module's path as well.
|
|
if (requiredName != null
|
|
&& !requiredName.Equals(moduleName, StringComparison.OrdinalIgnoreCase)
|
|
&& !MatchesModulePath(modulePath, requiredName))
|
|
{
|
|
matchFailureReason = ModuleMatchFailure.Name;
|
|
return false;
|
|
}
|
|
|
|
// If a GUID is required, check it matches
|
|
if (requiredGuid != null && !requiredGuid.Equals(moduleGuid))
|
|
{
|
|
matchFailureReason = ModuleMatchFailure.Guid;
|
|
return false;
|
|
}
|
|
|
|
// Check the versions
|
|
return IsVersionMatchingConstraints(out matchFailureReason, moduleVersion, requiredVersion, minimumRequiredVersion, maximumRequiredVersion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check that a given module version matches the required or minimum/maximum version constraints.
|
|
/// Null constraints are not checked.
|
|
/// </summary>
|
|
/// <param name="version">The module version to check. Must not be null.</param>
|
|
/// <param name="requiredVersion">The version that the given version must be, if not null.</param>
|
|
/// <param name="minimumVersion">The minimum version that the given version must be greater than or equal to, if not null.</param>
|
|
/// <param name="maximumVersion">The maximum version that the given version must be less then or equal to, if not null.</param>
|
|
/// <returns>
|
|
/// True if the version matches the required version, or if it is absent, is between the minimum and maximum versions, and false otherwise.
|
|
/// </returns>
|
|
internal static bool IsVersionMatchingConstraints(
|
|
Version version,
|
|
Version requiredVersion = null,
|
|
Version minimumVersion = null,
|
|
Version maximumVersion = null)
|
|
{
|
|
return IsVersionMatchingConstraints(out ModuleMatchFailure matchFailureReason, version, requiredVersion, minimumVersion, maximumVersion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check that a given module version matches the required or minimum/maximum version constraints.
|
|
/// Null constraints are not checked.
|
|
/// </summary>
|
|
/// <param name="matchFailureReason">The reason why the match failed.</param>
|
|
/// <param name="version">The module version to check. Must not be null.</param>
|
|
/// <param name="requiredVersion">The version that the given version must be, if not null.</param>
|
|
/// <param name="minimumVersion">The minimum version that the given version must be greater than or equal to, if not null.</param>
|
|
/// <param name="maximumVersion">The maximum version that the given version must be less then or equal to, if not null.</param>
|
|
/// <returns>
|
|
/// True if the version matches the required version, or if it is absent, is between the minimum and maximum versions, and false otherwise.
|
|
/// </returns>
|
|
internal static bool IsVersionMatchingConstraints(
|
|
out ModuleMatchFailure matchFailureReason,
|
|
Version version,
|
|
Version requiredVersion = null,
|
|
Version minimumVersion = null,
|
|
Version maximumVersion = null)
|
|
{
|
|
Dbg.Assert(version != null, $"Caller to verify that {nameof(version)} is not null");
|
|
|
|
// If a RequiredVersion is given it overrides other version settings
|
|
if (requiredVersion != null)
|
|
{
|
|
matchFailureReason = ModuleMatchFailure.RequiredVersion;
|
|
return requiredVersion.Equals(version);
|
|
}
|
|
|
|
// Check the version is at least the minimum version
|
|
if (minimumVersion != null && version < minimumVersion)
|
|
{
|
|
matchFailureReason = ModuleMatchFailure.MinimumVersion;
|
|
return false;
|
|
}
|
|
|
|
// Check the version is at most the maximum version
|
|
if (maximumVersion != null && version > maximumVersion)
|
|
{
|
|
matchFailureReason = ModuleMatchFailure.MaximumVersion;
|
|
return false;
|
|
}
|
|
|
|
matchFailureReason = ModuleMatchFailure.None;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether a given module path is the same as
|
|
/// a required path.
|
|
/// </summary>
|
|
/// <param name="modulePath">The path of the module whose path to check. This must be the path to the module file (.psd1, .psm1, .dll, etc).</param>
|
|
/// <param name="requiredPath">The path of the required module. This may be the module directory path or the file path. Only normalized absolute paths will work for this.</param>
|
|
/// <returns>True if the module path matches the required path, false otherwise.</returns>
|
|
internal static bool MatchesModulePath(string modulePath, string requiredPath)
|
|
{
|
|
Dbg.Assert(requiredPath != null, $"Caller to verify that {nameof(requiredPath)} is not null");
|
|
|
|
if (modulePath == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if UNIX
|
|
const StringComparison strcmp = StringComparison.Ordinal;
|
|
#else
|
|
const StringComparison strcmp = StringComparison.OrdinalIgnoreCase;
|
|
#endif
|
|
|
|
// We must check modulePath (e.g. /path/to/module/module.psd1) against several possibilities:
|
|
// 1. "/path/to/module" - Module dir path
|
|
// 2. "/path/to/module/module.psd1" - Module root file path
|
|
// 3. "/path/to/module/2.1/module.psd1" - Versioned module path
|
|
|
|
// If the required module just matches the module path (case 1), we are done
|
|
if (modulePath.Equals(requiredPath, strcmp))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// At this point we are looking for the module directory (case 2 or 3).
|
|
// We can some allocations here if module path doesn't sit under the required path
|
|
// (the required path may still refer to some nested module though)
|
|
if (!modulePath.StartsWith(requiredPath, strcmp))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string moduleDirPath = Path.GetDirectoryName(modulePath);
|
|
|
|
// The module itself may be in a versioned directory (case 3)
|
|
if (Version.TryParse(Path.GetFileName(moduleDirPath), out Version unused))
|
|
{
|
|
moduleDirPath = Path.GetDirectoryName(moduleDirPath);
|
|
}
|
|
|
|
return moduleDirPath.Equals(requiredPath, strcmp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes the name of a module as used in a module specification
|
|
/// and either returns it as a simple name (if it was a simple name)
|
|
/// or a fully qualified, PowerShell-resolved path.
|
|
/// </summary>
|
|
/// <param name="moduleName">The name or path of the module from the specification.</param>
|
|
/// <param name="basePath">The path to base relative paths off.</param>
|
|
/// <param name="executionContext">The current execution context.</param>
|
|
/// <returns>
|
|
/// The simple module name if the given one was simple,
|
|
/// otherwise a fully resolved, absolute path to the module.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// 2018-11-09 rjmholt:
|
|
/// There are several, possibly inconsistent, path handling mechanisms
|
|
/// in the module cmdlets. After looking through all of them and seeing
|
|
/// they all make some assumptions about their caller I wrote this method.
|
|
/// Hopefully we can find a standard path resolution API to settle on.
|
|
/// </remarks>
|
|
internal static string NormalizeModuleName(
|
|
string moduleName,
|
|
string basePath,
|
|
ExecutionContext executionContext)
|
|
{
|
|
if (moduleName == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Check whether the module is a path -- if not, it is a simple name and we just return it.
|
|
if (!IsModuleNamePath(moduleName))
|
|
{
|
|
return moduleName;
|
|
}
|
|
|
|
// Standardize directory separators -- Path.IsPathRooted() will return false for "\path\here" on *nix and for "/path/there" on Windows
|
|
moduleName = moduleName.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator);
|
|
|
|
// Note: Path.IsFullyQualified("\default\root") is false on Windows, but Path.IsPathRooted returns true
|
|
if (!Path.IsPathRooted(moduleName))
|
|
{
|
|
moduleName = Path.Join(basePath, moduleName);
|
|
}
|
|
|
|
// Use the PowerShell filesystem provider to fully resolve the path
|
|
// If there is a problem, null could be returned -- so default back to the pre-normalized path
|
|
string normalizedPath = ModuleCmdletBase.GetResolvedPath(moduleName, executionContext)?.TrimEnd(StringLiterals.DefaultPathSeparator);
|
|
|
|
// ModuleCmdletBase.GetResolvePath will return null in the unlikely event that it failed.
|
|
// If it does, we return the fully qualified path generated before.
|
|
return normalizedPath ?? Path.GetFullPath(moduleName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if a given module name is a path to a module rather than a simple name.
|
|
/// </summary>
|
|
/// <param name="moduleName">The module name to check.</param>
|
|
/// <returns>True if the module name is a path, false otherwise.</returns>
|
|
internal static bool IsModuleNamePath(string moduleName)
|
|
{
|
|
return moduleName.Contains(StringLiterals.DefaultPathSeparator)
|
|
|| moduleName.Contains(StringLiterals.AlternatePathSeparator)
|
|
|| moduleName.Equals("..")
|
|
|| moduleName.Equals(".");
|
|
}
|
|
|
|
internal static Version GetManifestModuleVersion(string manifestPath)
|
|
{
|
|
try
|
|
{
|
|
Hashtable dataFileSetting =
|
|
PsUtils.GetModuleManifestProperties(
|
|
manifestPath,
|
|
PsUtils.ManifestModuleVersionPropertyName);
|
|
|
|
object versionValue = dataFileSetting["ModuleVersion"];
|
|
if (versionValue != null)
|
|
{
|
|
Version moduleVersion;
|
|
if (LanguagePrimitives.TryConvertTo(versionValue, out moduleVersion))
|
|
{
|
|
return moduleVersion;
|
|
}
|
|
}
|
|
}
|
|
catch (PSInvalidOperationException) { }
|
|
|
|
return new Version(0, 0);
|
|
}
|
|
|
|
internal static Guid GetManifestGuid(string manifestPath)
|
|
{
|
|
try
|
|
{
|
|
Hashtable dataFileSetting =
|
|
PsUtils.GetModuleManifestProperties(
|
|
manifestPath,
|
|
PsUtils.ManifestGuidPropertyName);
|
|
|
|
object guidValue = dataFileSetting["GUID"];
|
|
if (guidValue != null)
|
|
{
|
|
Guid guidID;
|
|
if (LanguagePrimitives.TryConvertTo(guidValue, out guidID))
|
|
{
|
|
return guidID;
|
|
}
|
|
}
|
|
}
|
|
catch (PSInvalidOperationException) { }
|
|
|
|
return new Guid();
|
|
}
|
|
|
|
internal static ExperimentalFeature[] GetExperimentalFeature(string manifestPath)
|
|
{
|
|
try
|
|
{
|
|
Hashtable dataFileSetting =
|
|
PsUtils.GetModuleManifestProperties(
|
|
manifestPath,
|
|
PsUtils.ManifestPrivateDataPropertyName);
|
|
|
|
object privateData = dataFileSetting["PrivateData"];
|
|
if (privateData is Hashtable hashData && hashData["PSData"] is Hashtable psData)
|
|
{
|
|
object expFeatureValue = psData["ExperimentalFeatures"];
|
|
if (expFeatureValue != null &&
|
|
LanguagePrimitives.TryConvertTo(expFeatureValue, out Hashtable[] features) &&
|
|
features.Length > 0)
|
|
{
|
|
string moduleName = ModuleIntrinsics.GetModuleName(manifestPath);
|
|
var expFeatureList = new List<ExperimentalFeature>();
|
|
foreach (Hashtable feature in features)
|
|
{
|
|
string featureName = feature["Name"] as string;
|
|
if (string.IsNullOrEmpty(featureName)) { continue; }
|
|
|
|
if (ExperimentalFeature.IsModuleFeatureName(featureName, moduleName))
|
|
{
|
|
string featureDescription = feature["Description"] as string;
|
|
expFeatureList.Add(new ExperimentalFeature(featureName, featureDescription, manifestPath,
|
|
ExperimentalFeature.IsEnabled(featureName)));
|
|
}
|
|
}
|
|
|
|
return expFeatureList.ToArray();
|
|
}
|
|
}
|
|
}
|
|
catch (PSInvalidOperationException) { }
|
|
|
|
return Array.Empty<ExperimentalFeature>();
|
|
}
|
|
|
|
// The extensions of all of the files that can be processed with Import-Module, put the ni.dll in front of .dll to have higher priority to be loaded.
|
|
internal static readonly string[] PSModuleProcessableExtensions = new string[] {
|
|
StringLiterals.PowerShellDataFileExtension,
|
|
StringLiterals.PowerShellScriptFileExtension,
|
|
StringLiterals.PowerShellModuleFileExtension,
|
|
StringLiterals.PowerShellCmdletizationFileExtension,
|
|
StringLiterals.PowerShellNgenAssemblyExtension,
|
|
StringLiterals.PowerShellILAssemblyExtension,
|
|
StringLiterals.PowerShellILExecutableExtension,
|
|
};
|
|
|
|
// A list of the extensions to check for implicit module loading and discovery, put the ni.dll in front of .dll to have higher priority to be loaded.
|
|
internal static readonly string[] PSModuleExtensions = new string[] {
|
|
StringLiterals.PowerShellDataFileExtension,
|
|
StringLiterals.PowerShellModuleFileExtension,
|
|
StringLiterals.PowerShellCmdletizationFileExtension,
|
|
StringLiterals.PowerShellNgenAssemblyExtension,
|
|
StringLiterals.PowerShellILAssemblyExtension,
|
|
StringLiterals.PowerShellILExecutableExtension,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Returns true if the extension is one of the module extensions...
|
|
/// </summary>
|
|
/// <param name="extension">The extension to check.</param>
|
|
/// <returns>True if it was a module extension...</returns>
|
|
internal static bool IsPowerShellModuleExtension(string extension)
|
|
{
|
|
foreach (string ext in PSModuleProcessableExtensions)
|
|
{
|
|
if (extension.Equals(ext, StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the module name from module path.
|
|
/// </summary>
|
|
/// <param name="path">The path to the module.</param>
|
|
/// <returns>The module name.</returns>
|
|
internal static string GetModuleName(string path)
|
|
{
|
|
string fileName = path == null ? string.Empty : Path.GetFileName(path);
|
|
string ext;
|
|
if (fileName.EndsWith(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
ext = StringLiterals.PowerShellNgenAssemblyExtension;
|
|
}
|
|
else
|
|
{
|
|
ext = Path.GetExtension(fileName);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(ext) && IsPowerShellModuleExtension(ext))
|
|
{
|
|
return fileName.Substring(0, fileName.Length - ext.Length);
|
|
}
|
|
else
|
|
{
|
|
return fileName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the personal module path.
|
|
/// </summary>
|
|
/// <returns>Personal module path.</returns>
|
|
internal static string GetPersonalModulePath()
|
|
{
|
|
#if UNIX
|
|
return Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES);
|
|
#else
|
|
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Utils.ModuleDirectory);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the PSHome module path, as known as the "system wide module path" in windows powershell.
|
|
/// </summary>
|
|
/// <returns>The PSHome module path.</returns>
|
|
internal static string GetPSHomeModulePath()
|
|
{
|
|
if (s_psHomeModulePath != null)
|
|
{
|
|
return s_psHomeModulePath;
|
|
}
|
|
|
|
try
|
|
{
|
|
string psHome = Utils.DefaultPowerShellAppBase;
|
|
if (!string.IsNullOrEmpty(psHome))
|
|
{
|
|
// Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed
|
|
// This happens because ModuleTable uses Path as the key and CBS installer
|
|
// expands the path to include "SysWOW64" (for
|
|
// HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\PowerShell\3\PowerShellEngine ApplicationBase).
|
|
// Because of this, the module that is getting loaded during startup (through LocalRunspace)
|
|
// is using "SysWow64" in the key. Later, when Import-Module is called, it loads the
|
|
// module using ""System32" in the key.
|
|
#if !UNIX
|
|
psHome = psHome.ToLowerInvariant().Replace("\\syswow64\\", "\\system32\\");
|
|
#endif
|
|
Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null);
|
|
}
|
|
}
|
|
catch (System.Security.SecurityException)
|
|
{
|
|
}
|
|
|
|
return s_psHomeModulePath;
|
|
}
|
|
|
|
private static string s_psHomeModulePath;
|
|
|
|
/// <summary>
|
|
/// Get the module path that is shared among different users.
|
|
/// It's known as "Program Files" module path in windows powershell.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal static string GetSharedModulePath()
|
|
{
|
|
#if UNIX
|
|
return Platform.SelectProductNameForDirectory(Platform.XDG_Type.SHARED_MODULES);
|
|
#else
|
|
string sharedModulePath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
|
|
|
if (!string.IsNullOrEmpty(sharedModulePath))
|
|
{
|
|
sharedModulePath = Path.Combine(sharedModulePath, Utils.ModuleDirectory);
|
|
}
|
|
|
|
return sharedModulePath;
|
|
#endif
|
|
}
|
|
|
|
#if !UNIX
|
|
/// <summary>
|
|
/// Get the path to the Windows PowerShell module directory under the
|
|
/// System32 directory on Windows (the Windows PowerShell $PSHOME).
|
|
/// </summary>
|
|
/// <returns>The path of the Windows PowerShell system module directory.</returns>
|
|
internal static string GetWindowsPowerShellPSHomeModulePath()
|
|
{
|
|
if (!string.IsNullOrEmpty(InternalTestHooks.TestWindowsPowerShellPSHomeLocation))
|
|
{
|
|
return InternalTestHooks.TestWindowsPowerShellPSHomeLocation;
|
|
}
|
|
|
|
return s_windowsPowerShellPSHomeModulePath;
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Combine the PS system-wide module path and the DSC module path
|
|
/// to get the system module paths.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static string CombineSystemModulePaths()
|
|
{
|
|
string psHomeModulePath = GetPSHomeModulePath();
|
|
string sharedModulePath = GetSharedModulePath();
|
|
|
|
bool isPSHomePathNullOrEmpty = string.IsNullOrEmpty(psHomeModulePath);
|
|
bool isSharedPathNullOrEmpty = string.IsNullOrEmpty(sharedModulePath);
|
|
|
|
if (!isPSHomePathNullOrEmpty && !isSharedPathNullOrEmpty)
|
|
{
|
|
return (sharedModulePath + Path.PathSeparator + psHomeModulePath);
|
|
}
|
|
|
|
if (!isPSHomePathNullOrEmpty || !isSharedPathNullOrEmpty)
|
|
{
|
|
return isPSHomePathNullOrEmpty ? sharedModulePath : psHomeModulePath;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVariableTarget target)
|
|
{
|
|
string result = Environment.GetEnvironmentVariable(name, target);
|
|
if (!string.IsNullOrEmpty(result))
|
|
{
|
|
result = Environment.ExpandEnvironmentVariables(result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a particular string (path) is a member of 'combined path' string (like %Path% or %PSModulePath%)
|
|
/// </summary>
|
|
/// <param name="pathToScan">'Combined path' string to analyze; can not be null.</param>
|
|
/// <param name="pathToLookFor">Path to search for; can not be another 'combined path' (semicolon-separated); can not be null.</param>
|
|
/// <returns>Index of pathToLookFor in pathToScan; -1 if not found.</returns>
|
|
private static int PathContainsSubstring(string pathToScan, string pathToLookFor)
|
|
{
|
|
// we don't support if any of the args are null - parent function should ensure this; empty values are ok
|
|
Diagnostics.Assert(pathToScan != null, "pathToScan should not be null according to contract of the function");
|
|
Diagnostics.Assert(pathToLookFor != null, "pathToLookFor should not be null according to contract of the function");
|
|
|
|
int pos = 0; // position of the current substring in pathToScan
|
|
string[] substrings = pathToScan.Split(Utils.Separators.PathSeparator, StringSplitOptions.None); // we want to process empty entries
|
|
string goodPathToLookFor = pathToLookFor.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison
|
|
foreach (string substring in substrings)
|
|
{
|
|
string goodSubstring = substring.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison
|
|
|
|
// We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains')
|
|
// because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" }
|
|
|
|
if (string.Equals(goodSubstring, goodPathToLookFor, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return pos; // match found - return index of it in the 'pathToScan' string
|
|
}
|
|
else
|
|
{
|
|
pos += substring.Length + 1; // '1' is for trailing semicolon
|
|
}
|
|
}
|
|
// if we are here, that means a match was not found
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there.
|
|
/// </summary>
|
|
/// <param name="basePath">Path string (like %Path% or %PSModulePath%).</param>
|
|
/// <param name="pathToAdd">Collection of individual paths to add.</param>
|
|
/// <param name="insertPosition">-1 to append to the end; 0 to insert in the beginning of the string; etc...</param>
|
|
/// <returns>Result string.</returns>
|
|
private static string AddToPath(string basePath, string pathToAdd, int insertPosition)
|
|
{
|
|
// we don't support if any of the args are null - parent function should ensure this; empty values are ok
|
|
Diagnostics.Assert(basePath != null, "basePath should not be null according to contract of the function");
|
|
Diagnostics.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function");
|
|
|
|
StringBuilder result = new StringBuilder(basePath);
|
|
|
|
if (!string.IsNullOrEmpty(pathToAdd)) // we don't want to append empty paths
|
|
{
|
|
foreach (string subPathToAdd in pathToAdd.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated)
|
|
{
|
|
int position = PathContainsSubstring(result.ToString(), subPathToAdd); // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly
|
|
if (position == -1) // subPathToAdd not found - add it
|
|
{
|
|
if (insertPosition == -1) // append subPathToAdd to the end
|
|
{
|
|
bool endsWithPathSeparator = false;
|
|
if (result.Length > 0) endsWithPathSeparator = (result[result.Length - 1] == Path.PathSeparator);
|
|
|
|
if (endsWithPathSeparator)
|
|
result.Append(subPathToAdd);
|
|
else
|
|
result.Append(Path.PathSeparator + subPathToAdd);
|
|
}
|
|
else if (insertPosition > result.Length)
|
|
{
|
|
// handle case where path is a singleton with no path seperator already
|
|
result.Append(Path.PathSeparator).Append(subPathToAdd);
|
|
}
|
|
else // insert at the requested location (this is used by DSC (<Program Files> location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User))
|
|
{
|
|
result.Insert(insertPosition, subPathToAdd + Path.PathSeparator);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks the various PSModulePath environment string and returns PSModulePath string as appropriate.
|
|
/// </summary>
|
|
public static string GetModulePath(string currentProcessModulePath, string hklmMachineModulePath, string hkcuUserModulePath)
|
|
{
|
|
string personalModulePath = GetPersonalModulePath();
|
|
string sharedModulePath = GetSharedModulePath(); // aka <Program Files> location
|
|
string psHomeModulePath = GetPSHomeModulePath(); // $PSHome\Modules location
|
|
|
|
// If the variable isn't set, then set it to the default value
|
|
if (currentProcessModulePath == null) // EVT.Process does Not exist - really corner case
|
|
{
|
|
// Handle the default case...
|
|
if (string.IsNullOrEmpty(hkcuUserModulePath)) // EVT.User does Not exist -> set to <SpecialFolder.MyDocuments> location
|
|
{
|
|
currentProcessModulePath = personalModulePath; // = SpecialFolder.MyDocuments + Utils.ProductNameForDirectory + Utils.ModuleDirectory
|
|
}
|
|
else // EVT.User exists -> set to EVT.User
|
|
{
|
|
currentProcessModulePath = hkcuUserModulePath; // = EVT.User
|
|
}
|
|
|
|
currentProcessModulePath += Path.PathSeparator;
|
|
if (string.IsNullOrEmpty(hklmMachineModulePath)) // EVT.Machine does Not exist
|
|
{
|
|
currentProcessModulePath += CombineSystemModulePaths(); // += (SharedModulePath + $PSHome\Modules)
|
|
}
|
|
else
|
|
{
|
|
currentProcessModulePath += hklmMachineModulePath; // += EVT.Machine
|
|
}
|
|
}
|
|
// EVT.Process exists
|
|
// Now handle the case where the environment variable is already set.
|
|
else
|
|
{
|
|
string personalModulePathToUse = string.IsNullOrEmpty(hkcuUserModulePath) ? personalModulePath : hkcuUserModulePath;
|
|
string systemModulePathToUse = string.IsNullOrEmpty(hklmMachineModulePath) ? psHomeModulePath : hklmMachineModulePath;
|
|
|
|
// Maintain order of the paths, but ahead of any existing paths:
|
|
// personalModulePath
|
|
// sharedModulePath
|
|
// systemModulePath
|
|
currentProcessModulePath = AddToPath(currentProcessModulePath, personalModulePathToUse, 0);
|
|
int insertIndex = PathContainsSubstring(currentProcessModulePath, personalModulePathToUse) + personalModulePathToUse.Length + 1;
|
|
currentProcessModulePath = AddToPath(currentProcessModulePath, sharedModulePath, insertIndex);
|
|
insertIndex = PathContainsSubstring(currentProcessModulePath, sharedModulePath) + sharedModulePath.Length + 1;
|
|
currentProcessModulePath = AddToPath(currentProcessModulePath, systemModulePathToUse, insertIndex);
|
|
}
|
|
|
|
return currentProcessModulePath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these
|
|
/// strings go through the provider, we need to escape any wildcards before passing them
|
|
/// along.
|
|
/// </summary>
|
|
internal static string GetModulePath()
|
|
{
|
|
string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process);
|
|
return currentModulePath;
|
|
}
|
|
|
|
#if !UNIX
|
|
/// <summary>
|
|
/// Returns a PSModulePath suiteable for Windows PowerShell by removing PowerShell's specific
|
|
/// paths from current PSModulePath.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Returns appropriate PSModulePath for Windows PowerShell.
|
|
/// </returns>
|
|
internal static string GetWindowsPowerShellModulePath()
|
|
{
|
|
string currentModulePath = GetModulePath();
|
|
|
|
if (currentModulePath == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// PowerShell specific paths including if set in powershell.config.json file we want to exclude
|
|
var excludeModulePaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
|
|
GetPersonalModulePath(),
|
|
GetSharedModulePath(),
|
|
GetPSHomeModulePath(),
|
|
PowerShellConfig.Instance.GetModulePath(ConfigScope.AllUsers),
|
|
PowerShellConfig.Instance.GetModulePath(ConfigScope.CurrentUser)
|
|
};
|
|
|
|
var modulePathList = new List<string>();
|
|
foreach (var path in currentModulePath.Split(';'))
|
|
{
|
|
var trimmedPath = path.Trim();
|
|
if (!excludeModulePaths.Contains(trimmedPath))
|
|
{
|
|
// make sure this module path is Not part of other PS Core installation
|
|
var possiblePwshDir = Path.GetDirectoryName(trimmedPath);
|
|
|
|
if (string.IsNullOrEmpty(possiblePwshDir))
|
|
{
|
|
// i.e. module dir is in the drive root
|
|
modulePathList.Add(trimmedPath);
|
|
}
|
|
else
|
|
{
|
|
if (!File.Exists(Path.Combine(possiblePwshDir, "pwsh.dll")))
|
|
{
|
|
modulePathList.Add(trimmedPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return string.Join(Path.PathSeparator, modulePathList);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these
|
|
/// strings go through the provider, we need to escape any wildcards before passing them
|
|
/// along.
|
|
/// </summary>
|
|
private static string SetModulePath()
|
|
{
|
|
string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process);
|
|
#if !UNIX
|
|
// if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete
|
|
// otherwise, the user modified it and we should use the process one
|
|
if (string.CompareOrdinal(GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.User), currentModulePath) == 0)
|
|
{
|
|
currentModulePath = currentModulePath + Path.PathSeparator + GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine);
|
|
}
|
|
#endif
|
|
string allUsersModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.AllUsers);
|
|
string personalModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.CurrentUser);
|
|
string newModulePathString = GetModulePath(currentModulePath, allUsersModulePath, personalModulePath);
|
|
|
|
if (!string.IsNullOrEmpty(newModulePathString))
|
|
{
|
|
Environment.SetEnvironmentVariable(Constants.PSModulePathEnvVar, newModulePathString);
|
|
}
|
|
|
|
return newModulePathString;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current module path setting.
|
|
/// </summary>
|
|
/// <param name="includeSystemModulePath">
|
|
/// Include The system wide module path ($PSHOME\Modules) even if it's not in PSModulePath.
|
|
/// In V3-V5, we prepended this path during module auto-discovery which incorrectly preferred
|
|
/// $PSHOME\Modules over user installed modules that might have a command that overrides
|
|
/// a product-supplied command.
|
|
/// For 5.1, we append $PSHOME\Modules in this case to avoid the rare case where PSModulePath
|
|
/// does not contain the path, but a script depends on previous behavior.
|
|
/// Note that appending is still a potential breaking change, but necessary to update in-box
|
|
/// modules long term - e.g. when open sourcing a module and installing from the gallery.
|
|
/// </param>
|
|
/// <param name="context"></param>
|
|
/// <returns>The module path as an array of strings.</returns>
|
|
internal static IEnumerable<string> GetModulePath(bool includeSystemModulePath, ExecutionContext context)
|
|
{
|
|
string modulePathString = Environment.GetEnvironmentVariable(Constants.PSModulePathEnvVar) ?? SetModulePath();
|
|
|
|
HashSet<string> processedPathSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
if (!string.IsNullOrWhiteSpace(modulePathString))
|
|
{
|
|
foreach (string envPath in modulePathString.Split(Utils.Separators.PathSeparator, StringSplitOptions.RemoveEmptyEntries))
|
|
{
|
|
var processedPath = ProcessOneModulePath(context, envPath, processedPathSet);
|
|
if (processedPath != null)
|
|
yield return processedPath;
|
|
}
|
|
}
|
|
|
|
if (includeSystemModulePath)
|
|
{
|
|
var processedPath = ProcessOneModulePath(context, GetPSHomeModulePath(), processedPathSet);
|
|
if (processedPath != null)
|
|
yield return processedPath;
|
|
}
|
|
}
|
|
|
|
private static string ProcessOneModulePath(ExecutionContext context, string envPath, HashSet<string> processedPathSet)
|
|
{
|
|
string trimmedenvPath = envPath.Trim();
|
|
|
|
bool isUnc = Utils.PathIsUnc(trimmedenvPath);
|
|
if (!isUnc)
|
|
{
|
|
// if the path start with "filesystem::", remove it so we can test for URI and
|
|
// also Directory.Exists (if the file system provider isn't actually loaded.)
|
|
if (trimmedenvPath.StartsWith("filesystem::", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
trimmedenvPath = trimmedenvPath.Remove(0, 12 /*"filesystem::".Length*/);
|
|
}
|
|
|
|
isUnc = Utils.PathIsUnc(trimmedenvPath);
|
|
}
|
|
|
|
// If we have an unc, just return the value as resolving the path is expensive.
|
|
if (isUnc)
|
|
{
|
|
return trimmedenvPath;
|
|
}
|
|
|
|
// We prefer using the file system provider to resolve paths so callers can avoid processing
|
|
// duplicates, e.g. the following are all the same:
|
|
// a\b
|
|
// a\.\b
|
|
// a\b\
|
|
// But if the file system provider isn't loaded, we will just check if the directory exists.
|
|
if (context.EngineSessionState.IsProviderLoaded(context.ProviderNames.FileSystem))
|
|
{
|
|
ProviderInfo provider = null;
|
|
IEnumerable<string> resolvedPaths = null;
|
|
try
|
|
{
|
|
resolvedPaths = context.SessionState.Path.GetResolvedProviderPathFromPSPath(
|
|
WildcardPattern.Escape(trimmedenvPath), out provider);
|
|
}
|
|
catch (ItemNotFoundException)
|
|
{
|
|
// silently skip directories that are not found
|
|
}
|
|
catch (DriveNotFoundException)
|
|
{
|
|
// silently skip drives that are not found
|
|
}
|
|
catch (NotSupportedException)
|
|
{
|
|
// silently skip invalid path
|
|
// NotSupportedException is thrown if path contains a colon (":") that is not part of a
|
|
// volume identifier (for example, "c:\" is Supported but not "c:\temp\Z:\invalidPath")
|
|
}
|
|
|
|
if (provider != null && resolvedPaths != null && provider.NameEquals(context.ProviderNames.FileSystem))
|
|
{
|
|
var result = resolvedPaths.FirstOrDefault();
|
|
if (processedPathSet.Add(result))
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
else if (Directory.Exists(trimmedenvPath))
|
|
{
|
|
return trimmedenvPath;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all functions not belonging to the parent module.
|
|
/// </summary>
|
|
/// <param name="module">Parent module.</param>
|
|
internal static void RemoveNestedModuleFunctions(PSModuleInfo module)
|
|
{
|
|
var input = module.SessionState?.Internal?.ExportedFunctions;
|
|
if ((input == null) || (input.Count == 0))
|
|
{ return; }
|
|
|
|
List<FunctionInfo> output = new List<FunctionInfo>(input.Count);
|
|
foreach (var fnInfo in input)
|
|
{
|
|
if (module.Name.Equals(fnInfo.ModuleName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
output.Add(fnInfo);
|
|
}
|
|
}
|
|
|
|
input.Clear();
|
|
input.AddRange(output);
|
|
}
|
|
|
|
private static void SortAndRemoveDuplicates<T>(List<T> input, Func<T, string> keyGetter)
|
|
{
|
|
Dbg.Assert(input != null, "Caller should verify that input != null");
|
|
|
|
input.Sort(
|
|
(T x, T y) =>
|
|
{
|
|
string kx = keyGetter(x);
|
|
string ky = keyGetter(y);
|
|
return string.Compare(kx, ky, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
);
|
|
|
|
bool firstItem = true;
|
|
string previousKey = null;
|
|
List<T> output = new List<T>(input.Count);
|
|
foreach (T item in input)
|
|
{
|
|
string currentKey = keyGetter(item);
|
|
if ((firstItem) || !currentKey.Equals(previousKey, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
output.Add(item);
|
|
}
|
|
|
|
previousKey = currentKey;
|
|
firstItem = false;
|
|
}
|
|
|
|
input.Clear();
|
|
input.AddRange(output);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mark stuff to be exported from the current environment using the various patterns.
|
|
/// </summary>
|
|
/// <param name="cmdlet">The cmdlet calling this method.</param>
|
|
/// <param name="sessionState">The session state instance to do the exports on.</param>
|
|
/// <param name="functionPatterns">Patterns describing the functions to export.</param>
|
|
/// <param name="cmdletPatterns">Patterns describing the cmdlets to export.</param>
|
|
/// <param name="aliasPatterns">Patterns describing the aliases to export.</param>
|
|
/// <param name="variablePatterns">Patterns describing the variables to export.</param>
|
|
/// <param name="doNotExportCmdlets">List of Cmdlets that will not be exported, even if they match in cmdletPatterns.</param>
|
|
internal static void ExportModuleMembers(
|
|
PSCmdlet cmdlet,
|
|
SessionStateInternal sessionState,
|
|
List<WildcardPattern> functionPatterns,
|
|
List<WildcardPattern> cmdletPatterns,
|
|
List<WildcardPattern> aliasPatterns,
|
|
List<WildcardPattern> variablePatterns,
|
|
List<string> doNotExportCmdlets)
|
|
{
|
|
// If this cmdlet is called, then mark that the export list should be used for exporting
|
|
// module members...
|
|
|
|
sessionState.UseExportList = true;
|
|
|
|
if (functionPatterns != null)
|
|
{
|
|
sessionState.FunctionsExported = true;
|
|
if (PatternContainsWildcard(functionPatterns))
|
|
{
|
|
sessionState.FunctionsExportedWithWildcard = true;
|
|
}
|
|
|
|
IDictionary<string, FunctionInfo> ft = sessionState.ModuleScope.FunctionTable;
|
|
|
|
foreach (KeyValuePair<string, FunctionInfo> entry in ft)
|
|
{
|
|
// Skip AllScope functions
|
|
if ((entry.Value.Options & ScopedItemOptions.AllScope) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (SessionStateUtilities.MatchesAnyWildcardPattern(entry.Key, functionPatterns, false))
|
|
{
|
|
sessionState.ExportedFunctions.Add(entry.Value);
|
|
string message = StringUtil.Format(Modules.ExportingFunction, entry.Key);
|
|
cmdlet.WriteVerbose(message);
|
|
}
|
|
}
|
|
|
|
SortAndRemoveDuplicates(sessionState.ExportedFunctions, static (FunctionInfo ci) => ci.Name);
|
|
}
|
|
|
|
if (cmdletPatterns != null)
|
|
{
|
|
IDictionary<string, List<CmdletInfo>> ft = sessionState.ModuleScope.CmdletTable;
|
|
|
|
// Subset the existing cmdlet exports if there are any. This will be the case
|
|
// if we're using ModuleToProcess to import a binary module which has nested modules.
|
|
if (sessionState.Module.CompiledExports.Count > 0)
|
|
{
|
|
CmdletInfo[] copy = sessionState.Module.CompiledExports.ToArray();
|
|
sessionState.Module.CompiledExports.Clear();
|
|
|
|
foreach (CmdletInfo element in copy)
|
|
{
|
|
if (doNotExportCmdlets == null
|
|
|| !doNotExportCmdlets.Exists(cmdletName => string.Equals(element.FullName, cmdletName, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
if (SessionStateUtilities.MatchesAnyWildcardPattern(element.Name, cmdletPatterns, false))
|
|
{
|
|
string message = StringUtil.Format(Modules.ExportingCmdlet, element.Name);
|
|
cmdlet.WriteVerbose(message);
|
|
// Copy the cmdlet info, changing the module association to be the current module...
|
|
CmdletInfo exportedCmdlet = new CmdletInfo(element.Name, element.ImplementingType,
|
|
element.HelpFile, null, element.Context)
|
|
{ Module = sessionState.Module };
|
|
Dbg.Assert(sessionState.Module != null, "sessionState.Module should not be null by the time we're exporting cmdlets");
|
|
sessionState.Module.CompiledExports.Add(exportedCmdlet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// And copy in any cmdlets imported from the nested modules...
|
|
foreach (KeyValuePair<string, List<CmdletInfo>> entry in ft)
|
|
{
|
|
CmdletInfo cmdletToImport = entry.Value[0];
|
|
if (doNotExportCmdlets == null
|
|
|| !doNotExportCmdlets.Exists(cmdletName => string.Equals(cmdletToImport.FullName, cmdletName, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
if (SessionStateUtilities.MatchesAnyWildcardPattern(entry.Key, cmdletPatterns, false))
|
|
{
|
|
string message = StringUtil.Format(Modules.ExportingCmdlet, entry.Key);
|
|
cmdlet.WriteVerbose(message);
|
|
// Copy the cmdlet info, changing the module association to be the current module...
|
|
CmdletInfo exportedCmdlet = new CmdletInfo(cmdletToImport.Name, cmdletToImport.ImplementingType,
|
|
cmdletToImport.HelpFile, null, cmdletToImport.Context)
|
|
{ Module = sessionState.Module };
|
|
Dbg.Assert(sessionState.Module != null, "sessionState.Module should not be null by the time we're exporting cmdlets");
|
|
sessionState.Module.CompiledExports.Add(exportedCmdlet);
|
|
}
|
|
}
|
|
}
|
|
|
|
SortAndRemoveDuplicates(sessionState.Module.CompiledExports, static (CmdletInfo ci) => ci.Name);
|
|
}
|
|
|
|
if (variablePatterns != null)
|
|
{
|
|
IDictionary<string, PSVariable> vt = sessionState.ModuleScope.Variables;
|
|
|
|
foreach (KeyValuePair<string, PSVariable> entry in vt)
|
|
{
|
|
// The magic variables are always private as are all-scope variables...
|
|
if (entry.Value.IsAllScope || Array.IndexOf(PSModuleInfo._builtinVariables, entry.Key) != -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (SessionStateUtilities.MatchesAnyWildcardPattern(entry.Key, variablePatterns, false))
|
|
{
|
|
string message = StringUtil.Format(Modules.ExportingVariable, entry.Key);
|
|
cmdlet.WriteVerbose(message);
|
|
sessionState.ExportedVariables.Add(entry.Value);
|
|
}
|
|
}
|
|
|
|
SortAndRemoveDuplicates(sessionState.ExportedVariables, static (PSVariable v) => v.Name);
|
|
}
|
|
|
|
if (aliasPatterns != null)
|
|
{
|
|
IEnumerable<AliasInfo> mai = sessionState.ModuleScope.AliasTable;
|
|
|
|
// Subset the existing alias exports if there are any. This will be the case
|
|
// if we're using ModuleToProcess to import a binary module which has nested modules.
|
|
if (sessionState.Module.CompiledAliasExports.Count > 0)
|
|
{
|
|
AliasInfo[] copy = sessionState.Module.CompiledAliasExports.ToArray();
|
|
|
|
foreach (var element in copy)
|
|
{
|
|
if (SessionStateUtilities.MatchesAnyWildcardPattern(element.Name, aliasPatterns, false))
|
|
{
|
|
string message = StringUtil.Format(Modules.ExportingAlias, element.Name);
|
|
cmdlet.WriteVerbose(message);
|
|
sessionState.ExportedAliases.Add(NewAliasInfo(element, sessionState));
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (AliasInfo entry in mai)
|
|
{
|
|
// Skip allscope items...
|
|
if ((entry.Options & ScopedItemOptions.AllScope) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (SessionStateUtilities.MatchesAnyWildcardPattern(entry.Name, aliasPatterns, false))
|
|
{
|
|
string message = StringUtil.Format(Modules.ExportingAlias, entry.Name);
|
|
cmdlet.WriteVerbose(message);
|
|
sessionState.ExportedAliases.Add(NewAliasInfo(entry, sessionState));
|
|
}
|
|
}
|
|
|
|
SortAndRemoveDuplicates(sessionState.ExportedAliases, static (AliasInfo ci) => ci.Name);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks pattern list for wildcard characters.
|
|
/// </summary>
|
|
/// <param name="list">Pattern list.</param>
|
|
/// <returns>True if pattern contains '*'.</returns>
|
|
internal static bool PatternContainsWildcard(List<WildcardPattern> list)
|
|
{
|
|
if (list != null)
|
|
{
|
|
foreach (var item in list)
|
|
{
|
|
if (WildcardPattern.ContainsWildcardCharacters(item.Pattern))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static AliasInfo NewAliasInfo(AliasInfo alias, SessionStateInternal sessionState)
|
|
{
|
|
Dbg.Assert(alias != null, "alias should not be null");
|
|
Dbg.Assert(sessionState != null, "sessionState should not be null");
|
|
Dbg.Assert(sessionState.Module != null, "sessionState.Module should not be null by the time we're exporting aliases");
|
|
|
|
// Copy the alias info, changing the module association to be the current module...
|
|
var aliasCopy = new AliasInfo(alias.Name, alias.Definition, alias.Context, alias.Options)
|
|
{
|
|
Module = sessionState.Module
|
|
};
|
|
return aliasCopy;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enumeration of reasons for a failure to match a module by constraints.
|
|
/// </summary>
|
|
internal enum ModuleMatchFailure
|
|
{
|
|
/// <summary>Match did not fail.</summary>
|
|
None,
|
|
|
|
/// <summary>Match failed because the module was null.</summary>
|
|
NullModule,
|
|
|
|
/// <summary>Module name did not match.</summary>
|
|
Name,
|
|
|
|
/// <summary>Module GUID did not match.</summary>
|
|
Guid,
|
|
|
|
/// <summary>Module version did not match the required version.</summary>
|
|
RequiredVersion,
|
|
|
|
/// <summary>Module version was lower than the minimum version.</summary>
|
|
MinimumVersion,
|
|
|
|
/// <summary>Module version was greater than the maximum version.</summary>
|
|
MaximumVersion,
|
|
|
|
/// <summary>The module specifcation passed in was null.</summary>
|
|
NullModuleSpecification,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used by Modules/Snapins to provide a hook to the engine for startup initialization
|
|
/// w.r.t compiled assembly loading.
|
|
/// </summary>
|
|
#nullable enable
|
|
public interface IModuleAssemblyInitializer
|
|
{
|
|
/// <summary>
|
|
/// Gets called when assembly is loaded.
|
|
/// </summary>
|
|
void OnImport();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used by modules to provide a hook to the engine for cleanup on removal
|
|
/// w.r.t. compiled assembly being removed.
|
|
/// </summary>
|
|
public interface IModuleAssemblyCleanup
|
|
{
|
|
/// <summary>
|
|
/// Gets called when the binary module is unloaded.
|
|
/// </summary>
|
|
void OnRemove(PSModuleInfo psModuleInfo);
|
|
}
|
|
}
|