mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Enforce the 'CompatiblePSEditions' check for modules from the legacy 'System32' module path (#7183)
- Add `%WINDIR%\System32\WindowsPowerShell\v1.0\Modules` (Windows PowerShell $PSHOME) to the end of the default PSCore 6 module path (i.e. the module path as initially set at startup). - Cause an error to be thrown by `Import-Module` when a module with `CompatiblePSEditions` not containing `"Core"` is being loaded from the 'System32' module path. - Suppress output of modules listed by `Get-Module -ListAvailable` from Windows PowerShell $PSHOME when `CompatiblePSEditions` does not contain `"Core"`. - Introduce the `-SkipCompatibilityCheck` switch parameter on both `Import-Module` and `Get-Module` to respectively allow importing incompatible modules and listing incompatible modules. - Adds a `PSEdition` column to the `PSModuleInfo` table view format. - Ensures that completions are not given for incompatible modules on the System32 module path.
This commit is contained in:
+13
@@ -1196,11 +1196,24 @@ namespace System.Management.Automation.Runspaces
|
||||
.AddHeader(Alignment.Left, width: 10)
|
||||
.AddHeader(Alignment.Left, width: 10)
|
||||
.AddHeader(Alignment.Left, width: 35)
|
||||
.AddHeader(Alignment.Left, width: 9, label: "PSEdition")
|
||||
.AddHeader(Alignment.Left, label: "ExportedCommands")
|
||||
.StartRowDefinition()
|
||||
.AddPropertyColumn("ModuleType")
|
||||
.AddPropertyColumn("Version")
|
||||
.AddPropertyColumn("Name")
|
||||
.AddScriptBlockColumn(@"
|
||||
$result = [System.Collections.ArrayList]::new()
|
||||
$editions = $_.CompatiblePSEditions
|
||||
if (-not $editions)
|
||||
{
|
||||
$editions = @('Desktop')
|
||||
}
|
||||
foreach ($edition in $editions)
|
||||
{
|
||||
$result += $edition.Substring(0,4)
|
||||
}
|
||||
($result | Sort-Object) -join ','")
|
||||
.AddScriptBlockColumn("$_.ExportedCommands.Keys")
|
||||
.EndRowDefinition()
|
||||
.EndTable());
|
||||
|
||||
@@ -398,7 +398,7 @@ namespace System.Management.Automation
|
||||
|
||||
#region Module Names
|
||||
|
||||
internal static List<CompletionResult> CompleteModuleName(CompletionContext context, bool loadedModulesOnly)
|
||||
internal static List<CompletionResult> CompleteModuleName(CompletionContext context, bool loadedModulesOnly, bool skipEditionCheck = false)
|
||||
{
|
||||
var moduleName = context.WordToComplete ?? string.Empty;
|
||||
var result = new List<CompletionResult>();
|
||||
@@ -413,6 +413,12 @@ namespace System.Management.Automation
|
||||
if (!loadedModulesOnly)
|
||||
{
|
||||
powershell.AddParameter("ListAvailable", true);
|
||||
|
||||
// -SkipEditionCheck should only be set or apply to -ListAvailable
|
||||
if (skipEditionCheck)
|
||||
{
|
||||
powershell.AddParameter("SkipEditionCheck", true);
|
||||
}
|
||||
}
|
||||
|
||||
Exception exceptionThrown;
|
||||
@@ -2101,17 +2107,19 @@ namespace System.Management.Automation
|
||||
case "Get-Module":
|
||||
{
|
||||
bool loadedModulesOnly = boundArguments == null || !boundArguments.ContainsKey("ListAvailable");
|
||||
NativeCompletionModuleCommands(context, parameterName, loadedModulesOnly, /* isImportModule: */ false, result);
|
||||
bool skipEditionCheck = !loadedModulesOnly && boundArguments.ContainsKey("SkipEditionCheck");
|
||||
NativeCompletionModuleCommands(context, parameterName, result, loadedModulesOnly, skipEditionCheck: skipEditionCheck);
|
||||
break;
|
||||
}
|
||||
case "Remove-Module":
|
||||
{
|
||||
NativeCompletionModuleCommands(context, parameterName, /* loadedModulesOnly: */ true, /* isImportModule: */ false, result);
|
||||
NativeCompletionModuleCommands(context, parameterName, result, loadedModulesOnly: true);
|
||||
break;
|
||||
}
|
||||
case "Import-Module":
|
||||
{
|
||||
NativeCompletionModuleCommands(context, parameterName, /* loadedModulesOnly: */ false, /* isImportModule: */ true, result);
|
||||
bool skipEditionCheck = boundArguments != null && boundArguments.ContainsKey("SkipEditionCheck");
|
||||
NativeCompletionModuleCommands(context, parameterName, result, isImportModule: true, skipEditionCheck: skipEditionCheck);
|
||||
break;
|
||||
}
|
||||
case "Debug-Process":
|
||||
@@ -3046,7 +3054,13 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
private static void NativeCompletionModuleCommands(CompletionContext context, string paramName, bool loadedModulesOnly, bool isImportModule, List<CompletionResult> result)
|
||||
private static void NativeCompletionModuleCommands(
|
||||
CompletionContext context,
|
||||
string paramName,
|
||||
List<CompletionResult> result,
|
||||
bool loadedModulesOnly = false,
|
||||
bool isImportModule = false,
|
||||
bool skipEditionCheck = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(paramName))
|
||||
{
|
||||
@@ -3067,7 +3081,7 @@ namespace System.Management.Automation
|
||||
StringLiterals.PowerShellILAssemblyExtension,
|
||||
StringLiterals.PowerShellCmdletizationFileExtension
|
||||
};
|
||||
var moduleFilesResults = new List<CompletionResult>(CompleteFilename(context, /* containerOnly: */ false, moduleExtensions));
|
||||
var moduleFilesResults = new List<CompletionResult>(CompleteFilename(context, containerOnly: false, moduleExtensions));
|
||||
if (moduleFilesResults.Count > 0)
|
||||
result.AddRange(moduleFilesResults);
|
||||
|
||||
@@ -3079,7 +3093,7 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
var moduleResults = CompleteModuleName(context, loadedModulesOnly);
|
||||
var moduleResults = CompleteModuleName(context, loadedModulesOnly, skipEditionCheck);
|
||||
if (moduleResults != null && moduleResults.Count > 0)
|
||||
result.AddRange(moduleResults);
|
||||
|
||||
|
||||
@@ -102,6 +102,12 @@ namespace System.Management.Automation
|
||||
var moduleManifestProperties = PsUtils.GetModuleManifestProperties(modulePath, PsUtils.FastModuleManifestAnalysisPropertyNames);
|
||||
if (moduleManifestProperties != null)
|
||||
{
|
||||
if (ModuleIsEditionIncompatible(modulePath, moduleManifestProperties))
|
||||
{
|
||||
ModuleIntrinsics.Tracer.WriteLine($"Module lies on the Windows System32 legacy module path and is incompatible with current PowerShell edition, skipping module: {modulePath}");
|
||||
return null;
|
||||
}
|
||||
|
||||
Version version;
|
||||
if (ModuleUtils.IsModuleInVersionSubdirectory(modulePath, out version))
|
||||
{
|
||||
@@ -160,6 +166,31 @@ namespace System.Management.Automation
|
||||
return result ?? AnalyzeTheOldWay(modulePath, context, lastWriteTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a module is compatible with the current PSEdition given its path and its manifest properties.
|
||||
/// </summary>
|
||||
/// <param name="modulePath">The path to the module.</param>
|
||||
/// <param name="moduleManifestProperties">The properties of the module's manifest.</param>
|
||||
/// <returns></returns>
|
||||
internal static bool ModuleIsEditionIncompatible(string modulePath, Hashtable moduleManifestProperties)
|
||||
{
|
||||
#if UNIX
|
||||
return false;
|
||||
#else
|
||||
if (!ModuleUtils.IsOnSystem32ModulePath(modulePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!moduleManifestProperties.ContainsKey("CompatiblePSEditions"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !Utils.IsPSEditionSupported(LanguagePrimitives.ConvertTo<string[]>(moduleManifestProperties["CompatiblePSEditions"]));
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static bool ModuleAnalysisViaGetModuleRequired(object modulePathObj, bool hadCmdlets, bool hadFunctions, bool hadAliases)
|
||||
{
|
||||
var modulePath = modulePathObj as string;
|
||||
@@ -449,6 +480,14 @@ namespace System.Management.Automation
|
||||
{
|
||||
ModuleIntrinsics.Tracer.WriteLine("Requested caching for {0}", module.Name);
|
||||
|
||||
// Don't cache incompatible modules on the system32 module path even if loaded with
|
||||
// -SkipEditionCheck, since it will break subsequent sessions.
|
||||
if (!module.IsConsideredEditionCompatible)
|
||||
{
|
||||
ModuleIntrinsics.Tracer.WriteLine($"Module '{module.Name}' not edition compatible and not cached.");
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime lastWriteTime;
|
||||
ModuleCacheEntry moduleCacheEntry;
|
||||
GetModuleEntryFromCache(module.Path, out lastWriteTime, out moduleCacheEntry);
|
||||
|
||||
@@ -84,6 +84,18 @@ namespace Microsoft.PowerShell.Commands
|
||||
[ArgumentCompleter(typeof(PSEditionArgumentCompleter))]
|
||||
public string PSEdition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When set, CompatiblePSEditions checking is disabled for modules in the System32 (Windows PowerShell) module directory.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = ParameterSet_AvailableLocally)]
|
||||
[Parameter(ParameterSetName = ParameterSet_AvailableInPsrpSession)]
|
||||
[Parameter(ParameterSetName = ParameterSet_AvailableInCimSession)]
|
||||
public SwitchParameter SkipEditionCheck
|
||||
{
|
||||
get { return (SwitchParameter)BaseSkipEditionCheck; }
|
||||
set { BaseSkipEditionCheck = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If specified, then Get-Module refreshes the internal cmdlet analysis cache
|
||||
/// </summary>
|
||||
@@ -341,6 +353,18 @@ namespace Microsoft.PowerShell.Commands
|
||||
ThrowTerminatingError(error);
|
||||
}
|
||||
|
||||
// -SkipEditionCheck only makes sense for -ListAvailable (otherwise the module is already loaded)
|
||||
if (SkipEditionCheck && !ListAvailable)
|
||||
{
|
||||
ErrorRecord error = new ErrorRecord(
|
||||
new InvalidOperationException(Modules.SkipEditionCheckNotSupportedWithoutListAvailable),
|
||||
nameof(Modules.SkipEditionCheckNotSupportedWithoutListAvailable),
|
||||
ErrorCategory.InvalidOperation,
|
||||
targetObject: null);
|
||||
|
||||
ThrowTerminatingError(error);
|
||||
}
|
||||
|
||||
var strNames = new List<string>();
|
||||
if (Name != null)
|
||||
{
|
||||
@@ -423,27 +447,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether a module info matches a given module specification table and specified PSEdition value.
|
||||
/// </summary>
|
||||
/// <param name="moduleInfo"></param>
|
||||
/// <param name="moduleSpecTable"></param>
|
||||
/// <param name="edition"></param>
|
||||
/// <returns></returns>
|
||||
private static bool ModuleMatch(PSModuleInfo moduleInfo, IDictionary<string, ModuleSpecification> moduleSpecTable, string edition)
|
||||
{
|
||||
ModuleSpecification moduleSpecification;
|
||||
return (String.IsNullOrEmpty(edition) || moduleInfo.CompatiblePSEditions.Contains(edition, StringComparer.OrdinalIgnoreCase)) &&
|
||||
(!moduleSpecTable.TryGetValue(moduleInfo.Name, out moduleSpecification) || ModuleIntrinsics.IsModuleMatchingModuleSpec(moduleInfo, moduleSpecification));
|
||||
}
|
||||
|
||||
private void GetAvailableViaCimSession(IEnumerable<string> names, IDictionary<string, ModuleSpecification> moduleSpecTable,
|
||||
CimSession cimSession, Uri resourceUri, string cimNamespace)
|
||||
{
|
||||
var remoteModules = GetAvailableViaCimSessionCore(names, cimSession, resourceUri, cimNamespace);
|
||||
IEnumerable<PSModuleInfo> remoteModules = GetAvailableViaCimSessionCore(names, cimSession, resourceUri, cimNamespace);
|
||||
|
||||
foreach (var remoteModule in remoteModules.Where(remoteModule => ModuleMatch(remoteModule, moduleSpecTable, PSEdition))
|
||||
)
|
||||
foreach (PSModuleInfo remoteModule in FilterModulesForEditionAndSpecification(remoteModules, moduleSpecTable))
|
||||
{
|
||||
RemoteDiscoveryHelper.AssociatePSModuleInfoWithSession(remoteModule, cimSession, resourceUri,
|
||||
cimNamespace);
|
||||
@@ -453,10 +462,9 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
private void GetAvailableViaPsrpSession(string[] names, IDictionary<string, ModuleSpecification> moduleSpecTable, PSSession session)
|
||||
{
|
||||
var remoteModules = GetAvailableViaPsrpSessionCore(names, session.Runspace);
|
||||
IEnumerable<PSModuleInfo> remoteModules = GetAvailableViaPsrpSessionCore(names, session.Runspace);
|
||||
|
||||
foreach (var remoteModule in remoteModules.Where(remoteModule => ModuleMatch(remoteModule, moduleSpecTable, PSEdition))
|
||||
)
|
||||
foreach (PSModuleInfo remoteModule in FilterModulesForEditionAndSpecification(remoteModules, moduleSpecTable))
|
||||
{
|
||||
RemoteDiscoveryHelper.AssociatePSModuleInfoWithSession(remoteModule, session);
|
||||
this.WriteObject(remoteModule);
|
||||
@@ -465,14 +473,10 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
private void GetAvailableLocallyModules(string[] names, IDictionary<string, ModuleSpecification> moduleSpecTable, bool all)
|
||||
{
|
||||
var refresh = Refresh.IsPresent;
|
||||
var modules = GetModule(names, all, refresh);
|
||||
|
||||
foreach (
|
||||
var psModule in
|
||||
modules.Where(module => ModuleMatch(module, moduleSpecTable, PSEdition)).Select(module => new PSObject(module))
|
||||
)
|
||||
IEnumerable<PSModuleInfo> modules = GetModule(names, all, Refresh);
|
||||
foreach (PSModuleInfo module in FilterModulesForEditionAndSpecification(modules, moduleSpecTable))
|
||||
{
|
||||
var psModule = new PSObject(module);
|
||||
psModule.TypeNames.Insert(0, "ModuleInfoGrouping");
|
||||
WriteObject(psModule);
|
||||
}
|
||||
@@ -482,14 +486,74 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
var modulesToWrite = Context.Modules.GetModules(names, all);
|
||||
|
||||
foreach (
|
||||
var moduleInfo in
|
||||
modulesToWrite.Where(moduleInfo => ModuleMatch(moduleInfo, moduleSpecTable, PSEdition))
|
||||
)
|
||||
foreach (PSModuleInfo moduleInfo in FilterModulesForEditionAndSpecification(modulesToWrite, moduleSpecTable))
|
||||
{
|
||||
WriteObject(moduleInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filter an enumeration of PowerShell modules based on the required PowerShell edition
|
||||
/// and the module specification constraints set for each module (if any).
|
||||
/// </summary>
|
||||
/// <param name="modules">The modules to filter through.</param>
|
||||
/// <param name="moduleSpecificationTable">Module constraints, keyed by module name, to filter modules of that name by.</param>
|
||||
/// <returns>All modules from the original input that meet both any module edition and module specification constraints provided.</returns>
|
||||
private IEnumerable<PSModuleInfo> FilterModulesForEditionAndSpecification(
|
||||
IEnumerable<PSModuleInfo> modules,
|
||||
IDictionary<string, ModuleSpecification> moduleSpecificationTable)
|
||||
{
|
||||
#if !UNIX
|
||||
// Edition check only applies to Windows System32 module path
|
||||
if (!SkipEditionCheck && ListAvailable && !All)
|
||||
{
|
||||
modules = modules.Where(module => module.IsConsideredEditionCompatible);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!String.IsNullOrEmpty(PSEdition))
|
||||
{
|
||||
modules = modules.Where(module => module.CompatiblePSEditions.Contains(PSEdition, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (moduleSpecificationTable != null && moduleSpecificationTable.Count > 0)
|
||||
{
|
||||
modules = FilterModulesForSpecificationMatch(modules, moduleSpecificationTable);
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take an enumeration of modules and only return those that match a specification
|
||||
/// in the given specification table, or have no corresponding entry in the specification table.
|
||||
/// </summary>
|
||||
/// <param name="modules">The modules to filter by specification match.</param>
|
||||
/// <param name="moduleSpecificationTable">The specification lookup table to filter the modules on.</param>
|
||||
/// <returns>The modules that match their corresponding table entry, or which have no table entry.</returns>
|
||||
private static IEnumerable<PSModuleInfo> FilterModulesForSpecificationMatch(
|
||||
IEnumerable<PSModuleInfo> modules,
|
||||
IDictionary<string, ModuleSpecification> moduleSpecificationTable)
|
||||
{
|
||||
Dbg.Assert(moduleSpecificationTable != null, $"Caller to verify that {nameof(moduleSpecificationTable)} is not null");
|
||||
Dbg.Assert(moduleSpecificationTable.Count != 0, $"Caller to verify that {nameof(moduleSpecificationTable)} is not empty");
|
||||
|
||||
foreach (PSModuleInfo module in modules)
|
||||
{
|
||||
// No table entry means we return the module
|
||||
if (!moduleSpecificationTable.TryGetValue(module.Name, out ModuleSpecification moduleSpecification))
|
||||
{
|
||||
yield return module;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Modules with table entries only get returned if they match them
|
||||
if (ModuleIntrinsics.IsModuleMatchingModuleSpec(module, moduleSpecification))
|
||||
{
|
||||
yield return module;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -513,5 +577,4 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Microsoft.PowerShell.Commands
|
||||
|
||||
}
|
||||
|
||||
@@ -207,6 +207,16 @@ namespace Microsoft.PowerShell.Commands
|
||||
set { BaseForce = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips the check on CompatiblePSEditions for modules loaded from the System32 module path.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public SwitchParameter SkipEditionCheck
|
||||
{
|
||||
get { return (SwitchParameter)BaseSkipEditionCheck; }
|
||||
set { BaseSkipEditionCheck = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This parameter causes the session state instance to be written...
|
||||
/// </summary>
|
||||
|
||||
@@ -105,6 +105,11 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// </summary>
|
||||
internal bool BaseGlobal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, CompatiblePSEditions checking will be disabled for modules on the System32 path.
|
||||
/// </summary>
|
||||
internal bool BaseSkipEditionCheck { get; set; }
|
||||
|
||||
internal SessionState TargetSessionState
|
||||
{
|
||||
get
|
||||
@@ -256,6 +261,16 @@ namespace Microsoft.PowerShell.Commands
|
||||
"ModuleVersion"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// When module manifests lack a CompatiblePSEditions field,
|
||||
/// they will be treated as if they have this value.
|
||||
/// The PSModuleInfo will still reflect the lack of value.
|
||||
/// </summary>
|
||||
internal static IReadOnlyList<string> DefaultCompatiblePSEditions { get; } = new string[]
|
||||
{
|
||||
"Desktop"
|
||||
};
|
||||
|
||||
private Dictionary<string, PSModuleInfo> _currentlyProcessingModules = new Dictionary<string, PSModuleInfo>();
|
||||
|
||||
internal bool LoadUsingModulePath(bool found, IEnumerable<string> modulePath, string name, SessionState ss,
|
||||
@@ -2376,6 +2391,52 @@ namespace Microsoft.PowerShell.Commands
|
||||
if (bailOnFirstError) return null;
|
||||
}
|
||||
|
||||
// On Windows, we want to include any modules under %WINDIR%\System32\WindowsPowerShell\v1.0\Modules
|
||||
// that have declared compatibility with PS Core (or if the check is skipped)
|
||||
IEnumerable<string> inferredCompatiblePSEditions = compatiblePSEditions ?? DefaultCompatiblePSEditions;
|
||||
bool isConsideredCompatible = ModuleUtils.IsPSEditionCompatible(moduleManifestPath, inferredCompatiblePSEditions);
|
||||
if (!BaseSkipEditionCheck && !isConsideredCompatible)
|
||||
{
|
||||
containedErrors = true;
|
||||
|
||||
if (writingErrors)
|
||||
{
|
||||
message = StringUtil.Format(
|
||||
Modules.PSEditionNotSupported,
|
||||
moduleManifestPath,
|
||||
PSVersionInfo.PSEdition,
|
||||
String.Join(',', inferredCompatiblePSEditions));
|
||||
|
||||
InvalidOperationException ioe = new InvalidOperationException(message);
|
||||
|
||||
ErrorRecord er = new ErrorRecord(
|
||||
ioe,
|
||||
nameof(Modules) + "_" + nameof(Modules.PSEditionNotSupported),
|
||||
ErrorCategory.ResourceUnavailable,
|
||||
moduleManifestPath);
|
||||
|
||||
WriteError(er);
|
||||
}
|
||||
|
||||
if (bailOnFirstError)
|
||||
{
|
||||
// If we're trying to load the module, return null so that caches
|
||||
// are not polluted
|
||||
if (importingModule)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we return null with Get-Module, a fake module info will be created. Since
|
||||
// we want to suppress output of the module, we need to do that here.
|
||||
return new PSModuleInfo(moduleManifestPath, context: null, sessionState: null)
|
||||
{
|
||||
HadErrorsLoading = true,
|
||||
IsConsideredEditionCompatible = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Process format.ps1xml / types.ps1.xml / RequiredAssemblies
|
||||
// as late as possible, but before ModuleToProcess, ScriptToProcess, NestedModules
|
||||
if (importingModule)
|
||||
@@ -2475,6 +2536,10 @@ namespace Microsoft.PowerShell.Commands
|
||||
manifestInfo.ProcessorArchitecture = requiredProcessorArchitecture;
|
||||
manifestInfo.Prefix = resolvedCommandPrefix;
|
||||
|
||||
// A module is considered compatible if it's not on the System32 module path, or
|
||||
// if it is and declared "Core" as a compatible PSEdition.
|
||||
manifestInfo.IsConsideredEditionCompatible = isConsideredCompatible;
|
||||
|
||||
if (expFeatureList != null)
|
||||
{
|
||||
manifestInfo.ExperimentalFeatures = new ReadOnlyCollection<ExperimentalFeature>(expFeatureList);
|
||||
@@ -2503,13 +2568,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
manifestInfo.AddToModuleList(m);
|
||||
}
|
||||
}
|
||||
|
||||
if (compatiblePSEditions != null)
|
||||
{
|
||||
foreach (var psEdition in compatiblePSEditions)
|
||||
{
|
||||
manifestInfo.AddToCompatiblePSEditions(psEdition);
|
||||
}
|
||||
manifestInfo.AddToCompatiblePSEditions(compatiblePSEditions);
|
||||
}
|
||||
|
||||
if (scriptsToProcess != null)
|
||||
{
|
||||
foreach (var s in scriptsToProcess)
|
||||
@@ -3076,8 +3140,11 @@ namespace Microsoft.PowerShell.Commands
|
||||
newManifestInfo.LicenseUri = manifestInfo.LicenseUri;
|
||||
newManifestInfo.IconUri = manifestInfo.IconUri;
|
||||
newManifestInfo.RepositorySourceLocation = manifestInfo.RepositorySourceLocation;
|
||||
newManifestInfo.IsConsideredEditionCompatible = manifestInfo.IsConsideredEditionCompatible;
|
||||
|
||||
newManifestInfo.ExperimentalFeatures = manifestInfo.ExperimentalFeatures;
|
||||
|
||||
|
||||
// If we are in module discovery, then fix the path.
|
||||
if (ss == null)
|
||||
{
|
||||
@@ -3155,10 +3222,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
if (compatiblePSEditions != null)
|
||||
{
|
||||
foreach (var psEdition in compatiblePSEditions)
|
||||
{
|
||||
newManifestInfo.AddToCompatiblePSEditions(psEdition);
|
||||
}
|
||||
newManifestInfo.AddToCompatiblePSEditions(compatiblePSEditions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,11 @@ namespace System.Management.Automation
|
||||
[TraceSource("Modules", "Module loading and analysis")]
|
||||
internal static 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;
|
||||
@@ -629,6 +634,23 @@ namespace System.Management.Automation
|
||||
#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.
|
||||
@@ -923,6 +945,12 @@ namespace System.Management.Automation
|
||||
|
||||
if (!string.IsNullOrEmpty(newModulePathString))
|
||||
{
|
||||
#if !UNIX
|
||||
// If on Windows, we want to add the System32 Windows PowerShell module directory
|
||||
// so that Windows modules are discoverable
|
||||
newModulePathString += Path.PathSeparator + GetWindowsPowerShellPSHomeModulePath();
|
||||
#endif
|
||||
|
||||
// Set the environment variable...
|
||||
Environment.SetEnvironmentVariable(Constants.PSModulePathEnvVar, newModulePathString);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace System.Management.Automation.Internal
|
||||
internal static IEnumerable<string> GetAllAvailableModuleFiles(string topDirectoryToCheck)
|
||||
{
|
||||
if (!Directory.Exists(topDirectoryToCheck)) { yield break; }
|
||||
|
||||
|
||||
var options = Utils.PathIsUnc(topDirectoryToCheck) ? s_uncPathEnumerationOptions : s_defaultEnumerationOptions;
|
||||
Queue<string> directoriesToCheck = new Queue<string>();
|
||||
directoriesToCheck.Enqueue(topDirectoryToCheck);
|
||||
@@ -106,6 +106,29 @@ namespace System.Management.Automation.Internal
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the CompatiblePSEditions field of a given module
|
||||
/// declares compatibility with the running PowerShell edition.
|
||||
/// </summary>
|
||||
/// <param name="moduleManifestPath">The path to the module manifest being checked.</param>
|
||||
/// <param name="compatiblePSEditions">The value of the CompatiblePSEditions field of the module manifest.</param>
|
||||
/// <returns>True if the module is compatible with the running PowerShell edition, false otherwise.</returns>
|
||||
internal static bool IsPSEditionCompatible(
|
||||
string moduleManifestPath,
|
||||
IEnumerable<string> compatiblePSEditions)
|
||||
{
|
||||
#if UNIX
|
||||
return true;
|
||||
#else
|
||||
if (!ModuleUtils.IsOnSystem32ModulePath(moduleManifestPath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return Utils.IsPSEditionSupported(compatiblePSEditions);
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> GetDefaultAvailableModuleFiles(bool isForAutoDiscovery, ExecutionContext context)
|
||||
{
|
||||
HashSet<string> uniqueModuleFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -344,6 +367,18 @@ namespace System.Management.Automation.Internal
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsOnSystem32ModulePath(string path)
|
||||
{
|
||||
#if UNIX
|
||||
return false;
|
||||
#else
|
||||
Dbg.Assert(!String.IsNullOrEmpty(path), $"Caller to verify that {nameof(path)} is not null or empty");
|
||||
|
||||
string windowsPowerShellPSHomePath = ModuleIntrinsics.GetWindowsPowerShellPSHomeModulePath();
|
||||
return path.StartsWith(windowsPowerShellPSHomePath, StringComparison.OrdinalIgnoreCase);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of matching commands
|
||||
/// </summary>
|
||||
@@ -371,7 +406,7 @@ namespace System.Management.Automation.Internal
|
||||
{
|
||||
// Skip modules that have already been loaded so that we don't expose private commands.
|
||||
string moduleName = Path.GetFileNameWithoutExtension(modulePath);
|
||||
var modules = context.Modules.GetExactMatchModules(moduleName, all: false, exactMatch: true);
|
||||
List<PSModuleInfo> modules = context.Modules.GetExactMatchModules(moduleName, all: false, exactMatch: true);
|
||||
PSModuleInfo tempModuleInfo = null;
|
||||
|
||||
if (modules.Count != 0)
|
||||
@@ -387,10 +422,10 @@ namespace System.Management.Automation.Internal
|
||||
if (modules.Count == 1)
|
||||
{
|
||||
PSModuleInfo psModule = modules[0];
|
||||
tempModuleInfo = new PSModuleInfo(psModule.Name, psModule.Path, null, null);
|
||||
tempModuleInfo = new PSModuleInfo(psModule.Name, psModule.Path, context: null, sessionState: null);
|
||||
tempModuleInfo.SetModuleBase(psModule.ModuleBase);
|
||||
|
||||
foreach (var entry in psModule.ExportedCommands)
|
||||
foreach (KeyValuePair<string, CommandInfo> entry in psModule.ExportedCommands)
|
||||
{
|
||||
if (commandPattern.IsMatch(entry.Value.Name))
|
||||
{
|
||||
@@ -398,7 +433,7 @@ namespace System.Management.Automation.Internal
|
||||
switch (entry.Value.CommandType)
|
||||
{
|
||||
case CommandTypes.Alias:
|
||||
current = new AliasInfo(entry.Value.Name, null, context);
|
||||
current = new AliasInfo(entry.Value.Name, definition: null, context);
|
||||
break;
|
||||
case CommandTypes.Function:
|
||||
current = new FunctionInfo(entry.Value.Name, ScriptBlock.EmptyScriptBlock, context);
|
||||
@@ -410,7 +445,7 @@ namespace System.Management.Automation.Internal
|
||||
current = new ConfigurationInfo(entry.Value.Name, ScriptBlock.EmptyScriptBlock, context);
|
||||
break;
|
||||
case CommandTypes.Cmdlet:
|
||||
current = new CmdletInfo(entry.Value.Name, null, null, null, context);
|
||||
current = new CmdletInfo(entry.Value.Name, implementingType: null, helpFile: null, PSSnapin: null, context);
|
||||
break;
|
||||
default:
|
||||
Dbg.Assert(false, "cannot be hit");
|
||||
@@ -427,11 +462,12 @@ namespace System.Management.Automation.Internal
|
||||
}
|
||||
|
||||
string moduleShortName = System.IO.Path.GetFileNameWithoutExtension(modulePath);
|
||||
var exportedCommands = AnalysisCache.GetExportedCommands(modulePath, false, context);
|
||||
|
||||
IDictionary<string, CommandTypes> exportedCommands = AnalysisCache.GetExportedCommands(modulePath, testOnly: false, context);
|
||||
|
||||
if (exportedCommands == null) { continue; }
|
||||
|
||||
tempModuleInfo = new PSModuleInfo(moduleShortName, modulePath, null, null);
|
||||
tempModuleInfo = new PSModuleInfo(moduleShortName, modulePath, sessionState: null, context: null);
|
||||
if (InitialSessionState.IsEngineModule(moduleShortName))
|
||||
{
|
||||
tempModuleInfo.SetModuleBase(Utils.DefaultPowerShellAppBase);
|
||||
@@ -444,10 +480,10 @@ namespace System.Management.Automation.Internal
|
||||
tempModuleInfo.SetGuid(ModuleIntrinsics.GetManifestGuid(modulePath));
|
||||
}
|
||||
|
||||
foreach (var pair in exportedCommands)
|
||||
foreach (KeyValuePair<string, CommandTypes> pair in exportedCommands)
|
||||
{
|
||||
var commandName = pair.Key;
|
||||
var commandTypes = pair.Value;
|
||||
string commandName = pair.Key;
|
||||
CommandTypes commandTypes = pair.Value;
|
||||
|
||||
if (commandPattern.IsMatch(commandName))
|
||||
{
|
||||
|
||||
@@ -820,7 +820,10 @@ namespace System.Management.Automation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CompatiblePSEditions
|
||||
/// Lists the PowerShell editions this module is compatible with. This should
|
||||
/// reflect the module manifest the module was loaded with, or if no manifest was given
|
||||
/// or the key was not in the manifest, this should be an empty collection. This
|
||||
/// property is never null.
|
||||
/// </summary>
|
||||
public IEnumerable<String> CompatiblePSEditions
|
||||
{
|
||||
@@ -834,6 +837,22 @@ namespace System.Management.Automation
|
||||
_compatiblePSEditions.Add(psEdition);
|
||||
}
|
||||
|
||||
internal void AddToCompatiblePSEditions(IEnumerable<string> psEditions)
|
||||
{
|
||||
_compatiblePSEditions.AddRange(psEditions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes whether the module was considered compatible at load time.
|
||||
/// Any module not on the System32 module path should have this as true.
|
||||
/// Modules loaded from the System32 module path will have this as true if they
|
||||
/// have declared edition compatibility with PowerShell Core. Currently, this field
|
||||
/// is true for all non-psd1 module files, when it should not be. Being able to
|
||||
/// load psm1/dll modules from the System32 module path without needing to skip
|
||||
/// the edition check is considered a bug and should be fixed.
|
||||
/// </summary>
|
||||
internal bool IsConsideredEditionCompatible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// ModuleList
|
||||
/// </summary>
|
||||
@@ -1585,4 +1604,4 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
}
|
||||
} // System.Management.Automation
|
||||
}
|
||||
|
||||
@@ -23,6 +23,19 @@ namespace Microsoft.PowerShell.Commands
|
||||
[OutputType(typeof(PSModuleInfo))]
|
||||
public sealed class TestModuleManifestCommand : ModuleCmdletBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an instance of the Test-ModuleManifest command.
|
||||
/// </summary>
|
||||
public TestModuleManifestCommand()
|
||||
{
|
||||
// Test-ModuleManifest reads a manifest with ModuleCmdletBase.LoadModuleManifest().
|
||||
// This will error on an edition-incompatible manifest loaded from the System32 path,
|
||||
// unless BaseSkipEditionCheck is true. Since Test-ModuleManifest shouldn't care about
|
||||
// module edition (it just tests manifest validity), we always want to set this rather
|
||||
// than provide it as a switch on the cmdlet.
|
||||
BaseSkipEditionCheck = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The output path for the generated file...
|
||||
/// </summary>
|
||||
|
||||
@@ -451,7 +451,7 @@ namespace System.Management.Automation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether current monad session supports edition specified
|
||||
/// Checks whether current PowerShell session supports edition specified
|
||||
/// by checkEdition.
|
||||
/// </summary>
|
||||
/// <param name="checkEdition">Edition to check</param>
|
||||
@@ -462,7 +462,26 @@ namespace System.Management.Automation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the specified edition values is allowed.
|
||||
/// Check whether the current PowerShell session supports any of the specified editions.
|
||||
/// </summary>
|
||||
/// <param name="editions">The PowerShell editions to check compatibility with.</param>
|
||||
/// <returns>True if the edition is supported by this runtime, false otherwise.</returns>
|
||||
internal static bool IsPSEditionSupported(IEnumerable<string> editions)
|
||||
{
|
||||
string currentPSEdition = PSVersionInfo.PSEdition;
|
||||
foreach (string edition in editions)
|
||||
{
|
||||
if (currentPSEdition.Equals(edition, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the specified edition value is allowed.
|
||||
/// </summary>
|
||||
/// <param name="editionValue">Edition value to check</param>
|
||||
/// <returns>true if allowed, false otherwise</returns>
|
||||
@@ -1449,6 +1468,10 @@ namespace System.Management.Automation.Internal
|
||||
internal static bool StopwatchIsNotHighResolution;
|
||||
internal static bool DisableGACLoading;
|
||||
|
||||
// A location to test PSEdition compatibility functionality for Windows PowerShell modules with
|
||||
// since we can't manipulate the System32 directory in a test
|
||||
internal static string TestWindowsPowerShellPSHomeLocation;
|
||||
|
||||
internal static bool ShowMarkdownOutputBypass;
|
||||
|
||||
/// <summary>This member is used for internal test purposes.</summary>
|
||||
|
||||
@@ -606,10 +606,16 @@
|
||||
<data name="PrerequisiteForDesktopEditionOnly" xml:space="preserve">
|
||||
<value>This prerequisite is valid for the PowerShell Desktop edition only.</value>
|
||||
</data>
|
||||
<data name="PSEditionNotSupported" xml:space="preserve">
|
||||
<value>Module '{0}' does not support current PowerShell edition '{1}'. Its supported editions are '{2}'. Use 'Import-Module -SkipEditionCheck' to ignore the compatibility of this module.</value>
|
||||
</data>
|
||||
<data name="ExperimentalFeatureNameMissingOrEmpty" xml:space="preserve">
|
||||
<value>A non-empty string value should be specified for an experimental feature declared in the module manifest.</value>
|
||||
</data>
|
||||
<data name="InvalidExperimentalFeatureName" xml:space="preserve">
|
||||
<value>One or more invalid experimental feature names found: {0}. A module experimental feature name should follow this convention: 'ModuleName.FeatureName'.</value>
|
||||
</data>
|
||||
<data name="SkipEditionCheckNotSupportedWithoutListAvailable" xml:space="preserve">
|
||||
<value>The -SkipEditionCheck switch parameter cannot be used without the -ListAvailable switch parameter.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -476,8 +476,18 @@ namespace System.Management.Automation
|
||||
|
||||
internal static readonly string[] ManifestModuleVersionPropertyName = new[] { "ModuleVersion" };
|
||||
internal static readonly string[] ManifestGuidPropertyName = new[] { "GUID" };
|
||||
internal static readonly string[] FastModuleManifestAnalysisPropertyNames = new[] { "AliasesToExport", "CmdletsToExport", "FunctionsToExport", "NestedModules", "RootModule", "ModuleToProcess", "ModuleVersion" };
|
||||
internal static readonly string[] ManifestPrivateDataPropertyName = new[] { "PrivateData" };
|
||||
internal static readonly string[] FastModuleManifestAnalysisPropertyNames = new[]
|
||||
{
|
||||
"AliasesToExport",
|
||||
"CmdletsToExport",
|
||||
"CompatiblePSEditions",
|
||||
"FunctionsToExport",
|
||||
"NestedModules",
|
||||
"RootModule",
|
||||
"ModuleToProcess",
|
||||
"ModuleVersion"
|
||||
};
|
||||
|
||||
internal static Hashtable GetModuleManifestProperties(string psDataFilePath, string[] keys)
|
||||
{
|
||||
|
||||
+862
@@ -0,0 +1,862 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
$script:oldModulePath = $env:PSModulePath
|
||||
|
||||
function Add-ModulePath
|
||||
{
|
||||
param([string]$Path, [switch]$Prepend)
|
||||
|
||||
$script:oldModulePath = $env:PSModulePath
|
||||
|
||||
if ($Prepend)
|
||||
{
|
||||
$env:PSModulePAth = $Path + [System.IO.Path]::PathSeparator + $env:PSModulePath
|
||||
}
|
||||
else
|
||||
{
|
||||
$env:PSModulePath = $env:PSModulePath + [System.IO.Path]::PathSeparator + $Path
|
||||
}
|
||||
}
|
||||
|
||||
function Restore-ModulePath
|
||||
{
|
||||
$env:PSModulePath = $script:oldModulePath
|
||||
}
|
||||
|
||||
# Creates a new dummy module compatible with the given PSEditions
|
||||
function New-EditionCompatibleModule
|
||||
{
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$ModuleName,
|
||||
[string]$DirPath,
|
||||
[string[]]$CompatiblePSEditions)
|
||||
|
||||
$modulePath = Join-Path $DirPath $ModuleName
|
||||
|
||||
$manifestPath = Join-Path $modulePath "$ModuleName.psd1"
|
||||
|
||||
$psm1Name = "$ModuleName.psm1"
|
||||
$psm1Path = Join-Path $modulePath $psm1Name
|
||||
|
||||
New-Item -Path $modulePath -ItemType Directory
|
||||
|
||||
New-Item -Path $psm1Path -Value "function Test-$ModuleName { `$true }"
|
||||
|
||||
if ($CompatiblePSEditions)
|
||||
{
|
||||
New-ModuleManifest -Path $manifestPath -CompatiblePSEditions $CompatiblePSEditions -RootModule $psm1Name
|
||||
}
|
||||
else
|
||||
{
|
||||
New-ModuleManifest -Path $manifestPath -RootModule $psm1Name
|
||||
}
|
||||
|
||||
return $modulePath
|
||||
}
|
||||
|
||||
function New-TestModules
|
||||
{
|
||||
param([hashtable[]]$TestCases, [string]$BaseDir)
|
||||
|
||||
for ($i = 0; $i -lt $TestCases.Count; $i++)
|
||||
{
|
||||
$path = New-EditionCompatibleModule -ModuleName $TestCases[$i].ModuleName -CompatiblePSEditions $TestCases[$i].Editions -Dir $BaseDir
|
||||
|
||||
$TestCases[$i].Path = $path
|
||||
$TestCases[$i].Name = $TestCases[$i].Editions -join ","
|
||||
}
|
||||
}
|
||||
|
||||
function New-TestNestedModule
|
||||
{
|
||||
param(
|
||||
[string]$ModuleBase,
|
||||
[string]$ScriptModuleFilename,
|
||||
[string]$ScriptModuleContent,
|
||||
[string]$BinaryModuleFilename,
|
||||
[string]$BinaryModuleDllPath,
|
||||
[string]$RootModuleFilename,
|
||||
[string]$RootModuleContent,
|
||||
[string[]]$CompatiblePSEditions,
|
||||
[bool]$UseRootModule,
|
||||
[bool]$UseAbsolutePath
|
||||
)
|
||||
|
||||
$nestedModules = [System.Collections.ArrayList]::new()
|
||||
|
||||
# Create script module
|
||||
New-Item -Path (Join-Path $ModuleBase $ScriptModuleFileName) -Value $ScriptModuleContent
|
||||
$nestedModules.Add($ScriptModuleFilename)
|
||||
|
||||
if ($BinaryModuleFilename -and $BinaryModuleDllPath)
|
||||
{
|
||||
# Create binary module
|
||||
Copy-Item -Path $BinaryModuleDllPath -Destination (Join-Path $ModuleBase $BinaryModuleFilename)
|
||||
$nestedModules.Add($BinaryModuleFilename)
|
||||
}
|
||||
|
||||
# Create the root module if there is one
|
||||
if ($UseRootModule)
|
||||
{
|
||||
New-Item -Path (Join-Path $ModuleBase $RootModuleFilename) -Value $RootModuleContent
|
||||
}
|
||||
|
||||
# Create the manifest command
|
||||
$moduleName = Split-Path -Leaf $ModuleBase
|
||||
$manifestPath = Join-Path $ModuleBase "$moduleName.psd1"
|
||||
|
||||
$nestedModules = $nestedModules -join ','
|
||||
|
||||
$newManifestCmd = "New-ModuleManifest -Path $manifestPath -NestedModules $nestedModules "
|
||||
if ($CompatiblePSEditions)
|
||||
{
|
||||
$compatibleModules = $CompatiblePSEditions -join ','
|
||||
$newManifestCmd += "-CompatiblePSEditions $compatibleModules "
|
||||
}
|
||||
if ($UseRootModule)
|
||||
{
|
||||
$newManifestCmd += "-RootModule $RootModuleFilename "
|
||||
$newManifestCmd += "-FunctionsToExport @('Test-RootModule') "
|
||||
}
|
||||
else
|
||||
{
|
||||
$newManifestCmd += "-FunctionsToExport @('Test-ScriptModule') "
|
||||
}
|
||||
|
||||
$newManifestCmd += "-CmdletsToExport @() -VariablesToExport @() -AliasesToExport @() "
|
||||
|
||||
# Create the manifest
|
||||
[scriptblock]::Create($newManifestCmd).Invoke()
|
||||
}
|
||||
|
||||
|
||||
Describe "Get-Module with CompatiblePSEditions-checked paths" -Tag "CI" {
|
||||
|
||||
BeforeAll {
|
||||
if (-not $IsWindows)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
$successCases = @(
|
||||
@{ Editions = "Core","Desktop"; ModuleName = "BothModule" },
|
||||
@{ Editions = "Core"; ModuleName = "CoreModule" }
|
||||
)
|
||||
|
||||
$failCases = @(
|
||||
@{ Editions = "Desktop"; ModuleName = "DesktopModule" },
|
||||
@{ Editions = $null; ModuleName = "NeitherModule" }
|
||||
)
|
||||
|
||||
$basePath = Join-Path $TestDrive "EditionCompatibleModules"
|
||||
New-TestModules -TestCases $successCases -BaseDir $basePath
|
||||
New-TestModules -TestCases $failCases -BaseDir $basePath
|
||||
|
||||
# Emulate the System32 module path for tests
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $basePath)
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $null)
|
||||
}
|
||||
|
||||
Context "Loading from checked paths on the module path with no flags" {
|
||||
BeforeAll {
|
||||
Add-ModulePath $basePath
|
||||
$modules = Get-Module -ListAvailable
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
Restore-ModulePath
|
||||
}
|
||||
|
||||
It "Lists compatible modules from the module path with -ListAvailable for PSEdition <Editions>" -TestCases $successCases -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName)
|
||||
|
||||
$modules.Name | Should -Contain $ModuleName
|
||||
}
|
||||
|
||||
It "Does not list incompatible modules with -ListAvailable for PSEdition <Editions>" -TestCases $failCases -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName)
|
||||
|
||||
$modules.Name | Should -Not -Contain $ModuleName
|
||||
}
|
||||
}
|
||||
|
||||
Context "Loading from checked paths by absolute path with no flags" {
|
||||
It "Lists compatible modules with -ListAvailable for PSEdition <Editions>" -TestCases $successCases -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName)
|
||||
|
||||
$modules = Get-Module -ListAvailable (Join-Path -Path $basePath -ChildPath $ModuleName)
|
||||
|
||||
$modules.Name | Should -Contain $ModuleName
|
||||
}
|
||||
|
||||
It "Does not list incompatible modules with -ListAvailable for PSEdition <Editions>" -TestCases $failCases -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName)
|
||||
|
||||
$modules = Get-Module -ListAvailable (Join-Path -Path $basePath -ChildPath $ModuleName)
|
||||
|
||||
$modules.Name | Should -Not -Contain $ModuleName
|
||||
}
|
||||
}
|
||||
|
||||
Context "Loading from checked paths on the module path with -SkipEditionCheck" {
|
||||
BeforeAll {
|
||||
Add-ModulePath $basePath
|
||||
$modules = Get-Module -ListAvailable -SkipEditionCheck
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
Restore-ModulePath
|
||||
}
|
||||
|
||||
It "Lists all modules from the module path with -ListAvailable for PSEdition <Editions>" -TestCases ($successCases + $failCases) -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName)
|
||||
|
||||
$modules.Name | Should -Contain $ModuleName
|
||||
}
|
||||
}
|
||||
|
||||
Context "Loading from checked paths by absolute path with -SkipEditionCheck" {
|
||||
It "Lists compatible modules with -ListAvailable for PSEdition <Editions>" -TestCases ($successCases + $failCases) -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName)
|
||||
|
||||
$modules = Get-Module -ListAvailable -SkipEditionCheck (Join-Path -Path $basePath -ChildPath $ModuleName)
|
||||
|
||||
$modules.Name | Should -Contain $ModuleName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Import-Module from CompatiblePSEditions-checked paths" -Tag "CI" {
|
||||
BeforeAll {
|
||||
$successCases = @(
|
||||
@{ Editions = "Core","Desktop"; ModuleName = "BothModule"; Result = $true },
|
||||
@{ Editions = "Core"; ModuleName = "CoreModule"; Result = $true }
|
||||
)
|
||||
|
||||
$failCases = @(
|
||||
@{ Editions = "Desktop"; ModuleName = "DesktopModule"; Result = $true },
|
||||
@{ Editions = $null; ModuleName = "NeitherModule"; Result = $true }
|
||||
)
|
||||
|
||||
$basePath = Join-Path $TestDrive "EditionCompatibleModules"
|
||||
New-TestModules -TestCases $successCases -BaseDir $basePath
|
||||
New-TestModules -TestCases $failCases -BaseDir $basePath
|
||||
|
||||
$allModules = ($successCases + $failCases).ModuleName
|
||||
|
||||
# Emulate the System32 module path for tests
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $basePath)
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $null)
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Get-Module $allModules | Remove-Module -Force
|
||||
}
|
||||
|
||||
Context "Imports from module path" {
|
||||
BeforeAll {
|
||||
Add-ModulePath $basePath
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
Restore-ModulePath
|
||||
}
|
||||
|
||||
It "Successfully imports compatible modules from the module path with PSEdition <Editions>" -TestCases $successCases -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName, $Result)
|
||||
|
||||
Import-Module $ModuleName -Force
|
||||
& "Test-$ModuleName" | Should -Be $Result
|
||||
}
|
||||
|
||||
It "Fails to import incompatible modules from the module path with PSEdition <Editions>" -TestCases $failCases -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName, $Result)
|
||||
|
||||
{
|
||||
Import-Module $ModuleName -Force -ErrorAction 'Stop'; & "Test-$ModuleName"
|
||||
} | Should -Throw -ErrorId "Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
}
|
||||
|
||||
It "Imports an incompatible module from the module path with -SkipEditionCheck with PSEdition <Editions>" -TestCases ($successCases + $failCases) -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName, $Result)
|
||||
|
||||
Import-Module $ModuleName -SkipEditionCheck -Force
|
||||
& "Test-$ModuleName" | Should -Be $Result
|
||||
}
|
||||
}
|
||||
|
||||
Context "Imports from absolute path" {
|
||||
It "Successfully imports compatible modules from an absolute path with PSEdition <Editions>" -TestCases $successCases -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName, $Result)
|
||||
|
||||
$path = Join-Path -Path $basePath -ChildPath $ModuleName
|
||||
|
||||
Import-Module $path -Force
|
||||
& "Test-$ModuleName" | Should -Be $Result
|
||||
}
|
||||
|
||||
It "Fails to import incompatible modules from an absolute path with PSEdition <Editions>" -TestCases $failCases -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName, $Result)
|
||||
|
||||
$path = Join-Path -Path $basePath -ChildPath $ModuleName
|
||||
|
||||
{
|
||||
Import-Module $path -Force -ErrorAction 'Stop'; & "Test-$ModuleName"
|
||||
} | Should -Throw -ErrorId "Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
}
|
||||
|
||||
It "Imports an incompatible module from an absolute path with -SkipEditionCheck with PSEdition <Editions>" -TestCases ($successCases + $failCases) -Skip:(-not $IsWindows) {
|
||||
param($Editions, $ModuleName, $Result)
|
||||
|
||||
$path = Join-Path -Path $basePath -ChildPath $ModuleName
|
||||
|
||||
Import-Module $path -SkipEditionCheck -Force
|
||||
& "Test-$ModuleName" | Should -Be $Result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "PSModulePath changes interacting with other PowerShell processes" -Tag "Feature" {
|
||||
BeforeAll {
|
||||
if (-not $IsWindows)
|
||||
{
|
||||
return
|
||||
}
|
||||
Add-ModulePath (Join-Path $env:windir "System32\WindowsPowerShell\v1.0\Modules") -Prepend
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if (-not $IsWindows)
|
||||
{
|
||||
return
|
||||
}
|
||||
Restore-ModulePath
|
||||
}
|
||||
|
||||
It "Allows Windows PowerShell subprocesses to call `$PSHome modules still" -Skip:(-not $IsWindows) {
|
||||
$errors = powershell.exe -Command "Get-ChildItem" 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }
|
||||
$errors | Should -Be $null
|
||||
}
|
||||
|
||||
It "Allows PowerShell Core 6 subprocesses to call core modules" -Skip:(-not $IsWindows) {
|
||||
$errors = pwsh.exe -Command "Get-ChildItem" 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }
|
||||
$errors | Should -Be $null
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Get-Module nested module behaviour with Edition checking" -Tag "Feature" {
|
||||
BeforeAll {
|
||||
$testConditions = @{
|
||||
SkipEditionCheck = @($true, $false)
|
||||
UseRootModule = @($true, $false)
|
||||
UseAbsolutePath = @($true, $false)
|
||||
MarkedEdition = @($null, "Desktop", "Core", @("Desktop","Core"))
|
||||
}
|
||||
|
||||
# Combine all the test conditions into a list of test cases
|
||||
$testCases = @(@{})
|
||||
foreach ($condition in $testConditions.Keys)
|
||||
{
|
||||
$list = [System.Collections.Generic.List[hashtable]]::new()
|
||||
foreach ($obj in $testCases)
|
||||
{
|
||||
foreach ($value in $testConditions[$condition])
|
||||
{
|
||||
$list.Add($obj + @{ $condition = $value })
|
||||
}
|
||||
}
|
||||
$testCases = $list
|
||||
}
|
||||
|
||||
# Define nested script module
|
||||
$scriptModuleName = "NestedScriptModule"
|
||||
$scriptModuleFile = "$scriptModuleName.psm1"
|
||||
$scriptModuleContent = 'function Test-ScriptModule { return $true }'
|
||||
|
||||
# Define nested binary module
|
||||
$binaryModuleName = "NestedBinaryModule"
|
||||
$binaryModuleFile = "$binaryModuleName.dll"
|
||||
$binaryModuleContent = 'public static class TestBinaryModuleClass { public static bool Test() { return true; } }'
|
||||
$binaryModuleSourcePath = Join-Path $TestDrive $binaryModuleFile
|
||||
Add-Type -OutputAssembly $binaryModuleSourcePath -TypeDefinition $binaryModuleContent
|
||||
|
||||
# Define root module definition
|
||||
$rootModuleName = "RootModule"
|
||||
$rootModuleFile = "$rootModuleName.psm1"
|
||||
$rootModuleContent = 'function Test-RootModule { Test-ScriptModule }'
|
||||
|
||||
# Module directory structure: $TestDrive/$compatibility/$guid/$moduleName/{module parts}
|
||||
$compatibleDir = "Compatible"
|
||||
$incompatibleDir = "Incompatible"
|
||||
$compatiblePath = Join-Path $TestDrive $compatibleDir
|
||||
$incompatiblePath = Join-Path $TestDrive $incompatibleDir
|
||||
|
||||
foreach ($basePath in $compatiblePath,$incompatiblePath)
|
||||
{
|
||||
New-Item -Path $basePath -ItemType Directory
|
||||
}
|
||||
}
|
||||
|
||||
Context "Modules ON the System32 test path" {
|
||||
BeforeAll {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $incompatiblePath)
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $null)
|
||||
}
|
||||
|
||||
BeforeEach {
|
||||
# Create the module directory
|
||||
$guid = New-Guid
|
||||
$compatibilityDir = $incompatibleDir
|
||||
$containingDir = Join-Path $TestDrive $compatibilityDir $guid
|
||||
$moduleName = "CpseTestModule"
|
||||
$moduleBase = Join-Path $containingDir $moduleName
|
||||
New-Item -Path $moduleBase -ItemType Directory
|
||||
Add-ModulePath $containingDir
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Restore-ModulePath
|
||||
}
|
||||
|
||||
It "Get-Module -ListAvailable gets all compatible modules when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases -Skip:(-not $IsWindows) {
|
||||
param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition)
|
||||
|
||||
New-TestNestedModule `
|
||||
-ModuleBase $moduleBase `
|
||||
-ScriptModuleFilename $scriptModuleFile `
|
||||
-ScriptModuleContent $scriptModuleContent `
|
||||
-BinaryModuleFilename $binaryModuleFile `
|
||||
-BinaryModuleDllPath $binaryModuleSourcePath `
|
||||
-RootModuleFilename $rootModuleFile `
|
||||
-RootModuleContent $rootModuleContent `
|
||||
-CompatiblePSEditions $MarkedEdition `
|
||||
-UseRootModule $UseRootModule `
|
||||
-UseAbsolutePath $UseAbsolutePath
|
||||
|
||||
if ($UseAbsolutePath)
|
||||
{
|
||||
if ((-not $SkipEditionCheck) -and (-not ($MarkedEdition -contains "Core")))
|
||||
{
|
||||
Get-Module -ListAvailable $moduleBase -ErrorAction Stop | Should -Be $null
|
||||
return
|
||||
}
|
||||
|
||||
$modules = if ($SkipEditionCheck)
|
||||
{
|
||||
Get-Module -ListAvailable $moduleBase -SkipEditionCheck
|
||||
}
|
||||
else
|
||||
{
|
||||
Get-Module -ListAvailable $moduleBase
|
||||
}
|
||||
|
||||
$modules.Count | Should -Be 1
|
||||
$modules[0].Name | Should -Be $moduleName
|
||||
return
|
||||
}
|
||||
|
||||
$modules = if ($SkipEditionCheck)
|
||||
{
|
||||
Get-Module -ListAvailable -SkipEditionCheck
|
||||
}
|
||||
else
|
||||
{
|
||||
Get-Module -ListAvailable
|
||||
}
|
||||
|
||||
$modules = $modules | Where-Object { $_.Path.Contains($guid) }
|
||||
|
||||
if ((-not $SkipEditionCheck) -and (-not ($MarkedEdition -contains "Core")))
|
||||
{
|
||||
$modules.Count | Should -Be 0
|
||||
return
|
||||
}
|
||||
|
||||
$modules.Count | Should -Be 1
|
||||
$modules[0].Name | Should -Be $moduleName
|
||||
}
|
||||
|
||||
It "Get-Module -ListAvailable -All gets all compatible modules when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases -Skip:(-not $IsWindows){
|
||||
param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition)
|
||||
|
||||
New-TestNestedModule `
|
||||
-ModuleBase $moduleBase `
|
||||
-ScriptModuleFilename $scriptModuleFile `
|
||||
-ScriptModuleContent $scriptModuleContent `
|
||||
-BinaryModuleFilename $binaryModuleFile `
|
||||
-BinaryModuleDllPath $binaryModuleSourcePath `
|
||||
-RootModuleFilename $rootModuleFile `
|
||||
-RootModuleContent $rootModuleContent `
|
||||
-CompatiblePSEditions $MarkedEdition `
|
||||
-UseRootModule $UseRootModule `
|
||||
-UseAbsolutePath $UseAbsolutePath
|
||||
|
||||
# Modules specified with an absolute path should only return themselves
|
||||
if ($UseAbsolutePath) {
|
||||
$modules = if ($SkipEditionCheck)
|
||||
{
|
||||
Get-Module -ListAvailable -All -SkipEditionCheck $moduleBase
|
||||
}
|
||||
else
|
||||
{
|
||||
Get-Module -ListAvailable -All $moduleBase
|
||||
}
|
||||
|
||||
$modules.Count | Should -Be 1
|
||||
$modules[0].Name | Should -BeExactly $moduleName
|
||||
return
|
||||
}
|
||||
|
||||
$modules = if ($SkipEditionCheck)
|
||||
{
|
||||
Get-Module -ListAvailable -All -SkipEditionCheck | Where-Object { $_.Path.Contains($guid) }
|
||||
}
|
||||
else
|
||||
{
|
||||
Get-Module -ListAvailable -All | Where-Object { $_.Path.Contains($guid) }
|
||||
}
|
||||
|
||||
if ($UseRootModule)
|
||||
{
|
||||
$modules.Count | Should -Be 4
|
||||
}
|
||||
else
|
||||
{
|
||||
$modules.Count | Should -Be 3
|
||||
}
|
||||
|
||||
$names = $modules.Name
|
||||
$names | Should -Contain $moduleName
|
||||
$names | Should -Contain $scriptModuleName
|
||||
$names | Should -Contain $binaryModuleName
|
||||
}
|
||||
}
|
||||
|
||||
Context "Modules OFF the System32 module path" {
|
||||
BeforeEach {
|
||||
# Create the module directory
|
||||
$guid = New-Guid
|
||||
$compatibilityDir = $compatibleDir
|
||||
$containingDir = Join-Path $TestDrive $compatibilityDir $guid
|
||||
$moduleName = "CpseTestModule"
|
||||
$moduleBase = Join-Path $containingDir $moduleName
|
||||
New-Item -Path $moduleBase -ItemType Directory
|
||||
Add-ModulePath $containingDir
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Restore-ModulePath
|
||||
}
|
||||
|
||||
It "Get-Module -ListAvailable gets all compatible modules when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases {
|
||||
param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition)
|
||||
|
||||
New-TestNestedModule `
|
||||
-ModuleBase $moduleBase `
|
||||
-ScriptModuleFilename $scriptModuleFile `
|
||||
-ScriptModuleContent $scriptModuleContent `
|
||||
-BinaryModuleFilename $binaryModuleFile `
|
||||
-BinaryModuleDllPath $binaryModuleSourcePath `
|
||||
-RootModuleFilename $rootModuleFile `
|
||||
-RootModuleContent $rootModuleContent `
|
||||
-CompatiblePSEditions $MarkedEdition `
|
||||
-UseRootModule $UseRootModule `
|
||||
-UseAbsolutePath $UseAbsolutePath
|
||||
|
||||
if ($UseAbsolutePath)
|
||||
{
|
||||
$modules = if ($SkipEditionCheck)
|
||||
{
|
||||
Get-Module -ListAvailable $moduleBase -SkipEditionCheck
|
||||
}
|
||||
else
|
||||
{
|
||||
Get-Module -ListAvailable $moduleBase
|
||||
}
|
||||
|
||||
$modules.Count | Should -Be 1
|
||||
$modules[0].Name | Should -Be $moduleName
|
||||
return
|
||||
}
|
||||
|
||||
$modules = if ($SkipEditionCheck)
|
||||
{
|
||||
Get-Module -ListAvailable -SkipEditionCheck
|
||||
}
|
||||
else
|
||||
{
|
||||
Get-Module -ListAvailable
|
||||
}
|
||||
|
||||
$modules = $modules | Where-Object { $_.Path.Contains($guid) }
|
||||
$modules.Count | Should -Be 1
|
||||
$modules[0].Name | Should -Be $moduleName
|
||||
}
|
||||
|
||||
It "Get-Module -ListAvailable -All gets all compatible modules when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases {
|
||||
param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition)
|
||||
|
||||
New-TestNestedModule `
|
||||
-ModuleBase $moduleBase `
|
||||
-ScriptModuleFilename $scriptModuleFile `
|
||||
-ScriptModuleContent $scriptModuleContent `
|
||||
-BinaryModuleFilename $binaryModuleFile `
|
||||
-BinaryModuleDllPath $binaryModuleSourcePath `
|
||||
-RootModuleFilename $rootModuleFile `
|
||||
-RootModuleContent $rootModuleContent `
|
||||
-CompatiblePSEditions $MarkedEdition `
|
||||
-UseRootModule $UseRootModule `
|
||||
-UseAbsolutePath $UseAbsolutePath
|
||||
|
||||
# Modules specified with an absolute path should only return themselves
|
||||
if ($UseAbsolutePath)
|
||||
{
|
||||
$modules = Get-Module -ListAvailable -All $moduleBase
|
||||
|
||||
$modules.Count | Should -Be 1
|
||||
$modules[0].Name | Should -BeExactly $moduleName
|
||||
return
|
||||
}
|
||||
|
||||
$modules = if ($SkipEditionCheck)
|
||||
{
|
||||
Get-Module -ListAvailable -All -SkipEditionCheck | Where-Object { $_.Path.Contains($guid) }
|
||||
}
|
||||
else
|
||||
{
|
||||
Get-Module -ListAvailable -All | Where-Object { $_.Path.Contains($guid) }
|
||||
}
|
||||
|
||||
if ($UseRootModule)
|
||||
{
|
||||
$modules.Count | Should -Be 4
|
||||
}
|
||||
else
|
||||
{
|
||||
$modules.Count | Should -Be 3
|
||||
}
|
||||
|
||||
$names = $modules.Name
|
||||
$names | Should -Contain $moduleName
|
||||
$names | Should -Contain $scriptModuleName
|
||||
$names | Should -Contain $binaryModuleName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Import-Module nested module behaviour with Edition checking" -Tag "Feature" {
|
||||
BeforeAll {
|
||||
$testConditions = @{
|
||||
SkipEditionCheck = @($true, $false)
|
||||
UseRootModule = @($true, $false)
|
||||
UseAbsolutePath = @($true, $false)
|
||||
MarkedEdition = @($null, "Desktop", "Core", @("Desktop","Core"))
|
||||
}
|
||||
|
||||
# Combine all the test conditions into a list of test cases
|
||||
$testCases = @(@{})
|
||||
foreach ($condition in $testConditions.Keys)
|
||||
{
|
||||
$list = [System.Collections.Generic.List[hashtable]]::new()
|
||||
foreach ($obj in $testCases)
|
||||
{
|
||||
foreach ($value in $testConditions[$condition])
|
||||
{
|
||||
$list.Add($obj + @{ $condition = $value })
|
||||
}
|
||||
}
|
||||
$testCases = $list
|
||||
}
|
||||
|
||||
# Define nested script module
|
||||
$scriptModuleName = "NestedScriptModule"
|
||||
$scriptModuleFile = "$scriptModuleName.psm1"
|
||||
$scriptModuleContent = 'function Test-ScriptModule { return $true }'
|
||||
|
||||
# Define root module definition
|
||||
$rootModuleName = "RootModule"
|
||||
$rootModuleFile = "$rootModuleName.psm1"
|
||||
$rootModuleContent = 'function Test-RootModule { Test-ScriptModule }'
|
||||
|
||||
# Module directory structure: $TestDrive/$compatibility/$guid/$moduleName/{module parts}
|
||||
$compatibleDir = "Compatible"
|
||||
$incompatibleDir = "Incompatible"
|
||||
$compatiblePath = Join-Path $TestDrive $compatibleDir
|
||||
$incompatiblePath = Join-Path $TestDrive $incompatibleDir
|
||||
|
||||
foreach ($basePath in $compatiblePath,$incompatiblePath)
|
||||
{
|
||||
New-Item -Path $basePath -ItemType Directory
|
||||
}
|
||||
}
|
||||
|
||||
Context "Modules ON the System32 test path" {
|
||||
BeforeAll {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $incompatiblePath)
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("TestWindowsPowerShellPSHomeLocation", $null)
|
||||
}
|
||||
|
||||
BeforeEach {
|
||||
# Create the module directory
|
||||
$guid = New-Guid
|
||||
$compatibilityDir = $incompatibleDir
|
||||
$containingDir = Join-Path $TestDrive $compatibilityDir $guid
|
||||
$moduleName = "CpseTestModule"
|
||||
$moduleBase = Join-Path $containingDir $moduleName
|
||||
New-Item -Path $moduleBase -ItemType Directory
|
||||
Add-ModulePath $containingDir
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Get-Module $moduleName | Remove-Module -Force
|
||||
Restore-ModulePath
|
||||
}
|
||||
|
||||
It "Import-Module when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases -Skip:(-not $IsWindows) {
|
||||
param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition)
|
||||
|
||||
New-TestNestedModule `
|
||||
-ModuleBase $moduleBase `
|
||||
-ScriptModuleFilename $scriptModuleFile `
|
||||
-ScriptModuleContent $scriptModuleContent `
|
||||
-RootModuleFilename $rootModuleFile `
|
||||
-RootModuleContent $rootModuleContent `
|
||||
-CompatiblePSEditions $MarkedEdition `
|
||||
-UseRootModule $UseRootModule `
|
||||
-UseAbsolutePath $UseAbsolutePath
|
||||
|
||||
if ($UseAbsolutePath)
|
||||
{
|
||||
if ((-not $SkipEditionCheck) -and (-not ($MarkedEdition -contains "Core")))
|
||||
{
|
||||
{
|
||||
Import-Module $moduleBase -ErrorAction Stop
|
||||
} | Should -Throw -ErrorId "Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
return
|
||||
}
|
||||
|
||||
if ($SkipEditionCheck)
|
||||
{
|
||||
Import-Module $moduleBase -SkipEditionCheck
|
||||
}
|
||||
else
|
||||
{
|
||||
Import-Module $moduleBase
|
||||
}
|
||||
|
||||
if ($UseRootModule)
|
||||
{
|
||||
Test-RootModule | Should -BeTrue
|
||||
{ Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException"
|
||||
return
|
||||
}
|
||||
|
||||
Test-ScriptModule | Should -BeTrue
|
||||
{ Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException"
|
||||
return
|
||||
}
|
||||
|
||||
if ((-not $SkipEditionCheck) -and (-not ($MarkedEdition -contains "Core")))
|
||||
{
|
||||
{
|
||||
Import-Module $moduleName -ErrorAction Stop
|
||||
} | Should -Throw -ErrorId "Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
return
|
||||
}
|
||||
|
||||
if ($SkipEditionCheck)
|
||||
{
|
||||
Import-Module $moduleName -SkipEditionCheck
|
||||
}
|
||||
else
|
||||
{
|
||||
Import-Module $moduleName
|
||||
}
|
||||
|
||||
if ($UseRootModule)
|
||||
{
|
||||
Test-RootModule | Should -BeTrue
|
||||
{ Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException"
|
||||
return
|
||||
}
|
||||
|
||||
Test-ScriptModule | Should -BeTrue
|
||||
{ Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException"
|
||||
}
|
||||
}
|
||||
|
||||
Context "Modules OFF the System32 module path" {
|
||||
BeforeEach {
|
||||
# Create the module directory
|
||||
$guid = New-Guid
|
||||
$compatibilityDir = $compatibleDir
|
||||
$containingDir = Join-Path $TestDrive $compatibilityDir $guid
|
||||
$moduleName = "CpseTestModule"
|
||||
$moduleBase = Join-Path $containingDir $moduleName
|
||||
New-Item -Path $moduleBase -ItemType Directory
|
||||
Add-ModulePath $containingDir
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Get-Module $moduleName | Remove-Module -Force
|
||||
Restore-ModulePath
|
||||
}
|
||||
|
||||
It "Import-Module when SkipEditionCheck: <SkipEditionCheck>, using root module: <UseRootModule>, using absolute path: <UseAbsolutePath>, CompatiblePSEditions: <MarkedEdition>" -TestCases $testCases {
|
||||
param([bool]$SkipEditionCheck, [bool]$UseRootModule, [bool]$UseAbsolutePath, [string[]]$MarkedEdition)
|
||||
|
||||
New-TestNestedModule `
|
||||
-ModuleBase $moduleBase `
|
||||
-ScriptModuleFilename $scriptModuleFile `
|
||||
-ScriptModuleContent $scriptModuleContent `
|
||||
-RootModuleFilename $rootModuleFile `
|
||||
-RootModuleContent $rootModuleContent `
|
||||
-CompatiblePSEditions $MarkedEdition `
|
||||
-UseRootModule $UseRootModule `
|
||||
-UseAbsolutePath $UseAbsolutePath
|
||||
|
||||
if ($UseAbsolutePath)
|
||||
{
|
||||
if ($SkipEditionCheck)
|
||||
{
|
||||
Import-Module $moduleBase -SkipEditionCheck
|
||||
}
|
||||
else
|
||||
{
|
||||
Import-Module $moduleBase
|
||||
}
|
||||
}
|
||||
elseif ($SkipEditionCheck)
|
||||
{
|
||||
Import-Module $moduleName -SkipEditionCheck
|
||||
}
|
||||
else
|
||||
{
|
||||
Import-Module $moduleName
|
||||
}
|
||||
|
||||
if ($UseRootModule)
|
||||
{
|
||||
Test-RootModule | Should -BeTrue
|
||||
{ Test-ScriptModule } | Should -Throw -ErrorId "CommandNotFoundException"
|
||||
return
|
||||
}
|
||||
|
||||
Test-ScriptModule | Should -BeTrue
|
||||
{ Test-RootModule } | Should -Throw -ErrorId "CommandNotFoundException"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,26 +99,35 @@ Describe "Import-Module with ScriptsToProcess" -Tags "CI" {
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Import-Module for Binary Modules in GAC" -Tags 'CI' {
|
||||
Describe "Import-Module for Binary Modules in GAC" -Tags 'Feature' {
|
||||
Context "Modules are not loaded from GAC" {
|
||||
BeforeAll {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('DisableGACLoading', $true)
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('DisableGACLoading', $false)
|
||||
}
|
||||
|
||||
It "Load PSScheduledJob from Windows Powershell Modules folder should fail" -Skip:(-not $IsWindows) {
|
||||
$modulePath = Join-Path $env:windir "System32/WindowsPowershell/v1.0/Modules/PSScheduledJob"
|
||||
{ Import-Module $modulePath -ErrorAction SilentlyContinue } | Should -Throw -ErrorId 'FormatXmlUpdateException,Microsoft.PowerShell.Commands.ImportModuleCommand'
|
||||
# NOTE:
|
||||
# This test attempts to load an assembly from the GAC and expects to fail.
|
||||
# However, if the assembly is already loaded, the assembly will be resolved before
|
||||
# the DisableGACLoading flag comes into effect, meaning no error is thrown and the test fails.
|
||||
#
|
||||
# Since the System32 module direction is now on the module path and Get-Module -ListAvailable
|
||||
# loads assemblies, the assembly is resolved and this test will fail if any other tests before
|
||||
# it call Get-Module -ListAvailable.
|
||||
#
|
||||
# So for now, the solution is to run this test in a fresh process.
|
||||
# Since disabling GAC loading is supposed to be a startup option, this is possibly appropriate.
|
||||
|
||||
$testScript = {
|
||||
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('DisableGACLoading', $true)
|
||||
$modulePath = Join-Path $env:windir "System32/WindowsPowershell/v1.0/Modules/PSScheduledJob"
|
||||
Import-Module $modulePath -SkipEditionCheck
|
||||
}
|
||||
$err = (& "$PSHOME\pwsh.exe" -c $testScript 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] })[0]
|
||||
$err.FullyQualifiedErrorId | Should -Be "FormatXmlUpdateException,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
}
|
||||
}
|
||||
|
||||
Context "Modules are loaded from GAC" {
|
||||
It "Load PSScheduledJob from Windows Powershell Modules folder" -Skip:(-not $IsWindows) {
|
||||
$modulePath = Join-Path $env:windir "System32/WindowsPowershell/v1.0/Modules/PSScheduledJob"
|
||||
Import-Module $modulePath
|
||||
Import-Module $modulePath -SkipEditionCheck
|
||||
(Get-Command New-JobTrigger).Name | Should -BeExactly 'New-JobTrigger'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,21 @@ Describe 'Get-WinEvent' -Tags "CI" {
|
||||
It 'Get-WinEvent can use the simplest of filters' {
|
||||
$filter = @{ ProviderName = $providerForTests.Name }
|
||||
$testEvents = Get-WinEvent -filterhashtable $filter
|
||||
|
||||
$testEventDict = [System.Collections.Generic.Dictionary[int, System.Diagnostics.Eventing.Reader.EventLogRecord]]::new()
|
||||
foreach ($te in $testEvents)
|
||||
{
|
||||
$testEventDict.TryAdd($te.Id, $te)
|
||||
}
|
||||
|
||||
foreach ($e in $events)
|
||||
{
|
||||
if (-not $testEventDict.ContainsKey($e.Id))
|
||||
{
|
||||
throw new "Unexpected event log: $e"
|
||||
}
|
||||
}
|
||||
|
||||
$testEvents.Count | Should -Be $events.Count
|
||||
}
|
||||
It 'Get-WinEvent can use a filter which includes two items' {
|
||||
|
||||
@@ -23,6 +23,11 @@ Describe "SxS Module Path Basic Tests" -tags "CI" {
|
||||
}
|
||||
$expectedSystemPath = Join-Path -Path $PSHOME -ChildPath 'Modules'
|
||||
|
||||
if ($IsWindows)
|
||||
{
|
||||
$expectedWindowsPowerShellPSHomePath = Join-Path $env:windir "System32" "WindowsPowerShell" "v1.0" "Modules"
|
||||
}
|
||||
|
||||
## Setup a fake PSHome
|
||||
$fakePSHome = Join-Path -Path $TestDrive -ChildPath 'FakePSHome'
|
||||
$fakePSHomeModuleDir = Join-Path -Path $fakePSHome -ChildPath 'Modules'
|
||||
@@ -48,10 +53,22 @@ Describe "SxS Module Path Basic Tests" -tags "CI" {
|
||||
|
||||
$paths = $defaultModulePath -split [System.IO.Path]::PathSeparator
|
||||
|
||||
$paths.Count | Should -Be 3
|
||||
if ($IsWindows)
|
||||
{
|
||||
$paths.Count | Should -Be 4
|
||||
}
|
||||
else
|
||||
{
|
||||
$paths.Count | Should -Be 3
|
||||
}
|
||||
|
||||
$paths[0].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedUserPath
|
||||
$paths[1].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedSharedPath
|
||||
$paths[2].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedSystemPath
|
||||
if ($IsWindows)
|
||||
{
|
||||
$paths[3].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedWindowsPowerShellPSHomePath
|
||||
}
|
||||
}
|
||||
|
||||
It "ignore pshome module path derived from a different powershell core instance" -Skip:(!$IsCoreCLR) {
|
||||
@@ -68,10 +85,22 @@ Describe "SxS Module Path Basic Tests" -tags "CI" {
|
||||
$newModulePath = & $powershell -nopro -c '$env:PSModulePath'
|
||||
$paths = $newModulePath -split [System.IO.Path]::PathSeparator
|
||||
|
||||
$paths.Count | Should -Be 3
|
||||
if ($IsWindows)
|
||||
{
|
||||
$paths.Count | Should -Be 4
|
||||
}
|
||||
else
|
||||
{
|
||||
$paths.Count | Should -Be 3
|
||||
}
|
||||
|
||||
$paths[0] | Should -Be $expectedUserPath
|
||||
$paths[1] | Should -Be $expectedSharedPath
|
||||
$paths[2] | Should -Be $expectedSystemPath
|
||||
if ($IsWindows)
|
||||
{
|
||||
$paths[3].TrimEnd([System.IO.Path]::DirectorySeparatorChar) | Should -Be $expectedWindowsPowerShellPSHomePath
|
||||
}
|
||||
|
||||
} finally {
|
||||
|
||||
@@ -89,7 +118,14 @@ Describe "SxS Module Path Basic Tests" -tags "CI" {
|
||||
$newModulePath = & $powershell -nopro -c '$env:PSModulePath'
|
||||
$paths = $newModulePath -split [System.IO.Path]::PathSeparator
|
||||
|
||||
$paths.Count | Should -Be 5
|
||||
if ($IsWindows)
|
||||
{
|
||||
$paths.Count | Should -Be 6
|
||||
}
|
||||
else
|
||||
{
|
||||
$paths.Count | Should -Be 5
|
||||
}
|
||||
$paths -contains $fakePSHomeModuleDir | Should -BeTrue
|
||||
$paths -contains $customeModules | Should -BeTrue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user