diff --git a/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs b/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs index fc8dd967356..f26bbcd7f12 100644 --- a/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs @@ -8,6 +8,7 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Collections; +using System.Collections.Generic; // // Now define the set of commands for manipulating modules. @@ -145,23 +146,12 @@ protected override void ProcessRecord() } } - // RootModule can be null, empty string or point to a valid .psm1, , .cdxml, .xaml, .dll or .exe. Anything else is invalid. - if (module.RootModule != null && module.RootModule != string.Empty) + if (!HasValidRootModule(module)) { - string rootModuleExt = System.IO.Path.GetExtension(module.RootModule); - if ((!IsValidFilePath(module.RootModule, module, true) && !IsValidGacAssembly(module.RootModule)) || - (!rootModuleExt.Equals(StringLiterals.PowerShellModuleFileExtension, StringComparison.OrdinalIgnoreCase) && - !rootModuleExt.Equals(StringLiterals.PowerShellILAssemblyExtension, StringComparison.OrdinalIgnoreCase) && - !rootModuleExt.Equals(StringLiterals.PowerShellCmdletizationFileExtension, StringComparison.OrdinalIgnoreCase) && - !rootModuleExt.Equals(StringLiterals.WorkflowFileExtension, StringComparison.OrdinalIgnoreCase) && - !rootModuleExt.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)) - ) - { - string errorMsg = StringUtil.Format(Modules.InvalidModuleManifest, module.RootModule, filePath); - var errorRecord = new ErrorRecord(new ArgumentException(errorMsg), "Modules_InvalidRootModuleInModuleManifest", - ErrorCategory.InvalidArgument, _path); - WriteError(errorRecord); - } + string errorMsg = StringUtil.Format(Modules.InvalidModuleManifest, module.RootModule, filePath); + var errorRecord = new ErrorRecord(new ArgumentException(errorMsg), "Modules_InvalidRootModuleInModuleManifest", + ErrorCategory.InvalidArgument, _path); + WriteError(errorRecord); } Hashtable data = null; @@ -302,6 +292,60 @@ protected override void ProcessRecord() } } + // All module extensions except ".psd1" are valid RootModule extensions + private static readonly IReadOnlyList s_validRootModuleExtensions = ModuleIntrinsics.PSModuleExtensions + .Where(ext => !string.Equals(ext, StringLiterals.PowerShellDataFileExtension, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + /// + /// Checks whether the RootModule field of a module is valid or not. + /// Valid root modules are: + /// - null + /// - Empty string + /// - A valid non-psd1 module file (psm1, cdxml, xaml, dll), as name with extension, name without extension, or path. + /// + /// The module for which we want to check the validity of the root module. + /// True if the root module is valid, false otherwise. + private bool HasValidRootModule(PSModuleInfo module) + { + // Empty/null root modules are allowed + if (string.IsNullOrEmpty(module.RootModule)) + { + return true; + } + + // GAC assemblies are allowed + if (IsValidGacAssembly(module.RootModule)) + { + return true; + } + + // Check for extensions + string rootModuleExt = System.IO.Path.GetExtension(module.RootModule); + if (!string.IsNullOrEmpty(rootModuleExt)) + { + // Check that the root module's extension is an allowed one + if (!s_validRootModuleExtensions.Contains(rootModuleExt, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + // Check the file path of the full root module + return IsValidFilePath(module.RootModule, module, verifyPathScope: true); + } + + // We have no extension, so we need to check all of them + foreach (string extension in s_validRootModuleExtensions) + { + if (IsValidFilePath(module.RootModule + extension, module, verifyPathScope: true)) + { + return true; + } + } + + return false; + } + /// /// Check if the given path is valid. /// diff --git a/test/powershell/engine/Module/TestModuleManifest.Tests.ps1 b/test/powershell/engine/Module/TestModuleManifest.Tests.ps1 index 84c8ef57131..a4ffc72772c 100644 --- a/test/powershell/engine/Module/TestModuleManifest.Tests.ps1 +++ b/test/powershell/engine/Module/TestModuleManifest.Tests.ps1 @@ -67,6 +67,16 @@ Describe "Test-ModuleManifest tests" -tags "CI" { $moduleManifest.RootModule | Should -Be $rootModuleValue } + It "module manifest containing valid rootmodule without specifying .psm1 extension succeeds" { + + $rootModuleFileName = "bar.psm1"; + New-Item -ItemType File -Path testdrive:/module/$rootModuleFileName > $null + New-ModuleManifest -Path $testModulePath -RootModule "bar" + $moduleManifest = Test-ModuleManifest -Path $testModulePath -ErrorAction Stop + $moduleManifest | Should -BeOfType System.Management.Automation.PSModuleInfo + $moduleManifest.RootModule | Should -Be "bar" + } + It "module manifest containing valid processed empty rootmodule file type fails: " -TestCases ( @{rootModuleValue = "foo.cdxml"; error = "System.Xml.XmlException"}, # fails when cmdlet tries to read it as XML @{rootModuleValue = "foo.xaml"; error = "Modules_WorkflowModuleNotSupported"} # not supported on PowerShell Core