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:
Robert Holt
2018-07-16 19:40:46 -07:00
committed by Travis Plunk
parent da7b5216b5
commit e857135d3c
17 changed files with 1339 additions and 79 deletions
@@ -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
}