@@ -117,6 +117,7 @@ function Get-ModuleFastPlan {
117117
118118 while ($currentTasks.Count -gt 0 ) {
119119 # The timeout here allow ctrl-C to continue working in PowerShell
120+ # -1 is returned by WaitAny if we hit the timeout before any tasks completed
120121 $noTasksYetCompleted = -1
121122 [int ]$thisTaskIndex = [Task ]::WaitAny($currentTasks , 500 )
122123 if ($thisTaskIndex -eq $noTasksYetCompleted ) { continue }
@@ -164,7 +165,7 @@ function Get-ModuleFastPlan {
164165 }
165166
166167 # FIXME: Replace with Overlap
167- [Version ]$versionMatch = $moduleSpec .FindHighestMatch ( $inlinedVersions )
168+ [Version ]$versionMatch = Limit-ModuleFastSpecVersions - ModuleSpec $currentModuleSpec - Highest - Versions $inlinedVersions
168169
169170
170171 if ($versionMatch ) {
@@ -186,8 +187,8 @@ function Get-ModuleFastPlan {
186187 $pages = $response.items | Where-Object {
187188 [Version ]$upper = $PSItem.Upper
188189 [Version ]$lower = $PSItem.Lower
189- if ($currentModuleSpec.RequiredVersion ) {
190- if ($currentModuleSpec.RequiredVersion -le $upper -and $currentModuleSpec.RequiredVersion -ge $lower ) {
190+ if ($currentModuleSpec.Required ) {
191+ if ($currentModuleSpec.Required -le $upper -and $currentModuleSpec.Required -ge $lower ) {
191192 return $true
192193 }
193194 } else {
@@ -217,21 +218,22 @@ function Get-ModuleFastPlan {
217218 # TODO: This is relatively slow and blocking, but we would need complicated logic to process it in the main task handler loop.
218219 # I really should make a pipeline that breaks off tasks based on the type of the response.
219220 # This should be a relatively rare query that only happens when the latest package isn't being resolved.
220- $entries = foreach ($page in $pages ) {
221- [ Task [ string ]] $tasks = Get-ModuleInfoAsync @httpContext - Endpoint $Source - Uri $page
221+ [ Task [ string ][]] $tasks = foreach ($page in $pages ) {
222+ Get-ModuleInfoAsync @httpContext - Uri $page . ' @id '
222223 # This loop is here to support ctrl-c cancellation again
223- while ($false -in $tasks.IsCompleted ) {
224- [Task ]::WaitAll($tasks , 500 )
225- }
226- ($tasks.GetAwaiter ().GetResult() | ConvertTo-Json ).items.catalogEntry
227224 }
225+ while ($false -in $tasks.IsCompleted ) {
226+ [void ][Task ]::WaitAll($tasks , 500 )
227+ }
228+ $entries = ($tasks.GetAwaiter ().GetResult() | ConvertFrom-Json ).items.catalogEntry
229+
228230 # TODO: Dedupe this logic with the above
229- [version [ ]]$inlinedVersions = $entries.version
231+ [HashSet [ Version ]]$inlinedVersions = $entries.version
230232 | Where-Object {
231233 $PSItem -and ! $PSItem.contains (' -' )
232234 }
233235
234- [Version ]$versionMatch = $moduleSpec.FindHighestMatch ( $inlinedVersions )
236+ [Version ]$versionMatch = Limit-ModuleFastSpecVersions - ModuleSpec $moduleSpec - Versions $inlinedVersions - Highest
235237 if ($versionMatch ) {
236238 Write-Debug " $currentModuleSpec `: Found satisfying version $versionMatch in one of the additional pages."
237239 $selectedEntry = $entries | Where-Object version -EQ $versionMatch
@@ -243,7 +245,7 @@ function Get-ModuleFastPlan {
243245 throw ' Something other than exactly 1 selectedModule was specified. This should never happen and is a bug'
244246 }
245247
246- $moduleInfo = [ComparableModuleSpecification ]@ {
248+ $moduleInfo = [ModuleFastSpec ]@ {
247249 ModuleName = $selectedEntry.id
248250 RequiredVersion = $selectedEntry.version
249251 # TODO: Fix in Server API GUID = $moduleInfo.Guid
@@ -269,14 +271,11 @@ function Get-ModuleFastPlan {
269271 # HACK: I should be using the Id provided by the server, for now I'm just guessing because
270272 # I need to add it to the ComparableModuleSpec class
271273 Write-Debug " $currentModuleSpec `: Processing dependencies"
272- try {
273- [List [ComparableModuleSpecification ]]$dependencies = $dependencyInfo | Parse- NugetDependency
274-
275- } catch { Wait-Debugger }
274+ [List [ModuleFastSpec ]]$dependencies = $dependencyInfo | Parse- NugetDependency
276275 Write-Debug " $currentModuleSpec has $ ( $dependencies.count ) dependencies"
277276
278277 # TODO: Where loop filter maybe
279- [ComparableModuleSpecification []]$dependenciesToResolve = $dependencies | Where-Object {
278+ [ModuleFastSpec []]$dependenciesToResolve = $dependencies | Where-Object {
280279 # TODO: This dependency resolution logic should be a separate function
281280 # Maybe ModulesToInstall should be nested/grouped by Module Name then version to speed this up, as it currently
282281 # enumerates every time which shouldn't be a big deal for small dependency trees but might be a
@@ -405,6 +404,11 @@ class ModuleFastSpec : IComparable {
405404 }
406405 }
407406
407+ # ModuleSpecification Compatible Aliases
408+ hidden [SemanticVersion ]Get_RequiredVersion() { return $this.Required }
409+ hidden [SemanticVersion ]Get_Version() { return $this.Min }
410+ hidden [SemanticVersion ]Get_MaximumVersion() { return $this.Max }
411+
408412 # Constructors
409413
410414 # HACK: A helper because we can't do constructor chaining in PowerShell
@@ -418,11 +422,11 @@ class ModuleFastSpec : IComparable {
418422 $Name = $ModuleSpec.Name
419423 $Guid = $ModuleSpec.Guid
420424 if ($ModuleSpec.RequiredVersion ) {
421- $Min = $ModuleSpec.RequiredVersion
422- $Max = $ModuleSpec.RequiredVersion
425+ $Min = [ ModuleFastSpec ]::ParseVersionString( $ModuleSpec.RequiredVersion )
426+ $Max = [ ModuleFastSpec ]::ParseVersionString( $ModuleSpec.RequiredVersion )
423427 } else {
424- if ( $moduleSpec.Version ) { $Min = $ ModuleSpec.Version }
425- if ( $moduleSpec.MaximumVersion ) { $Max = $ ModuleSpec.MaximumVersion }
428+ $Min = $moduleSpec.Version ? [ ModuleFastSpec ]::ParseVersionString( $ ModuleSpec.Version ) : $null
429+ $Max = $moduleSpec.MaximumVersion ? [ ModuleFastSpec ]::ParseVersionString( $ ModuleSpec.MaximumVersion ) : $null
426430 }
427431 }
428432
@@ -590,7 +594,7 @@ class ModuleFastSpec : IComparable {
590594 ([ModuleSpecification ]) { return $this.Equals ([ModuleFastSpec ]$obj ) }
591595
592596 # When comparing a version, we want to return equal if the version is within the range of the spec
593- ([SemanticVersion ]) { return $this.ContainsSemVer ($obj ) }
597+ ([SemanticVersion ]) { return $this.CompareTo ($obj ) -eq 0 }
594598 ([string ]) { return $this.Equals ([ModuleFastSpec ]::ParseVersionString($obj )) }
595599 ([Version ]) { return $this.Equals ([ModuleFastSpec ]::ParseVersion($obj )) }
596600 default {
@@ -609,14 +613,15 @@ class ModuleFastSpec : IComparable {
609613 # Implementation of https://learn.microsoft.com/en-us/dotnet/api/system.icomparable-1.compareto
610614 [int ] CompareTo([Object ]$obj ) {
611615 if ($null -eq $obj ) { throw [NotSupportedException ]' null not supported' }
612- if ($this.Equals ($obj )) { return 0 }
613616
614617 # This is somewhat analagous to C# Pattern Matching: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/pattern-matching#compare-discrete-values
615618 switch ($obj.GetType ()) {
616619 # We determine greater than or less than if the version is within the range of the spec or not
617620 ([SemanticVersion ]) {
621+ if ($obj -ge $this.Min -and $obj -le $this.Max ) { return 0 }
618622 if ($obj -lt $this.Min ) { return 1 }
619623 if ($obj -gt $this.Max ) { return -1 }
624+ throw ' Unexpected comparison result. This should never happen and is a bug in ModuleFastSpec'
620625 }
621626 ([ModuleFastSpec ]) {
622627 if (-not $obj.Required ) { throw [NotSupportedException ]' Cannot compare two range specs as they can overlap. Supply a required spec to this range' }
@@ -629,6 +634,7 @@ class ModuleFastSpec : IComparable {
629634 return $this.CompareTo ([ModuleFastSpec ]::ParseVersionString($obj ))
630635 }
631636 default {
637+ if ($this.Equals ($obj )) { return 0 }
632638 # Try a cast. This should work for ModuleSpecification
633639 try {
634640 return $this.CompareTo ([ModuleFastSpec ]$obj )
@@ -922,27 +928,27 @@ filter Parse-NugetDependency ([Parameter(Mandatory, ValueFromPipeline)]$Dependen
922928 # Treat a null result as "any"
923929 if ([String ]::IsNullOrEmpty($Version )) {
924930 $dep.ModuleVersion = ' 0.0.0'
925- return [ComparableModuleSpecification ]$dep
931+ return [ModuleFastSpec ]$dep
926932 }
927933
928934 # If it is a direct module specification, treat this as a required version.
929935 $exactVersion = $null
930936 if ([Version ]::TryParse($Version , [ref ]$exactVersion )) {
931937 $dep.RequiredVersion = $exactVersion
932- return [ComparableModuleSpecification ]$dep
938+ return [ModuleFastSpec ]$dep
933939 }
934940
935941 # If it is an open bound, set ModuleVersion to 0.0.0 (meaning any version)
936942 if ($version -eq ' (, )' ) {
937943 $dep.ModuleVersion = ' 0.0.0'
938- return [ComparableModuleSpecification ]$dep
944+ return [ModuleFastSpec ]$dep
939945 }
940946
941947 # If it is an exact match version (has brackets and doesn't have a comma), set version accordingly
942948 $ExactVersionRegex = ' \[([^,]+)\]'
943949 if ($version -match $ExactVersionRegex ) {
944950 $dep.RequiredVersion = $matches [1 ]
945- return [ComparableModuleSpecification ]$dep
951+ return [ModuleFastSpec ]$dep
946952 }
947953
948954 # Parse all other remainder options. For this purpose we ignore inclusive vs. exclusive
@@ -954,7 +960,7 @@ filter Parse-NugetDependency ([Parameter(Mandatory, ValueFromPipeline)]$Dependen
954960 if ($minimumVersion -and $maximumVersion -and ($minimumVersion -eq $maximumVersion )) {
955961 # If the minimum and maximum versions match, we treat this as an explicit version
956962 $dep.RequiredVersion = $minimumVersion
957- return [ComparableModuleSpecification ]$dep
963+ return [ModuleFastSpec ]$dep
958964 } elseif ($minimumVersion -or $maximumVersion ) {
959965 if ($minimumVersion ) { $dep.ModuleVersion = $minimumVersion }
960966 if ($maximumVersion ) { $dep.MaximumVersion = $maximumVersion }
@@ -963,7 +969,7 @@ filter Parse-NugetDependency ([Parameter(Mandatory, ValueFromPipeline)]$Dependen
963969 Write-Warning " $ ( $dep.ModuleName ) has an invalid version spec, falling back to maximum version."
964970 }
965971
966- return [ComparableModuleSpecification ]$dep
972+ return [ModuleFastSpec ]$dep
967973}
968974
969975<#
@@ -1038,7 +1044,7 @@ function Find-LocalModule {
10381044 Write-Verbose " $moduleSpec `: module folder exists at $moduleNamePath but no modules found that match the version spec."
10391045 continue
10401046 }
1041- $versionMatch = Find-HighestSatisfiesVersion - ModuleSpec $ModuleSpec - Version $candidateVersions
1047+ $versionMatch = Limit-ModuleFastSpecVersions - ModuleSpec $ModuleSpec - Versions $candidateVersions - Highest
10421048 if ($versionMatch ) {
10431049 $manifestPath = Join-Path $moduleNamePath $ ([Version ]$versionMatch ) " $ ( $ModuleSpec.Name ) .psd1"
10441050 if (-not [File ]::Exists($manifestPath )) {
@@ -1050,7 +1056,6 @@ function Find-LocalModule {
10501056 }
10511057 }
10521058 }
1053-
10541059 return $false
10551060}
10561061
@@ -1066,27 +1071,51 @@ function Get-NormalizedVersions ([Version]$Version) {
10661071<#
10671072Given an array of versions, find the ones that satisfy the module spec. Returns $false if no match is found.
10681073#>
1069- function Find-VersionMatch {
1074+ function Limit-ModuleFastSpecVersions {
10701075 [OutputType ([Version []])]
1076+ [OutputType ([Version ], ParameterSetName = ' Highest' )]
10711077 param (
10721078 [Parameter (Mandatory )][ModuleFastSpec ]$ModuleSpec ,
10731079 # Versions that are potential candidates to satisfy the modulespec
10741080 [Parameter (Mandatory )][HashSet [Version ]]$Versions ,
1075- [Switch ]$Highest
1081+ # Only return the highest version that satisfies the spec
1082+ [Parameter (ParameterSetName = ' Highest' )][Switch ]$Highest
10761083 )
1077- $Versions | Where-Object {
1078- $ModuleSpec.ContainsSemVer ([ ModuleFastSpec ]::ParseVersion( $Version ) )
1084+ $candidates = $ Versions | Where-Object {
1085+ $ModuleSpec.Matches ( $PSItem )
10791086 }
1087+ -not $Highest ? $candidates : $candidates | Sort-Object - Descending | Select-Object - First 1
10801088}
10811089
1082- # BUG: This is required because the SMA.SemanticVersion class cannot handle build (+) by itself
1083- # https://github.com/PowerShell/PowerShell/issues/14605
1084- function ConvertTo-Version ([SemanticVersion ]$Version , [string ]$BuildHint = ' SEMBUILD' ) {
1085- if ($null -eq ($Version.BuildLabel -as [int ])) {
1086- Write-Warning [InvalidDataException ]" BuildLabel $ ( $Version.BuildLabel ) is not numeric and cannot be cast, and will be skipped."
1087- [Version ]::new($Version.Major , $Version.Minor , $Version.Patch )
1090+ function Limit-ModuleFastSpecSemanticVersions {
1091+ [OutputType ([SemanticVersion []])]
1092+ [OutputType ([Version ], ParameterSetName = ' Highest' )]
1093+ param (
1094+ [Parameter (Mandatory )][ModuleFastSpec ]$ModuleSpec ,
1095+ # Versions that are potential candidates to satisfy the modulespec
1096+ [Parameter (Mandatory )][HashSet [SemanticVersion ]]$Versions ,
1097+ # Only return the highest version that satisfies the spec
1098+ [Parameter (ParameterSetName = ' Highest' )][Switch ]$Highest
1099+ )
1100+ $Versions | Where-Object {
1101+ $ModuleSpec.Matches ($PSItem )
10881102 }
1089- [Version ]::new($Version.Major , $Version.Minor , $Version.Patch )
1103+ -not $Highest ? $Versions : @ ($Versions | Sort-Object - Descending | Select-Object - First 1 )
1104+ }
1105+ function Limit-ModuleFastSpecs {
1106+ [OutputType ([ModuleFastSpec []])]
1107+ [OutputType ([Version ], ParameterSetName = ' Highest' )]
1108+ param (
1109+ [Parameter (Mandatory )][ModuleFastSpec ]$ModuleSpec ,
1110+ # Versions that are potential candidates to satisfy the modulespec
1111+ [Parameter (Mandatory )][HashSet [ModuleFastSpec ]]$ModuleSpecs ,
1112+ # Only return the highest version that satisfies the spec
1113+ [Parameter (ParameterSetName = ' Highest' )][Switch ]$Highest
1114+ )
1115+ $ModuleSpecs | Where-Object {
1116+ $ModuleSpec.Matches ($PSItem )
1117+ }
1118+ -not $Highest ? $Versions : @ ($Versions | Sort-Object - Descending | Select-Object - First 1 )
10901119}
10911120
10921121# endregion Helpers
@@ -1096,6 +1125,9 @@ function ConvertTo-Version([SemanticVersion]$Version, [string]$BuildHint = 'SEMB
10961125
10971126# ## ISSUES
10981127# FIXME: When doing directory match comparison for local modules, need to preserve original folder name. See: Reflection 4.8
1128+ # To fix this we will just use the name out of the module.psd1 when installing
10991129# FIXME: DBops dependency version issue
1100- # FIXME: Dependency range when it is just a version number with a really high build - Vmware.Vimautomation.core 10.0.0.xxxxxxxx
1101- # FIXME: Semver and 4 octet modules are incompatible, need to handle this
1130+ # FIXME: Currently Legacy 1.2.3 will be selected over 1.2.3.1111 due to semver versioning sort order, need additional logic if build is present. This can be implemented in CompareTo based on the build tag.
1131+ # FIXME IN GALLERY: A version not in the latest 100 versions will not be found, we are checking for a NextLink but this needs to be wired up to a page request.
1132+
1133+ # Export-ModuleMember -Function Get-ModuleFastPlan
0 commit comments