diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index a16e50e589b..8c64918fd86 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -2820,7 +2820,7 @@ internal PSModuleInfo LoadModuleManifest( { bool found = false; // Never load nested modules to the global scope. - bool oldGLobal = this.BaseGlobal; + bool oldGlobal = this.BaseGlobal; this.BaseGlobal = false; PSModuleInfo nestedModule = LoadModuleNamedInManifest( @@ -2838,7 +2838,7 @@ internal PSModuleInfo LoadModuleManifest( out found, shortModuleName: null); - this.BaseGlobal = oldGLobal; + this.BaseGlobal = oldGlobal; // If found, add it to the parent's list of NestedModules if (found) @@ -2976,30 +2976,16 @@ internal PSModuleInfo LoadModuleManifest( BaseCmdletPatterns = oldCmdletPatterns; } - // If there is an existing session state and the new module info - // session state is empty, then use the existing session state. - // This will be the case when ModuleToProcess is a binary module - // and there were nested modules. - if (newManifestInfo.SessionState == null && ss != null) - { - newManifestInfo.SessionState = ss; - ss.Internal.Module = newManifestInfo; - } - // If the new module info session state is not empty but there is no - // existing session state, then use the new module info's sessions state; - // This will be the case if the module to process is a script module - // but there where no nested modules. - else if (newManifestInfo.SessionState != null && ss == null) - { - ss = newManifestInfo.SessionState; - } - - // We don't need to care if they are both null which will be the - // case when there is only a binary module in ModuleToProcess and - // no nested modules. At this point the moduleInfo and the value in - // ss should be identical. - Dbg.Assert(newManifestInfo.SessionState == ss, - "ss and newManifestInfo.SessionState should be the same after handling ModuleToProcess"); + // For most cases, 'newManifestInfo.SessionState' should be identical to 'ss': + // 1. when 'importingModule == true', 'newManifestInfo' uses the same session state as 'ss' because we passed in 'ss' when loading the RootModule via 'LoadModuleNamedInManifest'. + // 2. when 'importingModule == false', both session states will be null since we are in module analysis mode (Get-Module -ListAvailable). + // + // However, there is one exception when the RootModule is also put in NestedModules in the module manifest (ill-organized module structure). + // For example, module folder 'test' contains two files: 'test.psd1' and 'test.psm1', and 'test.psd1' has the following content: + // "@{ ModuleVersion = '0.0.1'; RootModule = 'test'; NestedModules = @('test') }" + // + // In that case, the nested module will first be loaded with a different session state, and then when trying to load the RootModule via 'LoadModuleNamedInManifest', + // the same loaded nested module will be reused for the RootModule by 'LoadModuleNamedInManifest'. // Change the module name to match the manifest name, not the original name newManifestInfo.SetName(manifestInfo.Name); @@ -3248,7 +3234,6 @@ internal PSModuleInfo LoadModuleManifest( { if ((exportedCmdlets != null) && (ss != null)) { - Dbg.Assert(ss.Internal.ExportedCmdlets != null, "ss.Internal.ExportedCmdlets should not be null"); manifestInfo.ExportedCmdlets.Clear(); // Mark stuff for export diff --git a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs index 2ceefd400f3..df216e5a515 100644 --- a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs +++ b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs @@ -577,6 +577,15 @@ public ReadOnlyDictionary GetExportedTypeDefinitions( var res = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var nestedModule in this.NestedModules) { + if (nestedModule == this) + { + // Circular nested modules could happen with ill-organized module structure. + // For example, module folder 'test' has two files: 'test.psd1' and 'test.psm1', and 'test.psd1' has the following content: + // "@{ ModuleVersion = '0.0.1'; RootModule = 'test'; NestedModules = @('test') }" + // Then, 'Import-Module test.psd1 -PassThru' will return a ModuleInfo object with circular nested modules. + continue; + } + foreach (var typePairs in nestedModule.GetExportedTypeDefinitions()) { // The last one name wins! It's the same for command names in nested modules. diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 index 04577e954f9..9f4d77a1557 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 @@ -241,3 +241,25 @@ Describe "Workflow .Xaml module is not supported in PSCore" -tags "Feature" { { Import-Module $xamlNestedModule -ErrorAction Stop } | Should -Throw -ErrorId "Modules_WorkflowModuleNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand" } } + +Describe "Circular nested module test" -tags "CI" { + BeforeAll { + $moduleFolder = Join-Path $TestDrive CircularNestedModuleTest + $psdPath = Join-Path $moduleFolder CircularNestedModuleTest.psd1 + $psmPath = Join-Path $moduleFolder CircularNestedModuleTest.psm1 + + New-Item -Path $moduleFolder -ItemType Directory -Force > $null + Set-Content -Path $psdPath -Value "@{ ModuleVersion = '0.0.1'; RootModule = 'CircularNestedModuleTest'; NestedModules = @('CircularNestedModuleTest') }" -Encoding Ascii + Set-Content -Path $psmPath -Value "function bar {}" -Encoding Ascii + } + + AfterAll { + Remove-Module -Name CircularNestedModuleTest -Force -ErrorAction SilentlyContinue + Remove-Item -Path $moduleFolder -Force -Recurse + } + + It "Loading the module should succeed and return a module with circular nested module" { + $m = Import-Module $psdPath -PassThru + $m.NestedModules[0] | Should -Be $m + } +}