mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Merged PR 23820: Fix for JEA session leaking functions
Related work items: #152543
This commit is contained in:
@@ -2981,13 +2981,20 @@ namespace System.Management.Automation.Runspaces
|
||||
HashSet<string> unresolvedCmdsToExpose)
|
||||
{
|
||||
RunspaceOpenModuleLoadException exceptionToReturn = null;
|
||||
List<PSModuleInfo> processedModules = new List<PSModuleInfo>();
|
||||
|
||||
foreach (object module in moduleList)
|
||||
{
|
||||
string moduleName = module as string;
|
||||
if (moduleName != null)
|
||||
{
|
||||
exceptionToReturn = ProcessOneModule(initializedRunspace, moduleName, null, path, publicCommands);
|
||||
exceptionToReturn = ProcessOneModule(
|
||||
initializedRunspace: initializedRunspace,
|
||||
name: moduleName,
|
||||
moduleInfoToLoad: null,
|
||||
path: path,
|
||||
publicCommands: publicCommands,
|
||||
processedModules: processedModules);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2998,7 +3005,13 @@ namespace System.Management.Automation.Runspaces
|
||||
{
|
||||
// if only name is specified in the module spec, just try import the module
|
||||
// ie., don't take the performance overhead of calling GetModule.
|
||||
exceptionToReturn = ProcessOneModule(initializedRunspace, moduleSpecification.Name, null, path, publicCommands);
|
||||
exceptionToReturn = ProcessOneModule(
|
||||
initializedRunspace: initializedRunspace,
|
||||
name: moduleSpecification.Name,
|
||||
moduleInfoToLoad: null,
|
||||
path: path,
|
||||
publicCommands: publicCommands,
|
||||
processedModules: processedModules);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3006,7 +3019,13 @@ namespace System.Management.Automation.Runspaces
|
||||
|
||||
if (moduleInfos != null && moduleInfos.Count > 0)
|
||||
{
|
||||
exceptionToReturn = ProcessOneModule(initializedRunspace, moduleSpecification.Name, moduleInfos[0], path, publicCommands);
|
||||
exceptionToReturn = ProcessOneModule(
|
||||
initializedRunspace: initializedRunspace,
|
||||
name: moduleSpecification.Name,
|
||||
moduleInfoToLoad: moduleInfos[0],
|
||||
path: path,
|
||||
publicCommands: publicCommands,
|
||||
processedModules: processedModules);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3055,7 +3074,11 @@ namespace System.Management.Automation.Runspaces
|
||||
string commandToMakeVisible = Utils.ParseCommandName(unresolvedCommand, out moduleName);
|
||||
bool found = false;
|
||||
|
||||
foreach (CommandInfo cmd in LookupCommands(commandToMakeVisible, moduleName, initializedRunspace.ExecutionContext))
|
||||
foreach (CommandInfo cmd in LookupCommands(
|
||||
commandPattern: commandToMakeVisible,
|
||||
moduleName: moduleName,
|
||||
context: initializedRunspace.ExecutionContext,
|
||||
processedModules: processedModules))
|
||||
{
|
||||
if (!found)
|
||||
{
|
||||
@@ -3106,11 +3129,13 @@ namespace System.Management.Automation.Runspaces
|
||||
/// <param name="commandPattern"></param>
|
||||
/// <param name="moduleName"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="processedModules"></param>
|
||||
/// <returns></returns>
|
||||
private static IEnumerable<CommandInfo> LookupCommands(
|
||||
string commandPattern,
|
||||
string moduleName,
|
||||
ExecutionContext context)
|
||||
ExecutionContext context,
|
||||
List<PSModuleInfo> processedModules)
|
||||
{
|
||||
bool isWildCardPattern = WildcardPattern.ContainsWildcardCharacters(commandPattern);
|
||||
var searchOptions = isWildCardPattern ?
|
||||
@@ -3124,7 +3149,11 @@ namespace System.Management.Automation.Runspaces
|
||||
CommandOrigin cmdOrigin = CommandOrigin.Runspace;
|
||||
while (true)
|
||||
{
|
||||
foreach (CommandInfo commandInfo in context.SessionState.InvokeCommand.GetCommands(commandPattern, CommandTypes.All, searchOptions, cmdOrigin))
|
||||
foreach (CommandInfo commandInfo in context.SessionState.InvokeCommand.GetCommands(
|
||||
name: commandPattern,
|
||||
commandTypes: CommandTypes.All,
|
||||
options: searchOptions,
|
||||
commandOrigin: cmdOrigin))
|
||||
{
|
||||
// If module name is provided then use it to restrict returned results.
|
||||
if (haveModuleName && !moduleName.Equals(commandInfo.ModuleName, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -3154,13 +3183,43 @@ namespace System.Management.Automation.Runspaces
|
||||
// Next try internal search.
|
||||
cmdOrigin = CommandOrigin.Internal;
|
||||
}
|
||||
|
||||
// If the command is associated with a module, try finding the command in the imported module list.
|
||||
// The SessionState function table holds only one command name, and if two or more modules contain
|
||||
// a command with the same name, only one of them will appear in the function table search above.
|
||||
if (!found && haveModuleName)
|
||||
{
|
||||
var pattern = new WildcardPattern(commandPattern);
|
||||
|
||||
foreach (PSModuleInfo moduleInfo in processedModules)
|
||||
{
|
||||
if (moduleInfo.Name.Equals(moduleName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreach (var cmd in moduleInfo.ExportedCommands.Values)
|
||||
{
|
||||
if (pattern.IsMatch(cmd.Name))
|
||||
{
|
||||
yield return cmd;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If <paramref name="moduleInfoToLoad"/> is null, import module using <paramref name="name"/>. Otherwise,
|
||||
/// import module using <paramref name="moduleInfoToLoad"/>
|
||||
/// </summary>
|
||||
private RunspaceOpenModuleLoadException ProcessOneModule(Runspace initializedRunspace, string name, PSModuleInfo moduleInfoToLoad, string path, HashSet<CommandInfo> publicCommands)
|
||||
private RunspaceOpenModuleLoadException ProcessOneModule(
|
||||
Runspace initializedRunspace,
|
||||
string name,
|
||||
PSModuleInfo moduleInfoToLoad,
|
||||
string path,
|
||||
HashSet<CommandInfo> publicCommands,
|
||||
List<PSModuleInfo> processedModules)
|
||||
{
|
||||
using (PowerShell pse = PowerShell.Create())
|
||||
{
|
||||
@@ -3197,6 +3256,11 @@ namespace System.Management.Automation.Runspaces
|
||||
c = new CmdletInfo("Out-Default", typeof(OutDefaultCommand), null, null, initializedRunspace.ExecutionContext);
|
||||
pse.AddCommand(new Command(c));
|
||||
}
|
||||
else
|
||||
{
|
||||
// For runspace init module processing, pass back the PSModuleInfo to the output pipeline.
|
||||
cmd.Parameters.Add("PassThru");
|
||||
}
|
||||
|
||||
pse.Runspace = initializedRunspace;
|
||||
// Module import should be run in FullLanguage mode since it is running in
|
||||
@@ -3205,7 +3269,10 @@ namespace System.Management.Automation.Runspaces
|
||||
pse.Runspace.ExecutionContext.LanguageMode = PSLanguageMode.FullLanguage;
|
||||
try
|
||||
{
|
||||
pse.Invoke();
|
||||
// For runspace init module processing, collect the imported PSModuleInfo returned in the output pipeline.
|
||||
// In other cases, this collection will be empty.
|
||||
Collection<PSModuleInfo> moduleInfos = pse.Invoke<PSModuleInfo>();
|
||||
processedModules.AddRange(moduleInfos);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -7192,6 +7192,9 @@ namespace Microsoft.PowerShell.Commands
|
||||
CommandOrigin.Internal,
|
||||
targetSessionState.ExecutionContext);
|
||||
|
||||
// Note that the module 'func' and the function table 'functionInfo' instances are now linked
|
||||
// together (see 'CopiedCommand' in CommandInfo class), so setting visibility on one also
|
||||
// sets it on the other.
|
||||
SetCommandVisibility(isImportModulePrivate, functionInfo);
|
||||
functionInfo.Module = sourceModule;
|
||||
|
||||
|
||||
@@ -1245,11 +1245,18 @@ namespace System.Management.Automation
|
||||
name != null,
|
||||
"The caller should verify the name");
|
||||
|
||||
var functionInfos = GetFunctions();
|
||||
FunctionInfo existingValue;
|
||||
Dictionary<string, FunctionInfo> functionInfos = GetFunctions();
|
||||
FunctionInfo result;
|
||||
if (!functionInfos.TryGetValue(name, out existingValue))
|
||||
|
||||
// Functions are equal only if they have the same name and if they come from the same module (if any).
|
||||
// If the function is not associated with a module then the info 'ModuleName' property is set to empty string.
|
||||
// If the new function has the same name of an existing function, but different module names, then the
|
||||
// existing table function is replaced with the new function.
|
||||
if (!functionInfos.TryGetValue(name, out FunctionInfo existingValue) ||
|
||||
(originalFunction != null &&
|
||||
!existingValue.ModuleName.Equals(originalFunction.ModuleName, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// Add new function info to function table and return.
|
||||
result = functionFactory(name, function, originalFunction, options, context, helpFile);
|
||||
functionInfos[name] = result;
|
||||
|
||||
@@ -1257,81 +1264,78 @@ namespace System.Management.Automation
|
||||
{
|
||||
GetAllScopeFunctions()[name] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Update the existing function.
|
||||
|
||||
// Make sure the function isn't constant or readonly.
|
||||
SessionState.ThrowIfNotVisible(origin, existingValue);
|
||||
|
||||
if (IsFunctionOptionSet(existingValue, ScopedItemOptions.Constant) ||
|
||||
(!force && IsFunctionOptionSet(existingValue, ScopedItemOptions.ReadOnly)))
|
||||
{
|
||||
SessionStateUnauthorizedAccessException e =
|
||||
new SessionStateUnauthorizedAccessException(
|
||||
name,
|
||||
SessionStateCategory.Function,
|
||||
"FunctionNotWritable",
|
||||
SessionStateStrings.FunctionNotWritable);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Ensure we are not trying to set the function to constant as this can only be
|
||||
// done at creation time.
|
||||
if ((options & ScopedItemOptions.Constant) != 0)
|
||||
{
|
||||
SessionStateUnauthorizedAccessException e =
|
||||
new SessionStateUnauthorizedAccessException(
|
||||
name,
|
||||
SessionStateCategory.Function,
|
||||
"FunctionCannotBeMadeConstant",
|
||||
SessionStateStrings.FunctionCannotBeMadeConstant);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Ensure we are not trying to remove the AllScope option.
|
||||
if ((options & ScopedItemOptions.AllScope) == 0 &&
|
||||
IsFunctionOptionSet(existingValue, ScopedItemOptions.AllScope))
|
||||
{
|
||||
SessionStateUnauthorizedAccessException e =
|
||||
new SessionStateUnauthorizedAccessException(
|
||||
name,
|
||||
SessionStateCategory.Function,
|
||||
"FunctionAllScopeOptionCannotBeRemoved",
|
||||
SessionStateStrings.FunctionAllScopeOptionCannotBeRemoved);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
FunctionInfo existingFunction = existingValue;
|
||||
|
||||
// If the function type changes (i.e.: function to workflow or back)
|
||||
// then we need to replace what was there.
|
||||
FunctionInfo newValue = functionFactory(name, function, originalFunction, options, context, helpFile);
|
||||
|
||||
bool changesFunctionType = existingFunction.GetType() != newValue.GetType();
|
||||
|
||||
// Since the options are set after the script block, we have to
|
||||
// forcefully apply the script block if the options will be
|
||||
// set to not being ReadOnly.
|
||||
if (changesFunctionType ||
|
||||
((existingFunction.Options & ScopedItemOptions.ReadOnly) != 0 && force))
|
||||
{
|
||||
result = newValue;
|
||||
functionInfos[name] = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure the function isn't constant or readonly
|
||||
|
||||
SessionState.ThrowIfNotVisible(origin, existingValue);
|
||||
|
||||
if (IsFunctionOptionSet(existingValue, ScopedItemOptions.Constant) ||
|
||||
(!force && IsFunctionOptionSet(existingValue, ScopedItemOptions.ReadOnly)))
|
||||
{
|
||||
SessionStateUnauthorizedAccessException e =
|
||||
new SessionStateUnauthorizedAccessException(
|
||||
name,
|
||||
SessionStateCategory.Function,
|
||||
"FunctionNotWritable",
|
||||
SessionStateStrings.FunctionNotWritable);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Ensure we are not trying to set the function to constant as this can only be
|
||||
// done at creation time.
|
||||
|
||||
if ((options & ScopedItemOptions.Constant) != 0)
|
||||
{
|
||||
SessionStateUnauthorizedAccessException e =
|
||||
new SessionStateUnauthorizedAccessException(
|
||||
name,
|
||||
SessionStateCategory.Function,
|
||||
"FunctionCannotBeMadeConstant",
|
||||
SessionStateStrings.FunctionCannotBeMadeConstant);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Ensure we are not trying to remove the AllScope option
|
||||
|
||||
if ((options & ScopedItemOptions.AllScope) == 0 &&
|
||||
IsFunctionOptionSet(existingValue, ScopedItemOptions.AllScope))
|
||||
{
|
||||
SessionStateUnauthorizedAccessException e =
|
||||
new SessionStateUnauthorizedAccessException(
|
||||
name,
|
||||
SessionStateCategory.Function,
|
||||
"FunctionAllScopeOptionCannotBeRemoved",
|
||||
SessionStateStrings.FunctionAllScopeOptionCannotBeRemoved);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
FunctionInfo existingFunction = existingValue;
|
||||
FunctionInfo newValue = null;
|
||||
|
||||
// If the function type changes (i.e.: function to workflow or back)
|
||||
// then we need to blast what was there
|
||||
newValue = functionFactory(name, function, originalFunction, options, context, helpFile);
|
||||
|
||||
bool changesFunctionType = existingFunction.GetType() != newValue.GetType();
|
||||
|
||||
// Since the options are set after the script block, we have to
|
||||
// forcefully apply the script block if the options will be
|
||||
// set to not being ReadOnly
|
||||
if (changesFunctionType ||
|
||||
((existingFunction.Options & ScopedItemOptions.ReadOnly) != 0 && force))
|
||||
{
|
||||
result = newValue;
|
||||
functionInfos[name] = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool applyForce = force || (options & ScopedItemOptions.ReadOnly) == 0;
|
||||
|
||||
existingFunction.Update(newValue, applyForce, options, helpFile);
|
||||
result = existingFunction;
|
||||
}
|
||||
bool applyForce = force || (options & ScopedItemOptions.ReadOnly) == 0;
|
||||
existingFunction.Update(newValue, applyForce, options, helpFile);
|
||||
result = existingFunction;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user