mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Report error when PowerShell built-in modules are missing (#16628)
This commit is contained in:
@@ -376,22 +376,16 @@ namespace System.Management.Automation
|
||||
|
||||
foreach (var requiresPSSnapIn in requiresPSSnapIns)
|
||||
{
|
||||
IEnumerable<PSSnapInInfo> loadedPSSnapIns = null;
|
||||
loadedPSSnapIns = context.InitialSessionState.GetPSSnapIn(requiresPSSnapIn.Name);
|
||||
if (loadedPSSnapIns == null || !loadedPSSnapIns.Any())
|
||||
var loadedPSSnapIn = context.InitialSessionState.GetPSSnapIn(requiresPSSnapIn.Name);
|
||||
if (loadedPSSnapIn is null)
|
||||
{
|
||||
if (requiresMissingPSSnapIns == null)
|
||||
{
|
||||
requiresMissingPSSnapIns = new Collection<string>();
|
||||
}
|
||||
|
||||
requiresMissingPSSnapIns ??= new Collection<string>();
|
||||
requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn));
|
||||
}
|
||||
else
|
||||
{
|
||||
// the requires PSSnapin is loaded. now check the PSSnapin version
|
||||
PSSnapInInfo loadedPSSnapIn = loadedPSSnapIns.First();
|
||||
Diagnostics.Assert(loadedPSSnapIn.Version != null,
|
||||
Dbg.Assert(loadedPSSnapIn.Version != null,
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Version is null for loaded PSSnapin {0}.", loadedPSSnapIn));
|
||||
@@ -400,11 +394,7 @@ namespace System.Management.Automation
|
||||
if (!AreInstalledRequiresVersionsCompatible(
|
||||
requiresPSSnapIn.Version, loadedPSSnapIn.Version))
|
||||
{
|
||||
if (requiresMissingPSSnapIns == null)
|
||||
{
|
||||
requiresMissingPSSnapIns = new Collection<string>();
|
||||
}
|
||||
|
||||
requiresMissingPSSnapIns ??= new Collection<string>();
|
||||
requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn));
|
||||
}
|
||||
}
|
||||
@@ -1063,14 +1053,12 @@ namespace System.Management.Automation
|
||||
return null;
|
||||
|
||||
CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Get-Module");
|
||||
if ((commandOrigin == CommandOrigin.Internal) ||
|
||||
((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public)))
|
||||
if (commandOrigin == CommandOrigin.Internal || cmdletInfo?.Visibility == SessionStateEntryVisibility.Public)
|
||||
{
|
||||
// Search for a module with a matching command, as long as the user would have the ability to
|
||||
// import the module.
|
||||
cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module");
|
||||
if (((commandOrigin == CommandOrigin.Internal) ||
|
||||
((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public))))
|
||||
if (commandOrigin == CommandOrigin.Internal || cmdletInfo?.Visibility == SessionStateEntryVisibility.Public)
|
||||
{
|
||||
discoveryTracer.WriteLine("Executing non module-qualified search: {0}", commandName);
|
||||
context.CommandDiscovery.RegisterLookupCommandInfoAction("ActiveModuleSearch", commandName);
|
||||
@@ -1085,30 +1073,33 @@ namespace System.Management.Automation
|
||||
{
|
||||
// WinBlue:69141 - We need to get the full path here because the module path might be C:\Users\User1\DOCUME~1
|
||||
// While the exportedCommands are cached, they are cached with the full path
|
||||
string expandedModulePath = IO.Path.GetFullPath(modulePath);
|
||||
string moduleShortName = System.IO.Path.GetFileNameWithoutExtension(expandedModulePath);
|
||||
string expandedModulePath = Path.GetFullPath(modulePath);
|
||||
string moduleShortName = Path.GetFileNameWithoutExtension(expandedModulePath);
|
||||
var exportedCommands = AnalysisCache.GetExportedCommands(expandedModulePath, false, context);
|
||||
|
||||
if (exportedCommands == null) { continue; }
|
||||
|
||||
CommandTypes exportedCommandTypes;
|
||||
// Skip if module only has class or other types and no commands.
|
||||
if (exportedCommands.TryGetValue(commandName, out exportedCommandTypes))
|
||||
if (exportedCommands.TryGetValue(commandName, out CommandTypes exportedCommandTypes))
|
||||
{
|
||||
Exception exception;
|
||||
discoveryTracer.WriteLine("Found in module: {0}", expandedModulePath);
|
||||
Collection<PSModuleInfo> matchingModule = AutoloadSpecifiedModule(expandedModulePath, context,
|
||||
Collection<PSModuleInfo> matchingModule = AutoloadSpecifiedModule(
|
||||
expandedModulePath,
|
||||
context,
|
||||
cmdletInfo != null ? cmdletInfo.Visibility : SessionStateEntryVisibility.Private,
|
||||
out exception);
|
||||
lastError = exception;
|
||||
if ((matchingModule == null) || (matchingModule.Count == 0))
|
||||
out lastError);
|
||||
|
||||
if (matchingModule is null || matchingModule.Count == 0)
|
||||
{
|
||||
string error = StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModule, commandName, moduleShortName);
|
||||
CommandNotFoundException commandNotFound = new CommandNotFoundException(
|
||||
string errorMessage = lastError is null
|
||||
? StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModule, commandName, moduleShortName)
|
||||
: StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModuleWithErrorMessage, commandName, moduleShortName, lastError.Message);
|
||||
|
||||
throw new CommandNotFoundException(
|
||||
originalCommandName,
|
||||
lastError,
|
||||
"CouldNotAutoloadMatchingModule", error);
|
||||
throw commandNotFound;
|
||||
"CouldNotAutoloadMatchingModule",
|
||||
errorMessage);
|
||||
}
|
||||
|
||||
result = LookupCommandInfo(commandName, commandTypes, searchResolutionOptions, commandOrigin, context);
|
||||
|
||||
@@ -1663,11 +1663,6 @@ namespace System.Management.Automation.Runspaces
|
||||
|
||||
ss.DisableFormatUpdates = this.DisableFormatUpdates;
|
||||
|
||||
foreach (var s in this.defaultSnapins)
|
||||
{
|
||||
ss.defaultSnapins.Add(s);
|
||||
}
|
||||
|
||||
foreach (var s in ImportedSnapins)
|
||||
{
|
||||
ss.ImportedSnapins.Add(s.Key, s.Value);
|
||||
@@ -3801,34 +3796,26 @@ namespace System.Management.Automation.Runspaces
|
||||
|
||||
// Now actually load the snapin...
|
||||
PSSnapInInfo snapin = ImportPSSnapIn(newPSSnapIn, out warning);
|
||||
if (snapin != null)
|
||||
{
|
||||
ImportedSnapins.Add(snapin.Name, snapin);
|
||||
}
|
||||
|
||||
return snapin;
|
||||
}
|
||||
|
||||
internal PSSnapInInfo ImportCorePSSnapIn()
|
||||
{
|
||||
// Load Microsoft.PowerShell.Core as a snapin
|
||||
// Load Microsoft.PowerShell.Core as a snapin.
|
||||
PSSnapInInfo coreSnapin = PSSnapInReader.ReadCoreEngineSnapIn();
|
||||
this.defaultSnapins.Add(coreSnapin);
|
||||
try
|
||||
{
|
||||
PSSnapInException warning;
|
||||
this.ImportPSSnapIn(coreSnapin, out warning);
|
||||
}
|
||||
catch (PSSnapInException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
ImportPSSnapIn(coreSnapin, out _);
|
||||
return coreSnapin;
|
||||
}
|
||||
|
||||
internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInException warning)
|
||||
{
|
||||
if (psSnapInInfo == null)
|
||||
{
|
||||
ArgumentNullException e = new ArgumentNullException(nameof(psSnapInInfo));
|
||||
throw e;
|
||||
}
|
||||
|
||||
// See if the snapin is already loaded. If has been then there will be an entry in the
|
||||
// Assemblies list for it already...
|
||||
bool reload = true;
|
||||
@@ -3861,12 +3848,6 @@ namespace System.Management.Automation.Runspaces
|
||||
Dictionary<string, List<SessionStateAliasEntry>> aliases = null;
|
||||
Dictionary<string, SessionStateProviderEntry> providers = null;
|
||||
|
||||
if (psSnapInInfo == null)
|
||||
{
|
||||
ArgumentNullException e = new ArgumentNullException(nameof(psSnapInInfo));
|
||||
throw e;
|
||||
}
|
||||
|
||||
Assembly assembly = null;
|
||||
string helpFile = null;
|
||||
|
||||
@@ -3985,37 +3966,18 @@ namespace System.Management.Automation.Runspaces
|
||||
}
|
||||
}
|
||||
|
||||
ImportedSnapins.Add(psSnapInInfo.Name, psSnapInInfo);
|
||||
return psSnapInInfo;
|
||||
}
|
||||
|
||||
internal List<PSSnapInInfo> GetPSSnapIn(string psSnapinName)
|
||||
internal PSSnapInInfo GetPSSnapIn(string psSnapinName)
|
||||
{
|
||||
List<PSSnapInInfo> loadedSnapins = null;
|
||||
foreach (var defaultSnapin in defaultSnapins)
|
||||
if (ImportedSnapins.TryGetValue(psSnapinName, out PSSnapInInfo importedSnapin))
|
||||
{
|
||||
if (defaultSnapin.Name.Equals(psSnapinName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (loadedSnapins == null)
|
||||
{
|
||||
loadedSnapins = new List<PSSnapInInfo>();
|
||||
}
|
||||
|
||||
loadedSnapins.Add(defaultSnapin);
|
||||
}
|
||||
return importedSnapin;
|
||||
}
|
||||
|
||||
PSSnapInInfo importedSnapin = null;
|
||||
if (ImportedSnapins.TryGetValue(psSnapinName, out importedSnapin))
|
||||
{
|
||||
if (loadedSnapins == null)
|
||||
{
|
||||
loadedSnapins = new List<PSSnapInInfo>();
|
||||
}
|
||||
|
||||
loadedSnapins.Add(importedSnapin);
|
||||
}
|
||||
|
||||
return loadedSnapins;
|
||||
return null;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")]
|
||||
@@ -4895,7 +4857,6 @@ end {
|
||||
|
||||
internal static readonly string CoreSnapin = "Microsoft.PowerShell.Core";
|
||||
internal static readonly string CoreModule = "Microsoft.PowerShell.Core";
|
||||
internal Collection<PSSnapInInfo> defaultSnapins = new Collection<PSSnapInInfo>();
|
||||
|
||||
// The list of engine modules to create warnings when you try to remove them
|
||||
internal static readonly HashSet<string> EngineModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
|
||||
@@ -636,6 +636,8 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModuleOptions, string name)
|
||||
{
|
||||
bool shallWriteError = !importModuleOptions.SkipSystem32ModulesAndSuppressError;
|
||||
|
||||
try
|
||||
{
|
||||
bool found = false;
|
||||
@@ -671,14 +673,14 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
|
||||
bool alreadyLoaded = false;
|
||||
var manifestProcessingFlags = ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.NullOnFirstError;
|
||||
if (shallWriteError)
|
||||
{
|
||||
manifestProcessingFlags |= ManifestProcessingFlags.WriteErrors;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(rootedPath))
|
||||
{
|
||||
// TODO/FIXME: use IsModuleAlreadyLoaded to get consistent behavior
|
||||
// TODO/FIXME: (for example checking ModuleType != Manifest below seems incorrect - cdxml modules also declare their own version)
|
||||
// PSModuleInfo alreadyLoadedModule = null;
|
||||
// TryGetFromModuleTable(rootedPath, out alreadyLoadedModule);
|
||||
// if (!BaseForce && IsModuleAlreadyLoaded(alreadyLoadedModule))
|
||||
|
||||
// If the module has already been loaded, just emit it and continue...
|
||||
if (!BaseForce && TryGetFromModuleTable(rootedPath, out PSModuleInfo module))
|
||||
{
|
||||
@@ -725,9 +727,14 @@ namespace Microsoft.PowerShell.Commands
|
||||
RemoveModule(moduleToRemove);
|
||||
}
|
||||
|
||||
foundModule = LoadModule(rootedPath, null, this.BasePrefix, null, ref importModuleOptions,
|
||||
ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
|
||||
out found);
|
||||
foundModule = LoadModule(
|
||||
fileName: rootedPath,
|
||||
moduleBase: null,
|
||||
prefix: BasePrefix,
|
||||
ss: null, /*SessionState*/
|
||||
ref importModuleOptions,
|
||||
manifestProcessingFlags,
|
||||
out found);
|
||||
}
|
||||
else if (Directory.Exists(rootedPath))
|
||||
{
|
||||
@@ -738,21 +745,24 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
|
||||
// Load the latest valid version if it is a multi-version module directory
|
||||
foundModule = LoadUsingMultiVersionModuleBase(rootedPath,
|
||||
ManifestProcessingFlags.LoadElements |
|
||||
ManifestProcessingFlags.WriteErrors |
|
||||
ManifestProcessingFlags.NullOnFirstError,
|
||||
importModuleOptions, out found);
|
||||
foundModule = LoadUsingMultiVersionModuleBase(rootedPath, manifestProcessingFlags, importModuleOptions, out found);
|
||||
|
||||
if (!found)
|
||||
{
|
||||
// If the path is a directory, double up the end of the string
|
||||
// then try to load that using extensions...
|
||||
rootedPath = Path.Combine(rootedPath, Path.GetFileName(rootedPath));
|
||||
foundModule = LoadUsingExtensions(null, rootedPath, rootedPath, null, null, this.BasePrefix, /*SessionState*/ null,
|
||||
importModuleOptions,
|
||||
ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
|
||||
out found);
|
||||
foundModule = LoadUsingExtensions(
|
||||
parentModule: null,
|
||||
moduleName: rootedPath,
|
||||
fileBaseName: rootedPath,
|
||||
extension: null,
|
||||
moduleBase: null,
|
||||
prefix: BasePrefix,
|
||||
ss: null, /*SessionState*/
|
||||
importModuleOptions,
|
||||
manifestProcessingFlags,
|
||||
out found);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -762,7 +772,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
// Check if module could be a snapin. This was the case for PowerShell version 2 engine modules.
|
||||
if (InitialSessionState.IsEngineModule(name))
|
||||
{
|
||||
PSSnapInInfo snapin = ModuleCmdletBase.GetEngineSnapIn(Context, name);
|
||||
PSSnapInInfo snapin = Context.CurrentRunspace.InitialSessionState.GetPSSnapIn(name);
|
||||
|
||||
// Return the command if we found a module
|
||||
if (snapin != null)
|
||||
@@ -786,16 +796,28 @@ namespace Microsoft.PowerShell.Commands
|
||||
// If there is no extension, we'll have to search using the extensions
|
||||
if (!string.IsNullOrEmpty(Path.GetExtension(name)))
|
||||
{
|
||||
foundModule = LoadModule(name, null, this.BasePrefix, null, ref importModuleOptions,
|
||||
ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
|
||||
out found);
|
||||
foundModule = LoadModule(
|
||||
fileName: name,
|
||||
moduleBase: null,
|
||||
prefix: BasePrefix,
|
||||
ss: null, /*SessionState*/
|
||||
ref importModuleOptions,
|
||||
manifestProcessingFlags,
|
||||
out found);
|
||||
}
|
||||
else
|
||||
{
|
||||
foundModule = LoadUsingExtensions(null, name, name, null, null, this.BasePrefix, /*SessionState*/ null,
|
||||
importModuleOptions,
|
||||
ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
|
||||
out found);
|
||||
foundModule = LoadUsingExtensions(
|
||||
parentModule: null,
|
||||
moduleName: name,
|
||||
fileBaseName: name,
|
||||
extension: null,
|
||||
moduleBase: null,
|
||||
prefix: BasePrefix,
|
||||
ss: null, /*SessionState*/
|
||||
importModuleOptions,
|
||||
manifestProcessingFlags,
|
||||
out found);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -807,14 +829,17 @@ namespace Microsoft.PowerShell.Commands
|
||||
this.AddToAppDomainLevelCache = true;
|
||||
}
|
||||
|
||||
found = LoadUsingModulePath(found, modulePath, name, /* SessionState*/ null,
|
||||
importModuleOptions,
|
||||
ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError,
|
||||
out foundModule);
|
||||
found = LoadUsingModulePath(
|
||||
modulePath,
|
||||
name,
|
||||
ss: null, /* SessionState*/
|
||||
importModuleOptions,
|
||||
manifestProcessingFlags,
|
||||
out foundModule);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
if (!found && shallWriteError)
|
||||
{
|
||||
ErrorRecord er = null;
|
||||
string message = null;
|
||||
@@ -856,8 +881,10 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
catch (PSInvalidOperationException e)
|
||||
{
|
||||
ErrorRecord er = new ErrorRecord(e.ErrorRecord, e);
|
||||
WriteError(er);
|
||||
if (shallWriteError)
|
||||
{
|
||||
WriteError(new ErrorRecord(e.ErrorRecord, e));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -1945,27 +1972,26 @@ namespace Microsoft.PowerShell.Commands
|
||||
return match;
|
||||
}
|
||||
|
||||
private List<T> FilterModuleCollection<T>(IEnumerable<T> moduleCollection)
|
||||
private IEnumerable<T> FilterModuleCollection<T>(IEnumerable<T> moduleCollection)
|
||||
{
|
||||
List<T> filteredModuleCollection = null;
|
||||
if (moduleCollection != null)
|
||||
if (moduleCollection is null)
|
||||
{
|
||||
// the ModuleDeny list is cached in PowerShellConfig object
|
||||
string[] moduleDenyList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityModuleDenyList();
|
||||
if (moduleDenyList?.Any() != true)
|
||||
return null;
|
||||
}
|
||||
|
||||
// The ModuleDeny list is cached in PowerShellConfig object
|
||||
string[] moduleDenyList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityModuleDenyList();
|
||||
if (moduleDenyList is null || moduleDenyList.Length == 0)
|
||||
{
|
||||
return moduleCollection;
|
||||
}
|
||||
|
||||
var filteredModuleCollection = new List<T>();
|
||||
foreach (var module in moduleCollection)
|
||||
{
|
||||
if (!IsModuleInDenyList(moduleDenyList, module as string, module as ModuleSpecification))
|
||||
{
|
||||
filteredModuleCollection = new List<T>(moduleCollection);
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredModuleCollection = new List<T>();
|
||||
foreach (var module in moduleCollection)
|
||||
{
|
||||
if (!IsModuleInDenyList(moduleDenyList, module as string, module as ModuleSpecification))
|
||||
{
|
||||
filteredModuleCollection.Add(module);
|
||||
}
|
||||
}
|
||||
filteredModuleCollection.Add(module);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1977,38 +2003,73 @@ namespace Microsoft.PowerShell.Commands
|
||||
Debug.Assert(string.IsNullOrEmpty(moduleName) ^ (moduleSpec == null), "Either moduleName or moduleSpec must be specified");
|
||||
|
||||
// moduleName can be just a module name and it also can be a full path to psd1 from which we need to extract the module name
|
||||
string coreModuleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec == null ? moduleName : moduleSpec.Name);
|
||||
string moduleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec is null ? moduleName : moduleSpec.Name);
|
||||
|
||||
var isModuleToLoadEngineModule = InitialSessionState.IsEngineModule(coreModuleToLoad);
|
||||
string[] noClobberModuleList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityNoClobberModuleList();
|
||||
if (isModuleToLoadEngineModule || ((noClobberModuleList != null) && noClobberModuleList.Contains(coreModuleToLoad, StringComparer.OrdinalIgnoreCase)))
|
||||
var isBuiltInModule = BuiltInModules.TryGetValue(moduleToLoad, out string normalizedName);
|
||||
if (isBuiltInModule)
|
||||
{
|
||||
// if it is one of engine modules - first try to load it from $PSHOME\Modules
|
||||
// otherwise rely on $env:PSModulePath (in which WinPS module location has to go after CorePS module location)
|
||||
if (isModuleToLoadEngineModule)
|
||||
moduleToLoad = normalizedName;
|
||||
}
|
||||
|
||||
string[] noClobberModuleList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityNoClobberModuleList();
|
||||
if (isBuiltInModule || noClobberModuleList?.Contains(moduleToLoad, StringComparer.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
bool shouldLoadModuleLocally = true;
|
||||
if (isBuiltInModule)
|
||||
{
|
||||
string expectedCoreModulePath = Path.Combine(ModuleIntrinsics.GetPSHomeModulePath(), coreModuleToLoad);
|
||||
if (Directory.Exists(expectedCoreModulePath))
|
||||
PSSnapInInfo loadedSnapin = Context.CurrentRunspace.InitialSessionState.GetPSSnapIn(moduleToLoad);
|
||||
shouldLoadModuleLocally = loadedSnapin is null;
|
||||
|
||||
if (shouldLoadModuleLocally)
|
||||
{
|
||||
coreModuleToLoad = expectedCoreModulePath;
|
||||
// If it is one of built-in modules, first try loading it from $PSHOME\Modules, otherwise rely on $env:PSModulePath.
|
||||
string expectedCoreModulePath = Path.Combine(ModuleIntrinsics.GetPSHomeModulePath(), moduleToLoad);
|
||||
if (Directory.Exists(expectedCoreModulePath))
|
||||
{
|
||||
moduleToLoad = expectedCoreModulePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleSpec == null)
|
||||
if (shouldLoadModuleLocally)
|
||||
{
|
||||
ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, coreModuleToLoad);
|
||||
}
|
||||
else
|
||||
{
|
||||
ModuleSpecification tmpModuleSpec = new ModuleSpecification()
|
||||
// Here we want to load a core-edition compatible version of the module, so the loading procedure will skip
|
||||
// the 'System32' module path when searching. Also, we want to suppress writing out errors in case that a
|
||||
// core-compatible version of the module cannot be found, because:
|
||||
// 1. that's OK as long as it's not a PowerShell built-in module such as the 'Utility' moudle;
|
||||
// 2. the error message will be confusing to the user.
|
||||
bool savedValue = importModuleOptions.SkipSystem32ModulesAndSuppressError;
|
||||
importModuleOptions.SkipSystem32ModulesAndSuppressError = true;
|
||||
|
||||
PSModuleInfo moduleInfo = moduleSpec is null
|
||||
? ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, moduleToLoad)
|
||||
: ImportModule_LocallyViaFQName(
|
||||
importModuleOptions,
|
||||
new ModuleSpecification()
|
||||
{
|
||||
Guid = moduleSpec.Guid,
|
||||
MaximumVersion = moduleSpec.MaximumVersion,
|
||||
Version = moduleSpec.Version,
|
||||
RequiredVersion = moduleSpec.RequiredVersion,
|
||||
Name = moduleToLoad
|
||||
});
|
||||
|
||||
// If we failed to load a core-compatible version of a built-in module, we should stop trying to load the
|
||||
// module in 'WinCompat' mode and report an error. This could happen when a user didn't correctly deploy
|
||||
// the built-in modules, which would result in very confusing errors when the module auto-loading silently
|
||||
// attempts to load those built-in modules in 'WinCompat' mode from the 'System32' module path.
|
||||
//
|
||||
// If the loading failed but it's NOT a built-in module, then it's fine to ignore this failure and continue
|
||||
// to load the module in 'WinCompat' mode.
|
||||
if (moduleInfo is null && isBuiltInModule)
|
||||
{
|
||||
Guid = moduleSpec.Guid,
|
||||
MaximumVersion = moduleSpec.MaximumVersion,
|
||||
Version = moduleSpec.Version,
|
||||
RequiredVersion = moduleSpec.RequiredVersion,
|
||||
Name = coreModuleToLoad
|
||||
};
|
||||
ImportModule_LocallyViaFQName(importModuleOptions, tmpModuleSpec);
|
||||
throw new InvalidOperationException(
|
||||
StringUtil.Format(
|
||||
Modules.CannotFindCoreCompatibleBuiltInModule,
|
||||
moduleToLoad));
|
||||
}
|
||||
|
||||
importModuleOptions.SkipSystem32ModulesAndSuppressError = savedValue;
|
||||
}
|
||||
|
||||
importModuleOptions.NoClobberExportPSSession = true;
|
||||
@@ -2020,28 +2081,15 @@ namespace Microsoft.PowerShell.Commands
|
||||
IList<PSModuleInfo> moduleProxyList = new List<PSModuleInfo>();
|
||||
#if !UNIX
|
||||
// one of the two parameters can be passed: either ModuleNames (most of the time) or ModuleSpecifications (they are used in different parameter sets)
|
||||
List<string> filteredModuleNames = FilterModuleCollection(moduleNames);
|
||||
List<ModuleSpecification> filteredModuleFullyQualifiedNames = FilterModuleCollection(moduleFullyQualifiedNames);
|
||||
IEnumerable<string> filteredModuleNames = FilterModuleCollection(moduleNames);
|
||||
IEnumerable<ModuleSpecification> filteredModuleFullyQualifiedNames = FilterModuleCollection(moduleFullyQualifiedNames);
|
||||
|
||||
// do not setup WinCompat resources if we have no modules to import
|
||||
if ((filteredModuleNames?.Any() != true) && (filteredModuleFullyQualifiedNames?.Any() != true))
|
||||
if (filteredModuleNames?.Any() != true && filteredModuleFullyQualifiedNames?.Any() != true)
|
||||
{
|
||||
return moduleProxyList;
|
||||
}
|
||||
|
||||
var winPSVersionString = Utils.GetWindowsPowerShellVersionFromRegistry();
|
||||
if (!winPSVersionString.StartsWith("5.1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string errorMessage = string.Format(CultureInfo.InvariantCulture, Modules.WinCompatRequredVersionError, winPSVersionString);
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
|
||||
PSSession WindowsPowerShellCompatRemotingSession = CreateWindowsPowerShellCompatResources();
|
||||
if (WindowsPowerShellCompatRemotingSession == null)
|
||||
{
|
||||
return new List<PSModuleInfo>();
|
||||
}
|
||||
|
||||
// perform necessary preparations if module has to be imported with NoClobber mode
|
||||
if (filteredModuleNames != null)
|
||||
{
|
||||
@@ -2059,13 +2107,26 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
}
|
||||
|
||||
var winPSVersionString = Utils.GetWindowsPowerShellVersionFromRegistry();
|
||||
if (!winPSVersionString.StartsWith("5.1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string errorMessage = string.Format(CultureInfo.InvariantCulture, Modules.WinCompatRequredVersionError, winPSVersionString);
|
||||
throw new InvalidOperationException(errorMessage);
|
||||
}
|
||||
|
||||
PSSession WindowsPowerShellCompatRemotingSession = CreateWindowsPowerShellCompatResources();
|
||||
if (WindowsPowerShellCompatRemotingSession == null)
|
||||
{
|
||||
return new List<PSModuleInfo>();
|
||||
}
|
||||
|
||||
// perform the module import / proxy generation
|
||||
moduleProxyList = ImportModule_RemotelyViaPsrpSession(importModuleOptions, filteredModuleNames, filteredModuleFullyQualifiedNames, WindowsPowerShellCompatRemotingSession, usingWinCompat: true);
|
||||
|
||||
foreach (PSModuleInfo moduleProxy in moduleProxyList)
|
||||
{
|
||||
moduleProxy.IsWindowsPowerShellCompatModule = true;
|
||||
System.Threading.Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter);
|
||||
Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter);
|
||||
|
||||
string message = StringUtil.Format(Modules.WinCompatModuleWarning, moduleProxy.Name, WindowsPowerShellCompatRemotingSession.Name);
|
||||
WriteWarning(message);
|
||||
|
||||
@@ -105,6 +105,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// Historically -AllowClobber in these scenarios was set as True.
|
||||
/// </summary>
|
||||
internal bool NoClobberExportPSSession;
|
||||
|
||||
/// <summary>
|
||||
/// Flag that controls skipping the System32 module path when searching a module in module paths. It also suppresses
|
||||
/// writing out errors when specified.
|
||||
/// </summary>
|
||||
internal bool SkipSystem32ModulesAndSuppressError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -280,6 +286,21 @@ namespace Microsoft.PowerShell.Commands
|
||||
"ModuleVersion"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// List of PowerShell built-in modules that are shipped with PowerShell only, not on PS Gallery.
|
||||
/// </summary>
|
||||
protected static readonly HashSet<string> BuiltInModules = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"CimCmdlets",
|
||||
"Microsoft.PowerShell.Diagnostics",
|
||||
"Microsoft.PowerShell.Host",
|
||||
"Microsoft.PowerShell.Management",
|
||||
"Microsoft.PowerShell.Security",
|
||||
"Microsoft.PowerShell.Utility",
|
||||
"Microsoft.WSMan.Management",
|
||||
"PSDiagnostics",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// When module manifests lack a CompatiblePSEditions field,
|
||||
/// they will be treated as if they have this value.
|
||||
@@ -307,14 +328,25 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
private readonly Dictionary<string, PSModuleInfo> _currentlyProcessingModules = new Dictionary<string, PSModuleInfo>();
|
||||
|
||||
internal bool LoadUsingModulePath(bool found, IEnumerable<string> modulePath, string name, SessionState ss,
|
||||
ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out PSModuleInfo module)
|
||||
internal bool LoadUsingModulePath(
|
||||
IEnumerable<string> modulePath,
|
||||
string name,
|
||||
SessionState ss,
|
||||
ImportModuleOptions options,
|
||||
ManifestProcessingFlags manifestProcessingFlags,
|
||||
out PSModuleInfo module)
|
||||
{
|
||||
return LoadUsingModulePath(null, found, modulePath, name, ss, options, manifestProcessingFlags, out module);
|
||||
return LoadUsingModulePath(parentModule: null, modulePath, name, ss, options, manifestProcessingFlags, out module);
|
||||
}
|
||||
|
||||
internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumerable<string> modulePath, string name, SessionState ss,
|
||||
ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out PSModuleInfo module)
|
||||
internal bool LoadUsingModulePath(
|
||||
PSModuleInfo parentModule,
|
||||
IEnumerable<string> modulePath,
|
||||
string name,
|
||||
SessionState ss,
|
||||
ImportModuleOptions options,
|
||||
ManifestProcessingFlags manifestProcessingFlags,
|
||||
out PSModuleInfo module)
|
||||
{
|
||||
string extension = Path.GetExtension(name);
|
||||
string fileBaseName;
|
||||
@@ -325,11 +357,18 @@ namespace Microsoft.PowerShell.Commands
|
||||
extension = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileBaseName = name.Substring(0, name.Length - extension.Length);
|
||||
}
|
||||
|
||||
// Now search using the module path...
|
||||
bool found = false;
|
||||
foreach (string path in modulePath)
|
||||
{
|
||||
if (options.SkipSystem32ModulesAndSuppressError && ModuleUtils.IsOnSystem32ModulePath(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#if UNIX
|
||||
foreach (string folder in Directory.EnumerateDirectories(path))
|
||||
{
|
||||
@@ -823,7 +862,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
|
||||
// Otherwise try the module path
|
||||
found = LoadUsingModulePath(parentModule, found, modulePath,
|
||||
found = LoadUsingModulePath(parentModule, modulePath,
|
||||
moduleSpecification.Name, ss,
|
||||
options, manifestProcessingFlags, out module);
|
||||
}
|
||||
@@ -2375,17 +2414,13 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
if (importingModule)
|
||||
{
|
||||
IList<PSModuleInfo> moduleProxies = ImportModulesUsingWinCompat(new string[] { moduleManifestPath }, null, options);
|
||||
IList<PSModuleInfo> moduleProxies = ImportModulesUsingWinCompat(
|
||||
moduleNames: new string[] { moduleManifestPath },
|
||||
moduleFullyQualifiedNames: null,
|
||||
importModuleOptions: options);
|
||||
|
||||
// we are loading by a single ManifestPath so expect max of 1
|
||||
if (moduleProxies.Count > 0)
|
||||
{
|
||||
return moduleProxies[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// We are loading by a single ManifestPath so expect max of 1
|
||||
return moduleProxies.Count > 0 ? moduleProxies[0] : null;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3706,7 +3741,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
// If the RequiredModule is one of the Engine modules, then they could have been loaded as snapins (using InitialSessionState.CreateDefault())
|
||||
if (result == null && InitialSessionState.IsEngineModule(requiredModule.Name))
|
||||
{
|
||||
result = ModuleCmdletBase.GetEngineSnapIn(context, requiredModule.Name);
|
||||
result = context.CurrentRunspace.InitialSessionState.GetPSSnapIn(requiredModule.Name);
|
||||
if (result != null)
|
||||
{
|
||||
loaded = true;
|
||||
@@ -4883,7 +4918,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal static System.EventHandler<LocationChangedEventArgs> SyncCurrentLocationDelegate;
|
||||
internal static EventHandler<LocationChangedEventArgs> SyncCurrentLocationDelegate;
|
||||
|
||||
internal virtual IList<PSModuleInfo> ImportModulesUsingWinCompat(IEnumerable<string> moduleNames, IEnumerable<ModuleSpecification> moduleFullyQualifiedNames, ImportModuleOptions importModuleOptions) { throw new System.NotImplementedException(); }
|
||||
|
||||
@@ -5309,9 +5344,8 @@ namespace Microsoft.PowerShell.Commands
|
||||
string moduleName, string fileBaseName, string extension, string moduleBase,
|
||||
string prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out bool found)
|
||||
{
|
||||
bool throwAwayModuleFileFound = false;
|
||||
return LoadUsingExtensions(parentModule, moduleName, fileBaseName, extension, moduleBase, prefix, ss,
|
||||
options, manifestProcessingFlags, out found, out throwAwayModuleFileFound);
|
||||
options, manifestProcessingFlags, out found, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -5412,7 +5446,6 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
else if (File.Exists(fileName))
|
||||
{
|
||||
moduleFileFound = true;
|
||||
// Win8: 325243 - Added the version check so that we do not unload modules with the same name but different version
|
||||
if (BaseForce && DoesAlreadyLoadedModuleSatisfyConstraints(module))
|
||||
{
|
||||
@@ -7359,29 +7392,6 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search a PSSnapin with the specified name.
|
||||
/// </summary>
|
||||
internal static PSSnapInInfo GetEngineSnapIn(ExecutionContext context, string name)
|
||||
{
|
||||
HashSet<PSSnapInInfo> snapinSet = new HashSet<PSSnapInInfo>();
|
||||
List<CmdletInfo> cmdlets = context.SessionState.InvokeCommand.GetCmdlets();
|
||||
foreach (CmdletInfo cmdlet in cmdlets)
|
||||
{
|
||||
PSSnapInInfo snapin = cmdlet.PSSnapIn;
|
||||
if (snapin != null && !snapinSet.Contains(snapin))
|
||||
snapinSet.Add(snapin);
|
||||
}
|
||||
|
||||
foreach (PSSnapInInfo snapin in snapinSet)
|
||||
{
|
||||
if (string.Equals(snapin.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
return snapin;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the context cached ModuleTable module for import only if found and has safe language boundaries while
|
||||
/// exporting all functions by default.
|
||||
|
||||
@@ -976,20 +976,17 @@ namespace System.Management.Automation
|
||||
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\\");
|
||||
// Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed.
|
||||
// This happens because 'ModuleTable' uses Path as the key and x86 WinPS has "SysWOW64" in its $PSHOME.
|
||||
// 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.
|
||||
// For the cross-platform PowerShell, a user can choose to install it under "C:\Windows\SysWOW64", and
|
||||
// thus it may have the same problem as described above. So we keep this line of code.
|
||||
psHome = psHome.ToLowerInvariant().Replace(@"\syswow64\", @"\system32\");
|
||||
#endif
|
||||
Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null);
|
||||
}
|
||||
Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null);
|
||||
}
|
||||
catch (System.Security.SecurityException)
|
||||
{
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace System.Management.Automation.Internal
|
||||
#if UNIX
|
||||
return true;
|
||||
#else
|
||||
if (!ModuleUtils.IsOnSystem32ModulePath(moduleManifestPath))
|
||||
if (!IsOnSystem32ModulePath(moduleManifestPath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -221,11 +221,7 @@ namespace System.Management.Automation
|
||||
{
|
||||
get
|
||||
{
|
||||
#pragma warning disable 56503
|
||||
|
||||
throw PSTraceSource.NewNotImplementedException();
|
||||
|
||||
#pragma warning restore 56503
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,11 +232,7 @@ namespace System.Management.Automation
|
||||
{
|
||||
get
|
||||
{
|
||||
#pragma warning disable 56503
|
||||
|
||||
throw PSTraceSource.NewNotImplementedException();
|
||||
|
||||
#pragma warning restore 56503
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,6 +206,10 @@ The #requires statement must be in one of the following formats:
|
||||
<data name="CouldNotAutoImportMatchingModule" xml:space="preserve">
|
||||
<value>The '{0}' command was found in the module '{1}', but the module could not be loaded. For more information, run 'Import-Module {1}'.</value>
|
||||
</data>
|
||||
<data name="CouldNotAutoImportMatchingModuleWithErrorMessage" xml:space="preserve">
|
||||
<value>The '{0}' command was found in the module '{1}', but the module could not be loaded due to the following error: [{2}]
|
||||
For more information, run 'Import-Module {1}'.</value>
|
||||
</data>
|
||||
<data name="CouldNotAutoImportModule" xml:space="preserve">
|
||||
<value>The module '{0}' could not be loaded. For more information, run 'Import-Module {0}'.</value>
|
||||
</data>
|
||||
|
||||
@@ -624,4 +624,7 @@
|
||||
<data name="CannotCreateModuleWithScriptBlock" xml:space="preserve">
|
||||
<value>Cannot create new module while the session is in ConstrainedLanguage mode.</value>
|
||||
</data>
|
||||
<data name="CannotFindCoreCompatibleBuiltInModule" xml:space="preserve">
|
||||
<value>Cannot find the built-in module '{0}' that is compatible with the 'Core' edition. Please make sure the PowerShell built-in modules are available. They usually come with the PowerShell package under the $PSHOME module path, and are required for PowerShell to function properly.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
+168
-1
@@ -11,7 +11,7 @@ function Add-ModulePath
|
||||
|
||||
if ($Prepend)
|
||||
{
|
||||
$env:PSModulePAth = $Path + [System.IO.Path]::PathSeparator + $env:PSModulePath
|
||||
$env:PSModulePath = $Path + [System.IO.Path]::PathSeparator + $env:PSModulePath
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -131,6 +131,22 @@ function New-TestNestedModule
|
||||
[scriptblock]::Create($newManifestCmd).Invoke()
|
||||
}
|
||||
|
||||
function Get-DesktopModuleToUse {
|
||||
$system32Path = "$env:windir\system32\WindowsPowerShell\v1.0\Modules"
|
||||
$persistentMemoryModule = "PersistentMemory"
|
||||
$remoteDesktopModule = "RemoteDesktop"
|
||||
|
||||
if (Test-Path -PathType Container "$system32Path\$persistentMemoryModule") {
|
||||
return $persistentMemoryModule
|
||||
} elseif (Test-Path -PathType Container "$system32Path\$remoteDesktopModule") {
|
||||
return $remoteDesktopModule
|
||||
} else {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
$desktopModuleToUse = Get-DesktopModuleToUse
|
||||
|
||||
Describe "Get-Module with CompatiblePSEditions-checked paths" -Tag "CI" {
|
||||
|
||||
BeforeAll {
|
||||
@@ -643,6 +659,37 @@ Describe "Additional tests for Import-Module with WinCompat" -Tag "Feature" {
|
||||
'{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": ["' + $ModuleName2 + '"]}' | Out-File -Force $ConfigPath
|
||||
& $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `'$basePath`');Test-${ModuleName2}PSEdition;Test-$ModuleName2" | Should -Be @('Desktop','Core')
|
||||
}
|
||||
|
||||
It "NoClobber WinCompat list in powershell.config is a Desktop-edition module" {
|
||||
if (-not $desktopModuleToUse) {
|
||||
throw 'Neither the "PersistentMemory" module nor the "RemoteDesktop" module is available. Please check and use a desktop-edition module that is under the System32 module path.'
|
||||
}
|
||||
|
||||
## The 'Desktop' edition module 'PersistentMemory' (available on Windows Client) or 'RemoteDesktop' (available on Windows Server) should not be imported twice.
|
||||
$ConfigPath = Join-Path $TestDrive 'powershell.config.json'
|
||||
@"
|
||||
{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": ["$desktopModuleToUse"]}
|
||||
"@ | Out-File -Force $ConfigPath
|
||||
$env:PSModulePath = ''
|
||||
|
||||
## The desktop-edition module is listed in the no-clobber list, so we will first try loading a core-edition
|
||||
## compatible version of the module before loading the remote one. The 'system32' module path will be skipped
|
||||
## in this attempt, which is by-design.
|
||||
## If we don't skip the 'system32' module path in this loading attempt, the desktop-edition module will be
|
||||
## imported twice as a remote module, and then 'Remove-Module' won't close the WinCompat session.
|
||||
$script = @"
|
||||
Import-Module $desktopModuleToUse -UseWindowsPowerShell -WarningAction Ignore
|
||||
Get-Module $desktopModuleToUse | ForEach-Object { `$_.ModuleType.ToString() }
|
||||
(Get-PSSession | Measure-Object).Count
|
||||
Remove-Module $desktopModuleToUse
|
||||
(Get-PSSession | Measure-Object).Count
|
||||
"@
|
||||
$scriptBlock = [scriptblock]::Create($script)
|
||||
$results = & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c $scriptBlock
|
||||
$results[0] | Should -BeExactly 'Script'
|
||||
$results[1] | Should -BeExactly 1
|
||||
$results[2] | Should -BeExactly 0
|
||||
}
|
||||
}
|
||||
|
||||
Context "Tests around PSModulePath in WinCompat process" {
|
||||
@@ -1343,3 +1390,123 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "WinCompat importing should check availablity of built-in modules" -Tag "CI" {
|
||||
BeforeAll {
|
||||
if (-not $IsWindows ) {
|
||||
$originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
|
||||
$PSDefaultParameterValues["it:skip"] = $true
|
||||
return
|
||||
}
|
||||
|
||||
## Copy the current PowerShell instance to a temp location
|
||||
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "WinCompat"
|
||||
$pwshDir = Join-Path $tempDir "pwsh"
|
||||
$moduleDir = Join-Path $tempDir "Modules"
|
||||
$savedModulePath = $env:PSModulePath
|
||||
|
||||
if (Test-Path $tempDir) {
|
||||
Remove-Item $tempDir -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Host "Making a copy of the running PowerShell instance ..." -ForegroundColor Yellow
|
||||
Copy-Item $PSHOME $pwshDir -Recurse -Force
|
||||
Move-Item $pwshDir\Modules $moduleDir -Force
|
||||
Write-Host "-- Done copying!" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
if (-not $IsWindows) {
|
||||
$global:PSDefaultParameterValues = $originalDefaultParameterValues
|
||||
return
|
||||
}
|
||||
|
||||
$env:PSModulePath = $savedModulePath
|
||||
Remove-Item $tempDir -Recurse -Force
|
||||
}
|
||||
|
||||
It "Missing built-in modules will trigger error instead of loading the non-compatible ones in System32 directory. Running '<Command>'" -TestCases @(
|
||||
@{
|
||||
Command = 'Start-Transcript';
|
||||
FullyQualifiedErrorId = "CouldNotAutoloadMatchingModule";
|
||||
ExceptionMessage = "*'Start-Transcript'*'Microsoft.PowerShell.Host'*'Microsoft.PowerShell.Host'*'Core'*`$PSHOME*'Import-Module Microsoft.PowerShell.Host'*"
|
||||
}
|
||||
@{
|
||||
Command = 'Import-Module Microsoft.PowerShell.Host';
|
||||
FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
ExceptionMessage = "*'Microsoft.PowerShell.Host'*'Core'*`$PSHOME*"
|
||||
}
|
||||
@{
|
||||
Command = 'Import-Module CimCmdlets'
|
||||
FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
ExceptionMessage = "*'CimCmdlets'*'Core'*`$PSHOME*"
|
||||
}
|
||||
@{
|
||||
Command = 'Import-Module Microsoft.PowerShell.Utility'
|
||||
FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
ExceptionMessage = "*'Microsoft.PowerShell.Utility'*'Core'*`$PSHOME*"
|
||||
}
|
||||
) {
|
||||
param(
|
||||
$Command,
|
||||
$FullyQualifiedErrorId,
|
||||
$ExceptionMessage
|
||||
)
|
||||
|
||||
$template = @'
|
||||
try {{
|
||||
{0}
|
||||
}} catch {{
|
||||
$_.FullyQualifiedErrorId
|
||||
$_.Exception.Message
|
||||
}}
|
||||
'@
|
||||
$env:PSModulePath = ''
|
||||
$script = $template -f $Command
|
||||
$scriptBlock = [scriptblock]::Create($script)
|
||||
|
||||
$result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c $scriptBlock
|
||||
$result | Should -HaveCount 2
|
||||
$result[0] | Should -BeExactly $FullyQualifiedErrorId
|
||||
$result[1] | Should -BeLike $ExceptionMessage
|
||||
}
|
||||
|
||||
It "Attempt to load a 'Desktop' edition module should fail because 'Export-PSSession' cannot be found" {
|
||||
if (-not $desktopModuleToUse) {
|
||||
throw 'Neither the "PersistentMemory" module nor the "RemoteDesktop" module is available. Please check and use a desktop-edition module that is under the System32 module path.'
|
||||
}
|
||||
|
||||
$script = @"
|
||||
try {
|
||||
Import-Module $desktopModuleToUse -ErrorAction Stop
|
||||
} catch {
|
||||
`$_.FullyQualifiedErrorId
|
||||
`$_.Exception.Message
|
||||
}
|
||||
"@
|
||||
$env:PSModulePath = ''
|
||||
$scriptBlock = [scriptblock]::Create($script)
|
||||
$result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c $scriptBlock
|
||||
$result | Should -HaveCount 2
|
||||
$result[0] | Should -BeExactly "CommandNotFoundException,Microsoft.PowerShell.Commands.ImportModuleCommand"
|
||||
$result[1] | Should -BeLike "*'$desktopModuleToUse'*'Export-PSSession'*'Microsoft.PowerShell.Utility'*"
|
||||
}
|
||||
|
||||
It "When built-in modules are available but not in `$PSHOME module path, things should work" {
|
||||
$env:PSModulePath = ''
|
||||
$result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c @"
|
||||
`$env:PSModulePath += ';$moduleDir'
|
||||
Import-Module Microsoft.PowerShell.Utility -UseWindowsPowerShell -WarningAction Ignore
|
||||
Get-Module Microsoft.PowerShell.Utility | ForEach-Object ModuleType
|
||||
Get-Module Microsoft.PowerShell.Utility | Where-Object ModuleType -eq 'Manifest' | ForEach-Object Path
|
||||
Get-Module Microsoft.PowerShell.Utility | Where-Object ModuleType -eq 'Script' | ForEach-Object { `$_.ExportedCommands.Keys }
|
||||
"@
|
||||
$result | Should -HaveCount 6
|
||||
$result[0] | Should -BeExactly 'Manifest'
|
||||
$result[1] | Should -BeExactly 'Script'
|
||||
$result[2] | Should -BeExactly "$moduleDir\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1"
|
||||
$result[3] | Should -BeExactly 'Convert-String'
|
||||
$result[4] | Should -BeExactly 'ConvertFrom-String'
|
||||
$result[5] | Should -BeExactly 'CFS'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user