diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs index e006d01fa..7afa4cc10 100644 --- a/src/code/FindHelper.cs +++ b/src/code/FindHelper.cs @@ -34,6 +34,7 @@ internal class FindHelper private bool _includeDependencies = false; private bool _repositoryNameContainsWildcard = true; private NetworkCredential _networkCredential; + private Dictionary> _packagesFound; #endregion @@ -46,6 +47,7 @@ public FindHelper(CancellationToken cancellationToken, PSCmdlet cmdletPassedIn, _cancellationToken = cancellationToken; _cmdletPassedIn = cmdletPassedIn; _networkCredential = networkCredential; + _packagesFound = new Dictionary>(StringComparer.OrdinalIgnoreCase); } #endregion @@ -404,7 +406,7 @@ public IEnumerable FindByCommandOrDscResource( { _cmdletPassedIn.WriteVerbose(errRecord.Exception.Message); } - + continue; } @@ -632,7 +634,6 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R { ErrorRecord errRecord = null; List parentPkgs = new List(); - HashSet pkgsFound = new HashSet(StringComparer.OrdinalIgnoreCase); string tagsAsString = String.Empty; _cmdletPassedIn.WriteDebug("In FindHelper::SearchByNames()"); @@ -677,8 +678,9 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R if (foundPkg.Type == _type || _type == ResourceType.None) { parentPkgs.Add(foundPkg); - pkgsFound.Add(String.Format("{0}{1}", foundPkg.Name, foundPkg.Version.ToString())); + TryAddToPackagesFound(foundPkg); _cmdletPassedIn.WriteDebug($"Found package '{foundPkg.Name}' version '{foundPkg.Version}'"); + yield return foundPkg; } } @@ -729,7 +731,8 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R PSResourceInfo foundPkg = currentResult.returnedObject; parentPkgs.Add(foundPkg); - pkgsFound.Add(String.Format("{0}{1}", foundPkg.Name, foundPkg.Version.ToString())); + TryAddToPackagesFound(foundPkg); + yield return foundPkg; } } @@ -778,7 +781,8 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R PSResourceInfo foundPkg = currentResult.returnedObject; parentPkgs.Add(foundPkg); - pkgsFound.Add(String.Format("{0}{1}", foundPkg.Name, foundPkg.Version.ToString())); + TryAddToPackagesFound(foundPkg); + yield return foundPkg; } } @@ -833,13 +837,14 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R "FindVersionConvertToPSResourceFailure", ErrorCategory.ObjectNotFound, this)); - + continue; } PSResourceInfo foundPkg = currentResult.returnedObject; parentPkgs.Add(foundPkg); - pkgsFound.Add(String.Format("{0}{1}", foundPkg.Name, foundPkg.Version.ToString())); + TryAddToPackagesFound(foundPkg); + yield return foundPkg; } } @@ -915,7 +920,7 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R && _versionRange.Satisfies(version)) { parentPkgs.Add(foundPkg); - pkgsFound.Add(String.Format("{0}{1}", foundPkg.Name, foundPkg.Version.ToString())); + TryAddToPackagesFound(foundPkg); yield return foundPkg; } @@ -936,7 +941,7 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R foreach (PSResourceInfo currentPkg in parentPkgs) { _cmdletPassedIn.WriteDebug($"Finding dependency packages for '{currentPkg.Name}'"); - foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository, pkgsFound)) + foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository)) { yield return pkgDep; } @@ -962,6 +967,48 @@ private HashSet GetPackageNamesPopulated(string[] pkgNames) return pkgsToDiscover; } + + private bool TryAddToPackagesFound(PSResourceInfo foundPkg) + { + bool addedToHash = false; + string foundPkgName = foundPkg.Name; + string foundPkgVersion = Utils.GetNormalizedVersionString(foundPkg.Version.ToString(), foundPkg.Prerelease); + + if (_packagesFound.ContainsKey(foundPkgName)) + { + List pkgVersions = _packagesFound[foundPkgName] as List; + + if (!pkgVersions.Contains(foundPkgVersion)) + { + pkgVersions.Add(foundPkgVersion); + _packagesFound[foundPkgName] = pkgVersions; + addedToHash = true; + } + } + else + { + _packagesFound.Add(foundPkg.Name, new List { foundPkgVersion }); + addedToHash = true; + } + + _cmdletPassedIn.WriteDebug($"Found package '{foundPkg.Name}' version '{foundPkg.Version}'"); + + return addedToHash; + } + + private string FormatPkgVersionString(PSResourceInfo pkg) + { + string fullPkgVersion = pkg.Version.ToString(); + + if (!string.IsNullOrWhiteSpace(pkg.Prerelease)) + { + fullPkgVersion += $"-{pkg.Prerelease}"; + } + _cmdletPassedIn.WriteDebug($"Formatted full package version is: '{fullPkgVersion}'"); + + return fullPkgVersion; + } + #endregion #region Internal Client Search Methods @@ -970,8 +1017,7 @@ internal IEnumerable FindDependencyPackages( ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, - PSRepositoryInfo repository, - HashSet foundPkgs) + PSRepositoryInfo repository) { if (currentPkg.Dependencies.Length > 0) { @@ -1009,15 +1055,26 @@ internal IEnumerable FindDependencyPackages( } depPkg = currentResult.returnedObject; - string pkgHashKey = String.Format("{0}{1}", depPkg.Name, depPkg.Version.ToString()); - if (!foundPkgs.Contains(pkgHashKey)) + if (!_packagesFound.ContainsKey(depPkg.Name)) { - foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository, foundPkgs)) + foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository)) { yield return depRes; } } + else + { + List pkgVersions = _packagesFound[depPkg.Name] as List; + // _packagesFound has depPkg.name in it, but the version is not the same + if (!pkgVersions.Contains(FormatPkgVersionString(depPkg))) + { + foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository)) + { + yield return depRes; + } + } + } } else { @@ -1066,7 +1123,7 @@ internal IEnumerable FindDependencyPackages( if (foundDep.IsPrerelease) { depVersionStr += $"-{foundDep.Prerelease}"; } - + if (NuGetVersion.TryParse(depVersionStr, out NuGetVersion depVersion) && dep.VersionRange.Satisfies(depVersion)) { @@ -1078,26 +1135,47 @@ internal IEnumerable FindDependencyPackages( { continue; } - - string pkgHashKey = String.Format("{0}{1}", depPkg.Name, depPkg.Version.ToString()); - if (!foundPkgs.Contains(pkgHashKey)) + if (!_packagesFound.ContainsKey(depPkg.Name)) { - foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository, foundPkgs)) + foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository)) { yield return depRes; } } + else { + List pkgVersions = _packagesFound[depPkg.Name] as List; + // _packagesFound has depPkg.name in it, but the version is not the same + if (!pkgVersions.Contains(FormatPkgVersionString(depPkg))) + { + foreach (PSResourceInfo depRes in FindDependencyPackages(currentServer, currentResponseUtil, depPkg, repository)) + { + yield return depRes; + } + } + } } } } - string currentPkgHashKey = String.Format("{0}{1}", currentPkg.Name, currentPkg.Version.ToString()); - - if (!foundPkgs.Contains(currentPkgHashKey)) + if (!_packagesFound.ContainsKey(currentPkg.Name)) { + TryAddToPackagesFound(currentPkg); + yield return currentPkg; } + else + { + List pkgVersions = _packagesFound[currentPkg.Name] as List; + // _packagesFound has currentPkg.name in it, but the version is not the same + if (!pkgVersions.Contains(FormatPkgVersionString(currentPkg))) + { + TryAddToPackagesFound(currentPkg); + + yield return currentPkg; + } + } + } #endregion diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index c245369a0..6fa0db2fc 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -486,7 +486,7 @@ private void MoveFilesIntoInstallPath( } } else - { + { var scriptXML = pkgInfo.Name + "_InstalledScriptInfo.xml"; if (!_savePkg) { @@ -509,8 +509,8 @@ private void MoveFilesIntoInstallPath( File.Delete(Path.Combine(finalModuleVersionDir, pkgInfo.Name + PSScriptFileExt)); } } - else { - _cmdletPassedIn.WriteVerbose(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, scriptXML))); + else { + _cmdletPassedIn.WriteVerbose(string.Format("Moving '{0}' to '{1}'", Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, scriptXML))); Utils.MoveFiles(Path.Combine(dirNameVersion, scriptXML), Path.Combine(installPath, scriptXML)); } @@ -591,12 +591,11 @@ private List InstallPackages( _cmdletPassedIn.WriteWarning("Installing dependencies is not currently supported for V3 server protocol repositories. The package will be installed without installing dependencies."); } - HashSet myHash = new HashSet(StringComparer.OrdinalIgnoreCase); // Get the dependencies from the installed package. if (parentPkgObj.Dependencies.Length > 0) { bool depFindFailed = false; - foreach (PSResourceInfo depPkg in findHelper.FindDependencyPackages(currentServer, currentResponseUtil, parentPkgObj, repository, myHash)) + foreach (PSResourceInfo depPkg in findHelper.FindDependencyPackages(currentServer, currentResponseUtil, parentPkgObj, repository)) { if (depPkg == null) { diff --git a/test/FindPSResourceTests/FindPSResourceV2Server.Tests.ps1 b/test/FindPSResourceTests/FindPSResourceV2Server.Tests.ps1 index d63507f12..fb1e6fa08 100644 --- a/test/FindPSResourceTests/FindPSResourceV2Server.Tests.ps1 +++ b/test/FindPSResourceTests/FindPSResourceV2Server.Tests.ps1 @@ -415,4 +415,25 @@ Describe 'Test HTTP Find-PSResource for V2 Server Protocol' -tags 'ManualValidat $res.Name | Should -Be $testModuleName } + + It "find should not duplicate dependencies found" { + $res = Find-PSResource -Name "Az" -IncludeDependencies -Repository $PSGalleryName + $res | Should -Not -BeNullOrEmpty + + $foundPkgs = [System.Collections.Generic.HashSet[String]]::new() + $duplicatePkgsFound = $false + foreach ($item in $res) + { + if ($foundPkgs.Contains($item.Name)) + { + write-host "this pkg already found" + $duplicatePkgsFound = $true + break + } + + $foundPkgs.Add($item.Name) + } + + $duplicatePkgsFound | Should -BeFalse + } }