diff --git a/src/code/PublishHelper.cs b/src/code/PublishHelper.cs index abdced37b..9607644c4 100644 --- a/src/code/PublishHelper.cs +++ b/src/code/PublishHelper.cs @@ -1195,7 +1195,7 @@ private Hashtable ParseRequiredModules(Hashtable parsedMetadataHash) // c. A string array of module names // d. A single string module name - var dependenciesHash = new Hashtable(); + var dependenciesHash = new Hashtable(StringComparer.OrdinalIgnoreCase); foreach (var reqModule in requiredModules) { if (LanguagePrimitives.TryConvertTo(reqModule, out Hashtable moduleHash)) @@ -1228,21 +1228,135 @@ private Hashtable ParseRequiredModules(Hashtable parsedMetadataHash) } } - var externalModuleDeps = parsedMetadataHash.ContainsKey("ExternalModuleDependencies") ? - parsedMetadataHash["ExternalModuleDependencies"] : null; + var externalModuleNames = GetExternalModuleDependencies(parsedMetadataHash); + foreach (var extModName in externalModuleNames) + { + if (dependenciesHash.ContainsKey(extModName)) + { + dependenciesHash.Remove(extModName); + } + } + + return dependenciesHash; + } + + private static string[] GetExternalModuleDependencies(Hashtable parsedMetadataHash) + { + if (parsedMetadataHash == null) + { + return Array.Empty(); + } + + var externalModules = new HashSet(StringComparer.OrdinalIgnoreCase); + AddExternalModuleDependencies(parsedMetadataHash, externalModules); + + if (TryGetHashtableValue(parsedMetadataHash, "PrivateData", out Hashtable privateData) && + TryGetHashtableValue(privateData, "PSData", out Hashtable psData)) + { + AddExternalModuleDependencies(psData, externalModules); + } + + return externalModules.ToArray(); + } - if (externalModuleDeps != null && LanguagePrimitives.TryConvertTo(externalModuleDeps, out string[] externalModuleNames)) + private static void AddExternalModuleDependencies(Hashtable container, HashSet externalModules) + { + if (container == null || externalModules == null || + !TryGetValue(container, "ExternalModuleDependencies", out object externalModuleDeps)) + { + return; + } + + if (externalModuleDeps == null) + { + return; + } + + if (LanguagePrimitives.TryConvertTo(externalModuleDeps, out string[] externalModuleNames) && externalModuleNames != null) { - foreach (var extModName in externalModuleNames) + foreach (var name in externalModuleNames) { - if (dependenciesHash.ContainsKey(extModName)) + if (!string.IsNullOrWhiteSpace(name)) { - dependenciesHash.Remove(extModName); + externalModules.Add(name.Trim()); } } + + return; } - return dependenciesHash; + if (LanguagePrimitives.TryConvertTo(externalModuleDeps, out string externalModuleName) && + !string.IsNullOrWhiteSpace(externalModuleName)) + { + externalModules.Add(externalModuleName.Trim()); + } + } + + private static bool TryGetHashtableValue(Hashtable source, string key, out Hashtable value) + { + value = null; + if (!TryGetValue(source, key, out object rawValue) || rawValue == null) + { + return false; + } + + if (rawValue is PSObject psObject && psObject.BaseObject != null) + { + rawValue = psObject.BaseObject; + } + + if (rawValue is Hashtable hashtable) + { + value = hashtable; + return true; + } + + if (rawValue is IDictionary dictionary) + { + var converted = new Hashtable(StringComparer.OrdinalIgnoreCase); + foreach (DictionaryEntry entry in dictionary) + { + converted[entry.Key] = entry.Value; + } + + value = converted; + return true; + } + + if (LanguagePrimitives.TryConvertTo(rawValue, out Hashtable parsed) && parsed != null) + { + value = parsed; + return true; + } + + return false; + } + + private static bool TryGetValue(Hashtable source, string key, out object value) + { + value = null; + if (source == null || string.IsNullOrWhiteSpace(key)) + { + return false; + } + + if (source.ContainsKey(key)) + { + value = source[key]; + return true; + } + + foreach (DictionaryEntry entry in source) + { + if (entry.Key is string entryKey && + string.Equals(entryKey, key, StringComparison.OrdinalIgnoreCase)) + { + value = entry.Value; + return true; + } + } + + return false; } private bool CheckDependenciesExist(Hashtable dependencies, string repositoryName, NetworkCredential networkCredential) diff --git a/test/PublishPSResourceTests/PublishPSResource.Tests.ps1 b/test/PublishPSResourceTests/PublishPSResource.Tests.ps1 index ed0adf1b8..97f5ceef1 100644 --- a/test/PublishPSResourceTests/PublishPSResource.Tests.ps1 +++ b/test/PublishPSResourceTests/PublishPSResource.Tests.ps1 @@ -311,6 +311,41 @@ Describe "Test Publish-PSResource" -tags 'CI' { {Publish-PSResource -Path $script:PublishModuleBase -ErrorAction Stop} | Should -Throw -ErrorId "DependencyNotFound,Microsoft.PowerShell.PSResourceGet.Cmdlets.PublishPSResource" } + It "Publish a module with ExternalModuleDependencies that are not published" { + $version = "1.0.0" + $externalModuleName = "PackageManagement" + $manifestPath = Join-Path -Path $script:PublishModuleBase -ChildPath "$script:PublishModuleName.psd1" + $moduleFilePath = Join-Path -Path $script:PublishModuleBase -ChildPath "$script:PublishModuleName.psm1" + + @' +function Test-PublishedFunction { + Write-Output "OK" +} +'@ | Out-File -FilePath $moduleFilePath + + @" +@{ + RootModule = '$script:PublishModuleName.psm1' + ModuleVersion = '$version' + Author = 'None' + Description = '$script:PublishModuleName module' + GUID = '6f703037-3e95-4dcf-b06c-916f2867cce7' + FunctionsToExport = @('Test-PublishedFunction') + RequiredModules = @('$externalModuleName') + PrivateData = @{ + PSData = @{ + ExternalModuleDependencies = @('$externalModuleName') + } + } +} +"@ | Out-File -FilePath $manifestPath + + Publish-PSResource -Path $script:PublishModuleBase -Repository $testRepository2 + + $expectedPath = Join-Path -Path $script:repositoryPath2 -ChildPath "$script:PublishModuleName.$version.nupkg" + (Get-ChildItem $script:repositoryPath2).FullName | Should -Be $expectedPath + } + It "Publish a module with -SkipDependenciesCheck" { $version = "1.0.0" $dependencyVersion = "2.0.0"