diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 32969ecde2..0c045818bc 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -2981,13 +2981,20 @@ namespace System.Management.Automation.Runspaces HashSet unresolvedCmdsToExpose) { RunspaceOpenModuleLoadException exceptionToReturn = null; + List processedModules = new List(); 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 /// /// /// + /// /// private static IEnumerable LookupCommands( string commandPattern, string moduleName, - ExecutionContext context) + ExecutionContext context, + List 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; + } + } + } } /// /// If is null, import module using . Otherwise, /// import module using /// - private RunspaceOpenModuleLoadException ProcessOneModule(Runspace initializedRunspace, string name, PSModuleInfo moduleInfoToLoad, string path, HashSet publicCommands) + private RunspaceOpenModuleLoadException ProcessOneModule( + Runspace initializedRunspace, + string name, + PSModuleInfo moduleInfoToLoad, + string path, + HashSet publicCommands, + List 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 moduleInfos = pse.Invoke(); + processedModules.AddRange(moduleInfos); } finally { diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index dc5f500f59..48dd73a987 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -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; diff --git a/src/System.Management.Automation/engine/SessionStateScope.cs b/src/System.Management.Automation/engine/SessionStateScope.cs index 57c4c6388b..080aebb2b0 100644 --- a/src/System.Management.Automation/engine/SessionStateScope.cs +++ b/src/System.Management.Automation/engine/SessionStateScope.cs @@ -1245,11 +1245,18 @@ namespace System.Management.Automation name != null, "The caller should verify the name"); - var functionInfos = GetFunctions(); - FunctionInfo existingValue; + Dictionary 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;