Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 122 additions & 8 deletions src/code/PublishHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Hashtable>(reqModule, out Hashtable moduleHash))
Expand Down Expand Up @@ -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<string>();
}

var externalModules = new HashSet<string>(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<string[]>(externalModuleDeps, out string[] externalModuleNames))
private static void AddExternalModuleDependencies(Hashtable container, HashSet<string> externalModules)
{
if (container == null || externalModules == null ||
!TryGetValue(container, "ExternalModuleDependencies", out object externalModuleDeps))
{
return;
}

if (externalModuleDeps == null)
{
return;
}

if (LanguagePrimitives.TryConvertTo<string[]>(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<string>(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<Hashtable>(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)
Expand Down
35 changes: 35 additions & 0 deletions test/PublishPSResourceTests/PublishPSResource.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down