From ba2d230d4c9585af3e65cd80cc1974988474ab51 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Tue, 30 Jun 2026 13:56:54 -0700
Subject: [PATCH 01/24] Implement unimplemented concurrency methods for all
server classes
---
src/code/ContainerRegistryServerAPICalls.cs | 53 ++++-
src/code/FindHelper.cs | 88 +++------
src/code/InstallHelper.cs | 4 +-
src/code/LocalServerApiCalls.cs | 65 ++++++-
src/code/NuGetServerAPICalls.cs | 53 ++++-
src/code/V3ServerAPICalls.cs | 205 +++++++++++++++++++-
6 files changed, 388 insertions(+), 80 deletions(-)
diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs
index b7af3b98d..1b011c884 100644
--- a/src/code/ContainerRegistryServerAPICalls.cs
+++ b/src/code/ContainerRegistryServerAPICalls.cs
@@ -82,14 +82,40 @@ public ContainerRegistryServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmd
#region Overridden Methods
+ ///
+ /// Async find method which allows for searching for single name with specific version.
+ /// Name: no wildcard support
+ /// Version: no wildcard support
+ /// This is the concurrent (parallel) counterpart of FindVersion().
+ ///
public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindVersionAsync is not implemented for ContainerRegistryServerAPICalls.");
+ debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindVersionAsync()");
+ FindResults findResponse = FindVersion(packageName, version, type, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
+ ///
+ /// Async find method which allows for searching for single name with version range.
+ /// Name: no wildcard support
+ /// Version: supports wildcards
+ /// This is the concurrent (parallel) counterpart of FindVersionGlobbing().
+ ///
public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindVersionGlobbingAsync is not implemented for ContainerRegistryServerAPICalls.");
+ debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindVersionGlobbingAsync()");
+ FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
///
@@ -158,9 +184,21 @@ public override FindResults FindName(string packageName, bool includePrerelease,
}
+ ///
+ /// Async find method which allows for searching for single name and returns latest version.
+ /// Name: no wildcard support
+ /// This is the concurrent (parallel) counterpart of FindName().
+ ///
public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindNameAsync is not implemented for ContainerRegistryServerAPICalls.");
+ debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindNameAsync()");
+ FindResults findResponse = FindName(packageName, includePrerelease, type, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
///
@@ -329,7 +367,14 @@ public override Stream InstallPackage(string packageName, string packageVersion,
///
public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindNameAsync is not implemented for ContainerRegistryServerAPICalls.");
+ debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::InstallPackageAsync()");
+ Stream results = InstallPackage(packageName, packageVersion, includePrerelease, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(results);
}
///
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index 1a074b67e..fb5c9a28d 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -912,17 +912,12 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R
ConcurrentDictionary> cachedNetworkCalls = new ConcurrentDictionary>();
Task response = null;
- if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2) {
- string key = $"{pkgName}|{_nugetVersion.ToNormalizedString()}|{_type}";
- response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(pkgName, _nugetVersion.ToNormalizedString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
-
- responses = response.GetAwaiter().GetResult();
+ string key = $"{pkgName}|{_nugetVersion.ToNormalizedString()}|{_type}";
+ response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(pkgName, _nugetVersion.ToNormalizedString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
+
+ responses = response.GetAwaiter().GetResult();
- Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
- }
- else {
- responses = currentServer.FindVersion(pkgName, _nugetVersion.ToNormalizedString(), _type, out errRecord);
- }
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
else
{
@@ -996,15 +991,10 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R
{
ConcurrentDictionary> cachedNetworkCalls = new ConcurrentDictionary>();
Task response = null;
- if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2) {
- string key = $"{pkgName}|{_versionRange.ToString()}|{_type}";
- response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(pkgName, _versionRange, _prerelease, _type, getOnlyLatest: false, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
-
- responses = response.GetAwaiter().GetResult();
- }
- else {
- responses = currentServer.FindVersionGlobbing(pkgName, _versionRange, _prerelease, _type, getOnlyLatest: false, out errRecord);
- }
+ string key = $"{pkgName}|{_versionRange.ToString()}|{_type}";
+ response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(pkgName, _versionRange, _prerelease, _type, getOnlyLatest: false, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
+
+ responses = response.GetAwaiter().GetResult();
}
else
{
@@ -1189,7 +1179,7 @@ internal void FindDependencyPackagesHelper(ServerApiCall currentServer, Response
//const int PARALLEL_THRESHOLD = 5; // TODO: Trottle limit from user, defaults to 5;
int processorCount = Environment.ProcessorCount;
int maxDegreeOfParallelism = processorCount * 4;
- if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2 && currentPkg.Dependencies.Length > processorCount)
+ if (currentPkg.Dependencies.Length > processorCount)
{
Parallel.ForEach(currentPkg.Dependencies, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, dep =>
{
@@ -1293,20 +1283,13 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
Task response = null;
debugMsgs.Enqueue("In FindHelper::FindDependencyWithSpecificVersion()");
- if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2)
- {
- // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results
- string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}";
- debugMsgs.Enqueue("Checking if network call is cached.");
- response = _cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
-
- responses = response.GetAwaiter().GetResult();
- }
- else
- {
- responses = currentServer.FindVersion(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, out errRecord);
- }
-
+
+ // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results
+ string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}";
+ debugMsgs.Enqueue("Checking if network call is cached.");
+ response = _cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
+
+ responses = response.GetAwaiter().GetResult();
// Error handling and Convert to PSResource object
if (errRecord != null)
@@ -1369,19 +1352,12 @@ private PSResourceInfo FindDependencyWithLowerBound(
Task response = null;
debugMsgs.Enqueue("In FindHelper::FindDependencyWithLowerBound()");
- if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2)
- {
- // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results
- string key = $"{dep.Name}|*|{_type}";
- debugMsgs.Enqueue("Checking if network call is cached.");
- response = _cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindNameAsync(dep.Name, includePrerelease: true, _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
-
- responses = response.GetAwaiter().GetResult();
- }
- else
- {
- responses = currentServer.FindName(dep.Name, includePrerelease: true, _type, out errRecord);
- }
+ // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results
+ string key = $"{dep.Name}|*|{_type}";
+ debugMsgs.Enqueue("Checking if network call is cached.");
+ response = _cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindNameAsync(dep.Name, includePrerelease: true, _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
+
+ responses = response.GetAwaiter().GetResult();
// Error handling and Convert to PSResource object
if (errRecord != null)
@@ -1445,21 +1421,13 @@ private PSResourceInfo FindDependencyWithUpperBound(
ConcurrentDictionary> cachedNetworkCalls = new ConcurrentDictionary>();
debugMsgs.Enqueue("In FindHelper::FindDependencyWithUpperBound()");
+ // See if the network call we're making is already caced, if not, call FindNameAsync() and cache results
+ string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}";
+ debugMsgs.Enqueue("Checking if network call is cached.");
+ response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(dep.Name, dep.VersionRange, includePrerelease: true, ResourceType.None, getOnlyLatest: true, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
- if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2)
- {
- // See if the network call we're making is already caced, if not, call FindNameAsync() and cache results
- string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}";
- debugMsgs.Enqueue("Checking if network call is cached.");
- response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(dep.Name, dep.VersionRange, includePrerelease: true, ResourceType.None, getOnlyLatest: true, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
-
- responses = response.GetAwaiter().GetResult();
+ responses = response.GetAwaiter().GetResult();
- }
- else
- {
- responses = currentServer.FindVersionGlobbing(dep.Name, dep.VersionRange, includePrerelease: true, ResourceType.None, getOnlyLatest: true, out errRecord);
- }
// Error handling and Convert to PSResource object
if (errRecord != null)
diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs
index c89ef0ee9..e3f95b616 100644
--- a/src/code/InstallHelper.cs
+++ b/src/code/InstallHelper.cs
@@ -801,7 +801,7 @@ private ConcurrentDictionary BeginPackageInstall(
}
else
{
- // Concurrent updates, currently only implemented for v2 server repositories
+ // Concurrent updates
// Find all dependencies
if (!skipDependencyCheck)
{
@@ -853,7 +853,7 @@ private ConcurrentDictionary InstallParentAndDependencyPackag
// TODO: figure out a good threshold and parallel count
int processorCount = Environment.ProcessorCount;
_cmdletPassedIn.WriteDebug($"parentAndDeps.Count is {parentAndDeps.Count}, processor count is: {processorCount}");
- if (currentServer.Repository.ApiVersion == PSRepositoryInfo.APIVersion.V2 && parentAndDeps.Count > processorCount)
+ if (parentAndDeps.Count > processorCount)
{
_cmdletPassedIn.WriteDebug($"parentAndDeps.Count is greater than processor count");
// Set the maximum degree of parallelism to 32? (Invoke-Command has default of 32, that's where we got this number from)
diff --git a/src/code/LocalServerApiCalls.cs b/src/code/LocalServerApiCalls.cs
index a8e505acb..1ebb72dcb 100644
--- a/src/code/LocalServerApiCalls.cs
+++ b/src/code/LocalServerApiCalls.cs
@@ -41,14 +41,40 @@ public LocalServerAPICalls (PSRepositoryInfo repository, PSCmdlet cmdletPassedIn
#region Overridden Methods
+ ///
+ /// Async find method which allows for searching for single name with specific version.
+ /// Name: no wildcard support
+ /// Version: no wildcard support
+ /// This is the concurrent (parallel) counterpart of FindVersion().
+ ///
public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException();
+ debugMsgs.Enqueue("In LocalServerApiCalls::FindVersionAsync()");
+ FindResults findResponse = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
+ ///
+ /// Async find method which allows for searching for single name with version range.
+ /// Name: no wildcard support
+ /// Version: supports wildcards
+ /// This is the concurrent (parallel) counterpart of FindVersionGlobbing().
+ ///
public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException();
+ debugMsgs.Enqueue("In LocalServerApiCalls::FindVersionGlobbingAsync()");
+ FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
///
/// Find method which allows for searching for all packages from a repository and returns latest version for each.
@@ -124,9 +150,21 @@ public override FindResults FindName(string packageName, bool includePrerelease,
return FindNameHelper(packageName, Utils.EmptyStrArray, includePrerelease, type, out errRecord);
}
+ ///
+ /// Async find method which allows for searching for single name and returns latest version.
+ /// Name: no wildcard support
+ /// This is the concurrent (parallel) counterpart of FindName().
+ ///
public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException();
+ debugMsgs.Enqueue("In LocalServerApiCalls::FindNameAsync()");
+ FindResults findResponse = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
///
@@ -278,7 +316,26 @@ public override Stream InstallPackage(string packageName, string packageVersion,
///
public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("InstallPackageAsync is not implemented for LocalServerAPICalls.");
+ debugMsgs.Enqueue("In LocalServerApiCalls::InstallPackageAsync()");
+ Stream results = new MemoryStream();
+ if (string.IsNullOrEmpty(packageVersion))
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ exception: new ArgumentNullException($"Package version could not be found for {packageName}"),
+ "PackageVersionNullOrEmptyError",
+ ErrorCategory.InvalidArgument,
+ _cmdletPassedIn));
+
+ return Task.FromResult(results);
+ }
+
+ results = InstallVersion(packageName, packageVersion, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(results);
}
#endregion
diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs
index 1c6bb2828..cc4c68223 100644
--- a/src/code/NuGetServerAPICalls.cs
+++ b/src/code/NuGetServerAPICalls.cs
@@ -49,14 +49,40 @@ public NuGetServerAPICalls (PSRepositoryInfo repository, PSCmdlet cmdletPassedIn
#region Overridden Methods
+ ///
+ /// Async find method which allows for searching for single name with specific version.
+ /// Name: no wildcard support
+ /// Version: no wildcard support
+ /// This is the concurrent (parallel) counterpart of FindVersion().
+ ///
public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindVersionAsync is not implemented for NuGetServerAPICalls.");
+ debugMsgs.Enqueue("In NuGetServerAPICalls::FindVersionAsync()");
+ FindResults findResponse = FindVersion(packageName, version, type, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
+ ///
+ /// Async find method which allows for searching for single name with version range.
+ /// Name: no wildcard support
+ /// Version: supports wildcards
+ /// This is the concurrent (parallel) counterpart of FindVersionGlobbing().
+ ///
public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindVersionGlobbingAsync is not implemented for NuGetServerAPICalls.");
+ debugMsgs.Enqueue("In NuGetServerAPICalls::FindVersionGlobbingAsync()");
+ FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
///
/// Find method which allows for searching for all packages from a repository and returns latest version for each.
@@ -193,9 +219,21 @@ public override FindResults FindName(string packageName, bool includePrerelease,
return new FindResults(stringResponse: new string[]{ response }, hashtableResponse: emptyHashResponses, responseType: FindResponseType);
}
+ ///
+ /// Async find method which allows for searching for single name and returns latest version.
+ /// Name: no wildcard support
+ /// This is the concurrent (parallel) counterpart of FindName().
+ ///
public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindNameAsync is not implemented for NuGetServerAPICalls.");
+ debugMsgs.Enqueue("In NuGetServerAPICalls::FindNameAsync()");
+ FindResults findResponse = FindName(packageName, includePrerelease, type, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
///
@@ -471,7 +509,14 @@ public override Stream InstallPackage(string packageName, string packageVersion,
///
public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("InstallPackageAsync is not implemented for NuGetServerAPICalls.");
+ debugMsgs.Enqueue("In NuGetServerAPICalls::InstallPackageAsync()");
+ Stream results = InstallPackage(packageName, packageVersion, includePrerelease, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(results);
}
///
diff --git a/src/code/V3ServerAPICalls.cs b/src/code/V3ServerAPICalls.cs
index a2beb1545..3693646a5 100644
--- a/src/code/V3ServerAPICalls.cs
+++ b/src/code/V3ServerAPICalls.cs
@@ -89,14 +89,43 @@ public V3ServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmdletPassedIn, Ne
#region Overridden Methods
+ ///
+ /// Async find method which allows for searching for single name with specific version.
+ /// Name: no wildcard support
+ /// Version: no wildcard support
+ /// Examples: Search "NuGet.Server.Core" "3.0.0-beta"
+ /// This is the concurrent (parallel) counterpart of FindVersion().
+ ///
public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindVersionAsync is not implemented for V3ServerAPICalls.");
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionAsync()");
+ FindResults findResponse = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
+ ///
+ /// Async find method which allows for searching for single name with version range.
+ /// Name: no wildcard support
+ /// Version: supports wildcards
+ /// Examples: Search "NuGet.Server.Core" "[1.0.0.0, 5.0.0.0]"
+ /// Search "NuGet.Server.Core" "3.*"
+ /// This is the concurrent (parallel) counterpart of FindVersionGlobbing().
+ ///
public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindVersionAsync is not implemented for V3ServerAPICalls.");
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionGlobbingAsync()");
+ FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
///
@@ -166,9 +195,22 @@ public override FindResults FindName(string packageName, bool includePrerelease,
return FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out errRecord);
}
+ ///
+ /// Async find method which allows for searching for single name and returns latest version.
+ /// Name: no wildcard support
+ /// Examples: Search "Newtonsoft.Json"
+ /// This is the concurrent (parallel) counterpart of FindName().
+ ///
public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("FindVersionAsync is not implemented for V3ServerAPICalls.");
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindNameAsync()");
+ FindResults findResponse = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ }
+
+ return Task.FromResult(findResponse);
}
///
@@ -239,6 +281,7 @@ public override FindResults FindNameGlobbingWithTag(string packageName, string[]
///
public override FindResults FindVersionGlobbing(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord)
{
+ // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when called from FindVersionGlobbingAsync().
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindVersionGlobbing()");
string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord);
if (errRecord != null)
@@ -267,6 +310,7 @@ public override FindResults FindVersionGlobbing(string packageName, VersionRange
if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion) && versionRange.Satisfies(pkgVersion))
{
+ // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when called from FindVersionGlobbingAsync(). ?
_cmdletPassedIn.WriteDebug($"Package version parsed as '{pkgVersion}' satisfies the version range");
if (!pkgVersion.IsPrerelease || includePrerelease)
{
@@ -353,9 +397,34 @@ public override Stream InstallPackage(string packageName, string packageVersion,
/// Examples: Install "PowerShellGet" -Version "3.5.0-alpha"
/// Install "PowerShellGet" -Version "3.0.0"
///
- public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
+ public override async Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- throw new NotImplementedException("InstallPackageAsync is not implemented for NuGetServerAPICalls.");
+ debugMsgs.Enqueue("In V3ServerAPICalls::InstallPackageAsync()");
+ Stream results = new MemoryStream();
+ if (string.IsNullOrEmpty(packageVersion))
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ exception: new ArgumentNullException($"Package version could not be found for {packageName}"),
+ "PackageVersionNullOrEmptyError",
+ ErrorCategory.InvalidArgument,
+ this));
+
+ return results;
+ }
+
+ if (!NuGetVersion.TryParse(packageVersion, out NuGetVersion requiredVersion))
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ new ArgumentException($"Version {packageVersion} to be installed is not a valid NuGet version."),
+ "InstallVersionFailure",
+ ErrorCategory.InvalidArgument,
+ this));
+
+ return results;
+ }
+
+ results = await InstallHelperAsync(packageName, requiredVersion, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return results;
}
#endregion
@@ -514,6 +583,7 @@ private FindResults FindTagsFromNuGetRepo(string[] tags, bool includePrerelease,
///
private FindResults FindNameHelper(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord)
{
+ // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when called from FindNameAsync(). ?
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameHelper()");
string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord);
if (errRecord != null)
@@ -553,6 +623,7 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu
if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion))
{
+ // ?
_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
if (!pkgVersion.IsPrerelease || includePrerelease)
{
@@ -610,6 +681,7 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu
///
private FindResults FindVersionHelper(string packageName, string version, string[] tags, ResourceType type, out ErrorRecord errRecord)
{
+ // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when called from FindVersionAsync(). ?
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindVersionHelper()");
if (!NuGetVersion.TryParse(version, out NuGetVersion requiredVersion))
{
@@ -621,7 +693,7 @@ private FindResults FindVersionHelper(string packageName, string version, string
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
}
- _cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'");
+ //_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'");
string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord);
if (errRecord != null)
@@ -830,6 +902,88 @@ private Stream InstallHelper(string packageName, NuGetVersion version, out Error
return content.ReadAsStreamAsync().GetAwaiter().GetResult();
}
+ ///
+ /// Helper method that is called by InstallPackageAsync()
+ /// For InstallName() we want latest version installed (so version parameter passed in will be null), for InstallVersion() we want specified, non-null version installed.
+ /// This is the async counterpart of InstallHelper() used for concurrent (parallel) installation workflows.
+ ///
+ private async Task InstallHelperAsync(string packageName, NuGetVersion version, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
+ {
+ debugMsgs.Enqueue("In V3ServerAPICalls::InstallHelperAsync()");
+ Stream pkgStream = null;
+ bool getLatestVersion = true;
+ if (version != null)
+ {
+ getLatestVersion = false;
+ }
+
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, packageContentProperty, isSearch: false, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ return pkgStream;
+ }
+
+ if (versionedResponses.Length == 0)
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ new Exception($"Package with name '{packageName}' and version '{version}' could not be found in repository '{Repository.Name}'"),
+ "InstallFailure",
+ ErrorCategory.InvalidResult,
+ this));
+
+ return null;
+ }
+
+ string pkgContentUrl = String.Empty;
+ if (getLatestVersion)
+ {
+ pkgContentUrl = versionedResponses[0];
+ }
+ else
+ {
+ // loop through responses to find one containing required version
+ foreach (string response in versionedResponses)
+ {
+ // Response will be "packageContent" element value that looks like: "{packageBaseAddress}/{packageName}/{normalizedVersion}/{packageName}.{normalizedVersion}.nupkg"
+ // Ex: https://api.nuget.org/v3-flatcontainer/test_module/1.0.0/test_module.1.0.0.nupkg
+ if (response.Contains(version.ToNormalizedString()))
+ {
+ pkgContentUrl = response;
+ break;
+ }
+ }
+ }
+
+ if (String.IsNullOrEmpty(pkgContentUrl))
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ new Exception($"Package with name '{packageName}' and version '{version}' could not be found in repository '{Repository.Name}'"),
+ "InstallFailure",
+ ErrorCategory.InvalidResult,
+ this));
+
+ return null;
+ }
+
+ var content = await HttpRequestCallForContentAsync(pkgContentUrl, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+
+ if (content is null)
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ new Exception($"No content was returned by repository '{Repository.Name}'"),
+ "InstallFailureContentNullv3Async",
+ ErrorCategory.InvalidResult,
+ this));
+
+ return new MemoryStream();
+ }
+
+ pkgStream = await content.ReadAsStreamAsync();
+
+ return pkgStream;
+ }
+
///
/// Gets the versioned package entries from the RegistrationsBaseUrl resource
/// i.e when the package Name being searched for does not contain wildcard
@@ -1060,6 +1214,7 @@ private string FindSearchQueryService(Dictionary resources, out
///
private JsonElement[] GetMetadataElementFromIdLinkElement(JsonElement idLinkElement, string packageName, out string upperVersion, out ErrorRecord errRecord)
{
+ // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ?
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetMetadataElementFromIdLinkElement()");
upperVersion = String.Empty;
JsonElement[] innerItems = new JsonElement[]{};
@@ -1102,6 +1257,7 @@ private JsonElement[] GetMetadataElementFromIdLinkElement(JsonElement idLinkElem
}
else
{
+ // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ?
_cmdletPassedIn.WriteDebug($"Package with name '{packageName}' did not have 'upper' property so package versions may not be in descending order.");
}
@@ -1228,6 +1384,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
}
else
{
+ // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ?
_cmdletPassedIn.WriteDebug($"Metadata for package with name '{packageName}' did not have inner 'items' or '@Id' properties.");
}
}
@@ -1267,6 +1424,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
}
else
{
+ // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ?
_cmdletPassedIn.WriteDebug($"Metadata for package with name '{packageName}' was not of value kind type string or object.");
}
}
@@ -1355,6 +1513,7 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat
///
private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out ErrorRecord errRecord)
{
+ // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when reached from the async find methods. ?
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::IsLatestVersionFirstForSearch()");
errRecord = null;
bool latestVersionFirst = true;
@@ -1675,6 +1834,40 @@ private HttpContent HttpRequestCallForContent(string requestUrlV3, out ErrorReco
return content;
}
+ ///
+ /// Helper method that makes the HTTP request for the V3 server protocol url passed in for install APIs asynchronously.
+ /// This is the async counterpart of HttpRequestCallForContent() used for concurrent (parallel) installation workflows.
+ ///
+ private async Task HttpRequestCallForContentAsync(string requestUrlV3, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
+ {
+ debugMsgs.Enqueue("In V3ServerAPICalls::HttpRequestCallForContentAsync()");
+ HttpContent content = null;
+ try
+ {
+ debugMsgs.Enqueue($"Request url is '{requestUrlV3}'");
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrlV3);
+
+ content = await SendV3RequestForContentAsync(request, _sessionClient);
+ }
+ catch (Exception e)
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ exception: e,
+ "HttpRequestCallForContentFailure",
+ ErrorCategory.InvalidResult,
+ this));
+
+ return null;
+ }
+
+ if (string.IsNullOrEmpty(content?.ToString()))
+ {
+ debugMsgs.Enqueue("Response is empty");
+ }
+
+ return content;
+ }
+
///
/// Helper method called by HttpRequestCall() that makes the HTTP request for string response.
///
From fbb940f572eb2b132d2bc196b0cb7ab8620d5e72 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Tue, 30 Jun 2026 14:11:38 -0700
Subject: [PATCH 02/24] build fixes
---
src/code/FindHelper.cs | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index fb5c9a28d..693c00aca 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -904,15 +904,13 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R
// Example: Find-PSResource -Name "Az" -Version "3.0.0.0"
// Example: Find-PSResource -Name "Az" -Version "3.0.0.0" -Tag "Windows"
_cmdletPassedIn.WriteDebug("Exact version and package name are specified");
-
+ string key = string.Empty;
FindResults responses = null;
if (_tag.Length == 0)
{
-
-
ConcurrentDictionary> cachedNetworkCalls = new ConcurrentDictionary>();
Task response = null;
- string key = $"{pkgName}|{_nugetVersion.ToNormalizedString()}|{_type}";
+ key = $"{pkgName}|{_nugetVersion.ToNormalizedString()}|{_type}";
response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(pkgName, _nugetVersion.ToNormalizedString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
responses = response.GetAwaiter().GetResult();
@@ -1318,7 +1316,7 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
string pkgVersion = FormatPkgVersionString(depPkg);
debugMsgs.Enqueue($"Found dependency '{depPkg.Name}' version '{pkgVersion}'");
- string key = $"{depPkg.Name}{pkgVersion}";
+ key = $"{depPkg.Name}{pkgVersion}";
if (!depPkgsFound.ContainsKey(key))
{
// Add pkg to collection of packages found then find dependencies
@@ -1386,7 +1384,7 @@ private PSResourceInfo FindDependencyWithLowerBound(
string pkgVersion = FormatPkgVersionString(depPkg);
debugMsgs.Enqueue($"Found dependency '{depPkg.Name}' version '{pkgVersion}'");
- string key = $"{depPkg.Name}{pkgVersion}";
+ key = $"{depPkg.Name}{pkgVersion}";
if (!depPkgsFound.ContainsKey(key))
{
// Add pkg to collection of packages found then find dependencies
@@ -1457,7 +1455,7 @@ private PSResourceInfo FindDependencyWithUpperBound(
string pkgVersion = FormatPkgVersionString(depPkg);
debugMsgs.Enqueue($"Found dependency '{depPkg.Name}' version '{pkgVersion}'");
- string key = $"{depPkg.Name}{pkgVersion}";
+ key = $"{depPkg.Name}{pkgVersion}";
if (!depPkgsFound.ContainsKey(key))
{
// Add pkg to collection of packages found then find dependencies
From a4730af8fdc5f96f90637b3dbf1c4b36619ec575 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Jul 2026 17:15:37 +0000
Subject: [PATCH 03/24] Fix thread-safety in
ContainerRegistryServerAPICalls.InstallPackageAsync
InstallPackageAsync was calling the synchronous InstallPackage() which
writes to _cmdletPassedIn.WriteDebug, causing cross-thread cmdlet stream
writes when used from Parallel.ForEach.
- Refactor InstallPackageAsync to not call InstallPackage(); inline the
null-version check and enqueue all messages via the provided queues
- Add a queue-aware InstallVersion overload that uses ConcurrentQueue
parameters instead of _cmdletPassedIn.Write* calls, for use by the
async install path
- Keep the original InstallVersion(out ErrorRecord) overload intact for
the synchronous path
---
src/code/ContainerRegistryServerAPICalls.cs | 84 ++++++++++++++++++++-
1 file changed, 81 insertions(+), 3 deletions(-)
diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs
index 1b011c884..376c3c053 100644
--- a/src/code/ContainerRegistryServerAPICalls.cs
+++ b/src/code/ContainerRegistryServerAPICalls.cs
@@ -368,12 +368,20 @@ public override Stream InstallPackage(string packageName, string packageVersion,
public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::InstallPackageAsync()");
- Stream results = InstallPackage(packageName, packageVersion, includePrerelease, out ErrorRecord errRecord);
- if (errRecord != null)
+ Stream results = new MemoryStream();
+ if (string.IsNullOrEmpty(packageVersion))
{
- errorMsgs.Enqueue(errRecord);
+ errorMsgs.Enqueue(new ErrorRecord(
+ exception: new ArgumentNullException($"Package version could not be found for {packageName}"),
+ "PackageVersionNullOrEmptyError",
+ ErrorCategory.InvalidArgument,
+ _cmdletPassedIn));
+
+ return Task.FromResult(results);
}
+ string packageNameForInstall = PrependMARPrefix(packageName);
+ results = InstallVersion(packageNameForInstall, packageVersion, errorMsgs, debugMsgs, verboseMsgs);
return Task.FromResult(results);
}
@@ -445,6 +453,76 @@ private Stream InstallVersion(
return responseContent.ReadAsStreamAsync().Result;
}
+ ///
+ /// Installs a package with version specified using concurrent queues for output instead of cmdlet streams.
+ /// Used by the async install path to avoid cross-thread cmdlet stream writes.
+ ///
+ private Stream InstallVersion(
+ string packageName,
+ string packageVersion,
+ ConcurrentQueue errorMsgs,
+ ConcurrentQueue debugMsgs,
+ ConcurrentQueue verboseMsgs)
+ {
+ debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::InstallVersion()");
+ string packageNameLowercase = packageName.ToLower();
+ string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ try
+ {
+ Directory.CreateDirectory(tempPath);
+ }
+ catch (Exception e)
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ exception: e,
+ "InstallVersionTempDirCreationError",
+ ErrorCategory.InvalidResult,
+ _cmdletPassedIn));
+
+ return null;
+ }
+
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: false, out ErrorRecord errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ return null;
+ }
+
+ verboseMsgs.Enqueue($"Getting manifest for {packageNameLowercase} - {packageVersion}");
+ var manifest = GetContainerRegistryRepositoryManifest(packageNameLowercase, packageVersion, containerRegistryAccessToken, out errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ return null;
+ }
+ string digest = GetDigestFromManifest(manifest, out errRecord);
+ if (errRecord != null)
+ {
+ errorMsgs.Enqueue(errRecord);
+ return null;
+ }
+
+ verboseMsgs.Enqueue($"Downloading blob for {packageNameLowercase} - {packageVersion}");
+ HttpContent responseContent;
+ try
+ {
+ responseContent = GetContainerRegistryBlobAsync(packageNameLowercase, digest, containerRegistryAccessToken).Result;
+ }
+ catch (Exception e)
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ exception: e,
+ "InstallVersionGetContainerRegistryBlobAsyncError",
+ ErrorCategory.InvalidResult,
+ _cmdletPassedIn));
+
+ return null;
+ }
+
+ return responseContent.ReadAsStreamAsync().Result;
+ }
+
#endregion
#region Authentication and Token Methods
From f800970c24f0e00a891ea5c04a2624c4b9caf43e Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Wed, 1 Jul 2026 10:55:24 -0700
Subject: [PATCH 04/24] Fix copilot commit for container registry
---
src/code/ContainerRegistryServerAPICalls.cs | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs
index 376c3c053..cd22c1c5d 100644
--- a/src/code/ContainerRegistryServerAPICalls.cs
+++ b/src/code/ContainerRegistryServerAPICalls.cs
@@ -343,7 +343,6 @@ public override Stream InstallPackage(string packageName, string packageVersion,
Stream results = new MemoryStream();
if (string.IsNullOrEmpty(packageVersion))
{
- errRecord = new ErrorRecord(
exception: new ArgumentNullException($"Package version could not be found for {packageName}"),
"PackageVersionNullOrEmptyError",
ErrorCategory.InvalidArgument,
@@ -381,7 +380,7 @@ public override Task InstallPackageAsync(string packageName, string pack
}
string packageNameForInstall = PrependMARPrefix(packageName);
- results = InstallVersion(packageNameForInstall, packageVersion, errorMsgs, debugMsgs, verboseMsgs);
+ results = InstallVersionAsync(packageNameForInstall, packageVersion, errorMsgs, debugMsgs, verboseMsgs);
return Task.FromResult(results);
}
@@ -457,14 +456,14 @@ private Stream InstallVersion(
/// Installs a package with version specified using concurrent queues for output instead of cmdlet streams.
/// Used by the async install path to avoid cross-thread cmdlet stream writes.
///
- private Stream InstallVersion(
+ private Stream InstallVersionAsync(
string packageName,
string packageVersion,
ConcurrentQueue errorMsgs,
ConcurrentQueue debugMsgs,
ConcurrentQueue verboseMsgs)
{
- debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::InstallVersion()");
+ debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::InstallVersionAsync()");
string packageNameLowercase = packageName.ToLower();
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
try
From 6ab7367f660b33782e1ebabcbed9d06117ad322a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Jul 2026 18:01:27 +0000
Subject: [PATCH 05/24] Avoid cmdlet stream writes in NuGet FindVersionAsync
path
---
src/code/NuGetServerAPICalls.cs | 62 ++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs
index cc4c68223..d8c6b5ffe 100644
--- a/src/code/NuGetServerAPICalls.cs
+++ b/src/code/NuGetServerAPICalls.cs
@@ -58,7 +58,18 @@ public NuGetServerAPICalls (PSRepositoryInfo repository, PSCmdlet cmdletPassedIn
public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In NuGetServerAPICalls::FindVersionAsync()");
- FindResults findResponse = FindVersion(packageName, version, type, out ErrorRecord errRecord);
+ var queryBuilder = new NuGetV2QueryBuilder(new Dictionary{
+ { "id", $"'{packageName}'" },
+ });
+ var filterBuilder = queryBuilder.FilterBuilder;
+
+ // We need to explicitly add 'Id eq ' whenever $filter is used, otherwise arbitrary results are returned.
+ filterBuilder.AddCriterion($"Id eq '{packageName}'");
+ filterBuilder.AddCriterion($"NormalizedVersion eq '{packageName}'");
+
+ var requestUrl = $"{Repository.Uri}/FindPackagesById()?{queryBuilder.BuildQueryString()}";
+ string response = HttpRequestCallAsync(requestUrl, debugMsgs, out ErrorRecord errRecord);
+ FindResults findResponse = new FindResults(stringResponse: new string[] { response }, hashtableResponse: emptyHashResponses, responseType: FindResponseType);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -617,6 +628,55 @@ private HttpContent HttpRequestCallForContent(string requestUrl, out ErrorRecord
return content;
}
+ ///
+ /// Helper method that makes the HTTP request for the NuGet server protocol url passed in for async find APIs.
+ /// This helper writes diagnostics to the provided debug queue and avoids cmdlet stream writes.
+ ///
+ private string HttpRequestCallAsync(string requestUrl, ConcurrentQueue debugMsgs, out ErrorRecord errRecord)
+ {
+ debugMsgs.Enqueue("In NuGetServerAPICalls::HttpRequestCallAsync()");
+ errRecord = null;
+ string response = string.Empty;
+
+ try
+ {
+ debugMsgs.Enqueue($"Request url is: '{requestUrl}'");
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
+ response = SendRequestAsync(request, _sessionClient).GetAwaiter().GetResult();
+ }
+ catch (HttpRequestException e)
+ {
+ errRecord = new ErrorRecord(
+ exception: e,
+ "HttpRequestFallFailure",
+ ErrorCategory.ConnectionError,
+ this);
+ }
+ catch (ArgumentNullException e)
+ {
+ errRecord = new ErrorRecord(
+ exception: e,
+ "HttpRequestFallFailure",
+ ErrorCategory.ConnectionError,
+ this);
+ }
+ catch (InvalidOperationException e)
+ {
+ errRecord = new ErrorRecord(
+ exception: e,
+ "HttpRequestFallFailure",
+ ErrorCategory.ConnectionError,
+ this);
+ }
+
+ if (string.IsNullOrEmpty(response))
+ {
+ debugMsgs.Enqueue("Response is empty");
+ }
+
+ return response;
+ }
+
#endregion
#region Private Methods
From dbd1689eec89fe96cd08651ae48da755d49f8563 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Jul 2026 18:13:00 +0000
Subject: [PATCH 06/24] Fix V3 async helper logging to use debug queues
---
src/code/V3ServerAPICalls.cs | 61 +++++++++++++++++++++---------------
1 file changed, 36 insertions(+), 25 deletions(-)
diff --git a/src/code/V3ServerAPICalls.cs b/src/code/V3ServerAPICalls.cs
index 3693646a5..9df736d7e 100644
--- a/src/code/V3ServerAPICalls.cs
+++ b/src/code/V3ServerAPICalls.cs
@@ -99,7 +99,7 @@ public V3ServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmdletPassedIn, Ne
public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionAsync()");
- FindResults findResponse = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out ErrorRecord errRecord);
+ FindResults findResponse = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out ErrorRecord errRecord, debugMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -119,7 +119,7 @@ public override Task FindVersionAsync(string packageName, string ve
public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionGlobbingAsync()");
- FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord);
+ FindResults findResponse = FindVersionGlobbingHelper(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord, debugMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -204,7 +204,7 @@ public override FindResults FindName(string packageName, bool includePrerelease,
public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In V3ServerAPICalls::FindNameAsync()");
- FindResults findResponse = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out ErrorRecord errRecord);
+ FindResults findResponse = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out ErrorRecord errRecord, debugMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -281,9 +281,13 @@ public override FindResults FindNameGlobbingWithTag(string packageName, string[]
///
public override FindResults FindVersionGlobbing(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord)
{
- // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when called from FindVersionGlobbingAsync().
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindVersionGlobbing()");
- string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord);
+ return FindVersionGlobbingHelper(packageName, versionRange, includePrerelease, type, getOnlyLatest, out errRecord);
+ }
+
+ private FindResults FindVersionGlobbingHelper(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
+ {
+ WriteDebug("In V3ServerAPICalls::FindVersionGlobbing()", debugMsgs);
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs);
if (errRecord != null)
{
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
@@ -310,8 +314,7 @@ public override FindResults FindVersionGlobbing(string packageName, VersionRange
if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion) && versionRange.Satisfies(pkgVersion))
{
- // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when called from FindVersionGlobbingAsync(). ?
- _cmdletPassedIn.WriteDebug($"Package version parsed as '{pkgVersion}' satisfies the version range");
+ WriteDebug($"Package version parsed as '{pkgVersion}' satisfies the version range", debugMsgs);
if (!pkgVersion.IsPrerelease || includePrerelease)
{
satisfyingVersions.Add(response);
@@ -581,11 +584,10 @@ private FindResults FindTagsFromNuGetRepo(string[] tags, bool includePrerelease,
///
/// Helper method called by FindName() and FindNameWithTag()
///
- private FindResults FindNameHelper(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord)
+ private FindResults FindNameHelper(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
{
- // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when called from FindNameAsync(). ?
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameHelper()");
- string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord);
+ WriteDebug("In V3ServerAPICalls::FindNameHelper()", debugMsgs);
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs);
if (errRecord != null)
{
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
@@ -623,8 +625,7 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu
if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion))
{
- // ?
- _cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
+ WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'", debugMsgs);
if (!pkgVersion.IsPrerelease || includePrerelease)
{
// Versions are always in descending order i.e 5.0.0, 3.0.0, 1.0.0 so grabbing the first match suffices
@@ -679,10 +680,9 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu
///
/// Helper method called by FindVersion() and FindVersionWithTag()
///
- private FindResults FindVersionHelper(string packageName, string version, string[] tags, ResourceType type, out ErrorRecord errRecord)
+ private FindResults FindVersionHelper(string packageName, string version, string[] tags, ResourceType type, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
{
- // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when called from FindVersionAsync(). ?
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindVersionHelper()");
+ WriteDebug("In V3ServerAPICalls::FindVersionHelper()", debugMsgs);
if (!NuGetVersion.TryParse(version, out NuGetVersion requiredVersion))
{
errRecord = new ErrorRecord(
@@ -695,7 +695,7 @@ private FindResults FindVersionHelper(string packageName, string version, string
}
//_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'");
- string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord);
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs);
if (errRecord != null)
{
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
@@ -989,7 +989,7 @@ private async Task InstallHelperAsync(string packageName, NuGetVersion v
/// i.e when the package Name being searched for does not contain wildcard
/// This is called by FindNameHelper(), FindVersionHelper(), FindVersionGlobbing(), InstallHelper()
///
- private string[] GetVersionedPackageEntriesFromRegistrationsResource(string packageName, string propertyName, bool isSearch, out ErrorRecord errRecord)
+ private string[] GetVersionedPackageEntriesFromRegistrationsResource(string packageName, string propertyName, bool isSearch, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
{
// TODO: pass in ConcurrentQueue to write out debug message.
//_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetVersionedPackageEntriesFromRegistrationsResource()");
@@ -1006,7 +1006,7 @@ private string[] GetVersionedPackageEntriesFromRegistrationsResource(string pack
return responses;
}
- responses = GetVersionedResponsesFromRegistrationsResource(registrationsBaseUrl, packageName, propertyName, isSearch, out errRecord);
+ responses = GetVersionedResponsesFromRegistrationsResource(registrationsBaseUrl, packageName, propertyName, isSearch, out errRecord, debugMsgs);
if (errRecord != null)
{
return Utils.EmptyStrArray;
@@ -1452,7 +1452,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
/// The "packageContent" property is used for download, and the value is a URI for the .nupkg file.
///
///
- private string[] GetVersionedResponsesFromRegistrationsResource(string registrationsBaseUrl, string packageName, string property, bool isSearch, out ErrorRecord errRecord)
+ private string[] GetVersionedResponsesFromRegistrationsResource(string registrationsBaseUrl, string packageName, string property, bool isSearch, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
{
// TODO: pass in ConcurrentQueue to write out debug message.
//_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetVersionedResponsesFromRegistrationsResource()");
@@ -1490,7 +1490,7 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat
if (isSearch)
{
- if (!IsLatestVersionFirstForSearch(versionedResponseArr, out errRecord))
+ if (!IsLatestVersionFirstForSearch(versionedResponseArr, out errRecord, debugMsgs))
{
Array.Reverse(versionedResponseArr);
}
@@ -1511,10 +1511,9 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat
/// ADO feeds usually return version entries in descending order, but Nuget.org repository returns them in ascending order.
/// Package versions will reflect prerelease preference, but upper version and lower version would not so we don't use them for comparison.
///
- private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out ErrorRecord errRecord)
+ private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
{
- // TODO: pass in ConcurrentQueue for debug messages so this is thread-safe when reached from the async find methods. ?
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::IsLatestVersionFirstForSearch()");
+ WriteDebug("In V3ServerAPICalls::IsLatestVersionFirstForSearch()", debugMsgs);
errRecord = null;
bool latestVersionFirst = true;
int versionResponsesCount = versionedResponses.Length;
@@ -1606,6 +1605,18 @@ private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out Erro
return latestVersionFirst;
}
+ private void WriteDebug(string message, ConcurrentQueue debugMsgs = null)
+ {
+ if (debugMsgs == null)
+ {
+ _cmdletPassedIn.WriteDebug(message);
+ }
+ else
+ {
+ debugMsgs.Enqueue(message);
+ }
+ }
+
///
/// Returns true if the nupkg URI entries for each package version are arranged in descending order with respect to the package's version.
/// ADO feeds usually return version entries in descending order, but Nuget.org repository returns them in ascending order.
From efb000d090039941dc343a493aabf685af192922 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Wed, 1 Jul 2026 11:24:52 -0700
Subject: [PATCH 07/24] Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
src/code/FindHelper.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index 693c00aca..0410af585 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -1419,7 +1419,7 @@ private PSResourceInfo FindDependencyWithUpperBound(
ConcurrentDictionary> cachedNetworkCalls = new ConcurrentDictionary>();
debugMsgs.Enqueue("In FindHelper::FindDependencyWithUpperBound()");
- // See if the network call we're making is already caced, if not, call FindNameAsync() and cache results
+ // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results
string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}";
debugMsgs.Enqueue("Checking if network call is cached.");
response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(dep.Name, dep.VersionRange, includePrerelease: true, ResourceType.None, getOnlyLatest: true, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
From 0e9c684f2d7d46ce3bfc9f754e791a6b1ae6221a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Jul 2026 18:28:13 +0000
Subject: [PATCH 08/24] Fix specific-version async dependency error handling
path
---
src/code/FindHelper.cs | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index 0410af585..f46b843b6 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -1282,12 +1282,10 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
debugMsgs.Enqueue("In FindHelper::FindDependencyWithSpecificVersion()");
- // See if the network call we're making is already cached, if not, call FindNameAsync() and cache results
+ // Call FindVersionAsync() for dependency with specific version.
string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}";
- debugMsgs.Enqueue("Checking if network call is cached.");
- response = _cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
-
- responses = response.GetAwaiter().GetResult();
+ responses = currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs).GetAwaiter().GetResult();
+ errorMsgs.TryPeek(out errRecord);
// Error handling and Convert to PSResource object
if (errRecord != null)
From e3c1af50c786662e0a7ef62c3860a4edf9f35c90 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Jul 2026 18:32:20 +0000
Subject: [PATCH 09/24] Scope async dependency queue handling to current
operation
---
src/code/FindHelper.cs | 35 +++++++++++++++++++++++++++++++----
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index f46b843b6..0e4c53865 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -1278,14 +1278,41 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
PSResourceInfo depPkg = null;
ErrorRecord errRecord = null;
FindResults responses = null;
- Task response = null;
debugMsgs.Enqueue("In FindHelper::FindDependencyWithSpecificVersion()");
-
+ ConcurrentQueue operationErrorMsgs = new ConcurrentQueue();
+ ConcurrentQueue operationWarningMsgs = new ConcurrentQueue();
+ ConcurrentQueue operationDebugMsgs = new ConcurrentQueue();
+ ConcurrentQueue operationVerboseMsgs = new ConcurrentQueue();
+
// Call FindVersionAsync() for dependency with specific version.
string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}";
- responses = currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, errorMsgs, warningMsgs, debugMsgs, verboseMsgs).GetAwaiter().GetResult();
- errorMsgs.TryPeek(out errRecord);
+ responses = currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, operationErrorMsgs, operationWarningMsgs, operationDebugMsgs, operationVerboseMsgs).GetAwaiter().GetResult();
+
+ while (operationErrorMsgs.TryDequeue(out ErrorRecord queuedError))
+ {
+ if (errRecord == null)
+ {
+ errRecord = queuedError;
+ }
+
+ errorMsgs.Enqueue(queuedError);
+ }
+
+ while (operationWarningMsgs.TryDequeue(out string queuedWarning))
+ {
+ warningMsgs.Enqueue(queuedWarning);
+ }
+
+ while (operationDebugMsgs.TryDequeue(out string queuedDebug))
+ {
+ debugMsgs.Enqueue(queuedDebug);
+ }
+
+ while (operationVerboseMsgs.TryDequeue(out string queuedVerbose))
+ {
+ verboseMsgs.Enqueue(queuedVerbose);
+ }
// Error handling and Convert to PSResource object
if (errRecord != null)
From 9b92e77a6ec610b222e488f3946a3a6b7ec3a574 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Jul 2026 18:38:00 +0000
Subject: [PATCH 10/24] Fix version-range async path: reset errRecord and flush
concurrent queues
---
src/code/FindHelper.cs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index 0e4c53865..3d149ab02 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -984,6 +984,7 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R
// Example: Find-PSResource -Name "Az" -Version "[1.0.0.0, 3.0.0.0]"
_cmdletPassedIn.WriteDebug("Version range and package name are specified");
+ errRecord = null;
FindResults responses = null;
if (_tag.Length == 0)
{
@@ -993,6 +994,8 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R
response = cachedNetworkCalls.GetOrAdd(key, _ => currentServer.FindVersionGlobbingAsync(pkgName, _versionRange, _prerelease, _type, getOnlyLatest: false, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
responses = response.GetAwaiter().GetResult();
+
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
else
{
From 900db222e7dba80c8ed6e6383f4dab11de9d6f24 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Wed, 1 Jul 2026 13:31:22 -0700
Subject: [PATCH 11/24] build fixes
---
src/code/ContainerRegistryServerAPICalls.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs
index cd22c1c5d..cd8c4c8be 100644
--- a/src/code/ContainerRegistryServerAPICalls.cs
+++ b/src/code/ContainerRegistryServerAPICalls.cs
@@ -343,6 +343,7 @@ public override Stream InstallPackage(string packageName, string packageVersion,
Stream results = new MemoryStream();
if (string.IsNullOrEmpty(packageVersion))
{
+ errRecord = new ErrorRecord(
exception: new ArgumentNullException($"Package version could not be found for {packageName}"),
"PackageVersionNullOrEmptyError",
ErrorCategory.InvalidArgument,
From 4cdbea84189a71965fb1c8b293ef78e3fcbda55c Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Mon, 20 Jul 2026 21:05:45 -0700
Subject: [PATCH 12/24] Incorporate code review changes - complete TODOs and
remove unneeded key creation
---
src/code/FindHelper.cs | 3 +-
src/code/V2ServerAPICalls.cs | 10 +-
src/code/V3ServerAPICalls.cs | 263 +++++++++++++++++++----------------
3 files changed, 147 insertions(+), 129 deletions(-)
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index 3d149ab02..06a0e9df0 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -1289,7 +1289,6 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
ConcurrentQueue operationVerboseMsgs = new ConcurrentQueue();
// Call FindVersionAsync() for dependency with specific version.
- string key = $"{dep.Name}|{dep.VersionRange.MaxVersion.ToString()}|{_type}";
responses = currentServer.FindVersionAsync(dep.Name, dep.VersionRange.MaxVersion.ToString(), _type, operationErrorMsgs, operationWarningMsgs, operationDebugMsgs, operationVerboseMsgs).GetAwaiter().GetResult();
while (operationErrorMsgs.TryDequeue(out ErrorRecord queuedError))
@@ -1344,7 +1343,7 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
string pkgVersion = FormatPkgVersionString(depPkg);
debugMsgs.Enqueue($"Found dependency '{depPkg.Name}' version '{pkgVersion}'");
- key = $"{depPkg.Name}{pkgVersion}";
+ string key = $"{depPkg.Name}{pkgVersion}";
if (!depPkgsFound.ContainsKey(key))
{
// Add pkg to collection of packages found then find dependencies
diff --git a/src/code/V2ServerAPICalls.cs b/src/code/V2ServerAPICalls.cs
index 6f2c5c3e1..963ed5ed7 100644
--- a/src/code/V2ServerAPICalls.cs
+++ b/src/code/V2ServerAPICalls.cs
@@ -1074,7 +1074,6 @@ private string HttpRequestCall(string requestUrlV2, out ErrorRecord errRecord)
///
private async Task HttpRequestCallAsync(string requestUrlV2, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: Async methods cannot have out ref, so currently handling errorRecords as thrown exceptions.
debugMsgs.Enqueue("In V2ServerAPICalls::HttpRequestCallAsync()");
string response = string.Empty;
@@ -1131,7 +1130,6 @@ private async Task HttpRequestCallAsync(string requestUrlV2, ConcurrentQ
///
private async Task HttpRequestCallForContentAsync(string requestUrlV2, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: Async methods cannot have out ref, so need to handle errorRecords a different way.
debugMsgs.Enqueue("In V2ServerAPICalls::HttpRequestCallForContentAsync()");
HttpContent content = null;
@@ -1744,13 +1742,7 @@ public override async Task FindVersionGlobbingAsync(string packageN
debugMsgs.Enqueue($"Count is '{count}'");
// skip 100
skip += 100;
- // TODO: this should be an async method
- var tmpResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, skip, getOnlyLatest, out ErrorRecord errRecord);
- if (errRecord != null)
- {
- Utils.EnqueueIfNotNull(errorMsgs, errRecord);
- return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v2FindResponseType);
- }
+ var tmpResponse = await FindVersionGlobbingAsync(packageName, versionRange, includePrerelease, type, skip, getOnlyLatest, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
responses.Add(tmpResponse);
count--;
}
diff --git a/src/code/V3ServerAPICalls.cs b/src/code/V3ServerAPICalls.cs
index 9df736d7e..234fc513a 100644
--- a/src/code/V3ServerAPICalls.cs
+++ b/src/code/V3ServerAPICalls.cs
@@ -99,7 +99,7 @@ public V3ServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmdletPassedIn, Ne
public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionAsync()");
- FindResults findResponse = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out ErrorRecord errRecord, debugMsgs);
+ FindResults findResponse = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out ErrorRecord errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -119,7 +119,7 @@ public override Task FindVersionAsync(string packageName, string ve
public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionGlobbingAsync()");
- FindResults findResponse = FindVersionGlobbingHelper(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord, debugMsgs);
+ FindResults findResponse = FindVersionGlobbingHelper(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -151,9 +151,15 @@ public override FindResults FindAll(bool includePrerelease, ResourceType type, o
public override FindResults FindTags(string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindTags()");
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
if (_isNuGetRepo || _isJFrogRepo)
{
- return FindTagsFromNuGetRepo(tags, includePrerelease, out errRecord);
+ FindResults findResults = FindTagsFromNuGetRepo(tags, includePrerelease, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return findResults;
}
else
{
@@ -163,6 +169,7 @@ public override FindResults FindTags(string[] tags, bool includePrerelease, Reso
ErrorCategory.InvalidOperation,
this);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
}
}
@@ -192,7 +199,13 @@ public override FindResults FindCommandOrDscResource(string[] tags, bool include
public override FindResults FindName(string packageName, bool includePrerelease, ResourceType type, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindName()");
- return FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out errRecord);
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+ FindResults findResults = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return findResults;
}
///
@@ -204,7 +217,7 @@ public override FindResults FindName(string packageName, bool includePrerelease,
public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In V3ServerAPICalls::FindNameAsync()");
- FindResults findResponse = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out ErrorRecord errRecord, debugMsgs);
+ FindResults findResponse = FindNameHelper(packageName, tags: Utils.EmptyStrArray, includePrerelease, type, out ErrorRecord errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -222,7 +235,13 @@ public override Task FindNameAsync(string packageName, bool include
public override FindResults FindNameWithTag(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameWithTag()");
- return FindNameHelper(packageName, tags, includePrerelease, type, out errRecord);
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+ FindResults findResults = FindNameHelper(packageName, tags, includePrerelease, type, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return findResults;
}
///
@@ -232,9 +251,15 @@ public override FindResults FindNameWithTag(string packageName, string[] tags, b
public override FindResults FindNameGlobbing(string packageName, bool includePrerelease, ResourceType type, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameGlobbing()");
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
if (_isNuGetRepo || _isJFrogRepo || _isGHPkgsRepo || _isMyGetRepo)
{
- return FindNameGlobbingFromNuGetRepo(packageName, tags: Utils.EmptyStrArray, includePrerelease, out errRecord);
+ FindResults findResults = FindNameGlobbingFromNuGetRepo(packageName, tags: Utils.EmptyStrArray, includePrerelease, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return findResults;
}
else
{
@@ -244,6 +269,7 @@ public override FindResults FindNameGlobbing(string packageName, bool includePre
ErrorCategory.InvalidOperation,
this);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
}
}
@@ -255,9 +281,15 @@ public override FindResults FindNameGlobbing(string packageName, bool includePre
public override FindResults FindNameGlobbingWithTag(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameGlobbingWithTag()");
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
if (_isNuGetRepo || _isJFrogRepo || _isGHPkgsRepo || _isMyGetRepo)
{
- return FindNameGlobbingFromNuGetRepo(packageName, tags, includePrerelease, out errRecord);
+ FindResults findResults = FindNameGlobbingFromNuGetRepo(packageName, tags, includePrerelease, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return findResults;
}
else
{
@@ -267,6 +299,7 @@ public override FindResults FindNameGlobbingWithTag(string packageName, string[]
ErrorCategory.InvalidOperation,
this);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
}
}
@@ -281,13 +314,19 @@ public override FindResults FindNameGlobbingWithTag(string packageName, string[]
///
public override FindResults FindVersionGlobbing(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord)
{
- return FindVersionGlobbingHelper(packageName, versionRange, includePrerelease, type, getOnlyLatest, out errRecord);
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+ FindResults findResults = FindVersionGlobbingHelper(packageName, versionRange, includePrerelease, type, getOnlyLatest, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return findResults;
}
- private FindResults FindVersionGlobbingHelper(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
+ private FindResults FindVersionGlobbingHelper(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- WriteDebug("In V3ServerAPICalls::FindVersionGlobbing()", debugMsgs);
- string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs);
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionGlobbing()");
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
@@ -314,7 +353,7 @@ private FindResults FindVersionGlobbingHelper(string packageName, VersionRange v
if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion) && versionRange.Satisfies(pkgVersion))
{
- WriteDebug($"Package version parsed as '{pkgVersion}' satisfies the version range", debugMsgs);
+ debugMsgs.Enqueue($"Package version parsed as '{pkgVersion}' satisfies the version range");
if (!pkgVersion.IsPrerelease || includePrerelease)
{
satisfyingVersions.Add(response);
@@ -347,7 +386,13 @@ private FindResults FindVersionGlobbingHelper(string packageName, VersionRange v
public override FindResults FindVersion(string packageName, string version, ResourceType type, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindVersion()");
- return FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out errRecord);
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+ FindResults findResults = FindVersionHelper(packageName, version, tags: Utils.EmptyStrArray, type, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return findResults;
}
///
@@ -360,7 +405,13 @@ public override FindResults FindVersion(string packageName, string version, Reso
public override FindResults FindVersionWithTag(string packageName, string version, string[] tags, ResourceType type, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindVersionWithTag()");
- return FindVersionHelper(packageName, version, tags: tags, type, out errRecord);
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+ FindResults findResults = FindVersionHelper(packageName, version, tags: tags, type, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return findResults;
}
/** INSTALL APIS **/
@@ -375,8 +426,11 @@ public override FindResults FindVersionWithTag(string packageName, string versio
///
public override Stream InstallPackage(string packageName, string packageVersion, bool includePrerelease, out ErrorRecord errRecord)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::InstallPackage()");
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+ debugMsgs.Enqueue("In V3ServerAPICalls::InstallPackage()");
Stream results = new MemoryStream();
if (string.IsNullOrEmpty(packageVersion))
{
@@ -386,10 +440,13 @@ public override Stream InstallPackage(string packageName, string packageVersion,
ErrorCategory.InvalidArgument,
_cmdletPassedIn);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
return results;
}
- return InstallVersion(packageName, packageVersion, out errRecord);
+ Stream installResults = InstallVersion(packageName, packageVersion, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return installResults;
}
///
@@ -437,9 +494,9 @@ public override async Task InstallPackageAsync(string packageName, strin
///
/// Helper method called by FindNameGlobbing() and FindNameGlobbingWithTag() for special case where repository is NuGet.org repository.
///
- private FindResults FindNameGlobbingFromNuGetRepo(string packageName, string[] tags, bool includePrerelease, out ErrorRecord errRecord)
+ private FindResults FindNameGlobbingFromNuGetRepo(string packageName, string[] tags, bool includePrerelease, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindNameGlobbingFromNuGetRepo()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindNameGlobbingFromNuGetRepo()");
var names = packageName.Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries);
string querySearchTerm;
@@ -475,7 +532,7 @@ private FindResults FindNameGlobbingFromNuGetRepo(string packageName, string[] t
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
}
- var matchingPkgEntries = GetVersionedPackageEntriesFromSearchQueryResource(querySearchTerm, includePrerelease, out errRecord);
+ var matchingPkgEntries = GetVersionedPackageEntriesFromSearchQueryResource(querySearchTerm, includePrerelease, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
@@ -549,13 +606,13 @@ private FindResults FindNameGlobbingFromNuGetRepo(string packageName, string[] t
///
/// Helper method called by FindTags() for special case where repository is NuGet.org repository.
///
- private FindResults FindTagsFromNuGetRepo(string[] tags, bool includePrerelease, out ErrorRecord errRecord)
+ private FindResults FindTagsFromNuGetRepo(string[] tags, bool includePrerelease, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindTagsFromNuGetRepo()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindTagsFromNuGetRepo()");
string tagsQueryTerm = $"tags:{String.Join(" ", tags)}";
// Get responses for all packages that contain the required tags
// example query:
- var tagPkgEntries = GetVersionedPackageEntriesFromSearchQueryResource(tagsQueryTerm, includePrerelease, out errRecord);
+ var tagPkgEntries = GetVersionedPackageEntriesFromSearchQueryResource(tagsQueryTerm, includePrerelease, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
@@ -584,10 +641,10 @@ private FindResults FindTagsFromNuGetRepo(string[] tags, bool includePrerelease,
///
/// Helper method called by FindName() and FindNameWithTag()
///
- private FindResults FindNameHelper(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
+ private FindResults FindNameHelper(string packageName, string[] tags, bool includePrerelease, ResourceType type, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- WriteDebug("In V3ServerAPICalls::FindNameHelper()", debugMsgs);
- string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs);
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindNameHelper()");
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
@@ -625,7 +682,7 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu
if (NuGetVersion.TryParse(pkgVersionElement.ToString(), out NuGetVersion pkgVersion))
{
- WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'", debugMsgs);
+ debugMsgs.Enqueue($"'{packageName}' version parsed as '{pkgVersion}'");
if (!pkgVersion.IsPrerelease || includePrerelease)
{
// Versions are always in descending order i.e 5.0.0, 3.0.0, 1.0.0 so grabbing the first match suffices
@@ -680,9 +737,9 @@ private FindResults FindNameHelper(string packageName, string[] tags, bool inclu
///
/// Helper method called by FindVersion() and FindVersionWithTag()
///
- private FindResults FindVersionHelper(string packageName, string version, string[] tags, ResourceType type, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
+ private FindResults FindVersionHelper(string packageName, string version, string[] tags, ResourceType type, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- WriteDebug("In V3ServerAPICalls::FindVersionHelper()", debugMsgs);
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindVersionHelper()");
if (!NuGetVersion.TryParse(version, out NuGetVersion requiredVersion))
{
errRecord = new ErrorRecord(
@@ -695,7 +752,7 @@ private FindResults FindVersionHelper(string packageName, string version, string
}
//_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'");
- string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, debugMsgs);
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
@@ -788,10 +845,10 @@ private FindResults FindVersionHelper(string packageName, string version, string
/// Name: no wildcard support.
/// Examples: Install "Newtonsoft.json"
///
- private Stream InstallName(string packageName, out ErrorRecord errRecord)
+ private Stream InstallName(string packageName, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::InstallName()");
- return InstallHelper(packageName, version: null, out errRecord);
+ debugMsgs.Enqueue("In V3ServerAPICalls::InstallName()");
+ return InstallHelper(packageName, version: null, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
}
///
@@ -801,10 +858,9 @@ private Stream InstallName(string packageName, out ErrorRecord errRecord)
/// Examples: Install "Newtonsoft.json" -Version "1.0.0.0"
/// Install "Newtonsoft.json" -Version "2.5.0-beta"
///
- private Stream InstallVersion(string packageName, string version, out ErrorRecord errRecord)
+ private Stream InstallVersion(string packageName, string version, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::InstallVersion()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::InstallVersion()");
if (!NuGetVersion.TryParse(version, out NuGetVersion requiredVersion))
{
errRecord = new ErrorRecord(
@@ -816,17 +872,16 @@ private Stream InstallVersion(string packageName, string version, out ErrorRecor
return null;
}
- return InstallHelper(packageName, requiredVersion, out errRecord);
+ return InstallHelper(packageName, requiredVersion, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
}
///
/// Helper method that is called by InstallName() and InstallVersion()
/// For InstallName() we want latest version installed (so version parameter passed in will be null), for InstallVersion() we want specified, non-null version installed.
///
- private Stream InstallHelper(string packageName, NuGetVersion version, out ErrorRecord errRecord)
+ private Stream InstallHelper(string packageName, NuGetVersion version, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::InstallHelper()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::InstallHelper()");
Stream pkgStream = null;
bool getLatestVersion = true;
if (version != null)
@@ -834,7 +889,7 @@ private Stream InstallHelper(string packageName, NuGetVersion version, out Error
getLatestVersion = false;
}
- string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, packageContentProperty, isSearch: false, out errRecord);
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, packageContentProperty, isSearch: false, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return pkgStream;
@@ -882,7 +937,7 @@ private Stream InstallHelper(string packageName, NuGetVersion version, out Error
return null;
}
- var content = HttpRequestCallForContent(pkgContentUrl, out errRecord);
+ var content = HttpRequestCallForContent(pkgContentUrl, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return null;
@@ -917,7 +972,7 @@ private async Task InstallHelperAsync(string packageName, NuGetVersion v
getLatestVersion = false;
}
- string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, packageContentProperty, isSearch: false, out ErrorRecord errRecord);
+ string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, packageContentProperty, isSearch: false, out ErrorRecord errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -989,24 +1044,23 @@ private async Task InstallHelperAsync(string packageName, NuGetVersion v
/// i.e when the package Name being searched for does not contain wildcard
/// This is called by FindNameHelper(), FindVersionHelper(), FindVersionGlobbing(), InstallHelper()
///
- private string[] GetVersionedPackageEntriesFromRegistrationsResource(string packageName, string propertyName, bool isSearch, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
+ private string[] GetVersionedPackageEntriesFromRegistrationsResource(string packageName, string propertyName, bool isSearch, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetVersionedPackageEntriesFromRegistrationsResource()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::GetVersionedPackageEntriesFromRegistrationsResource()");
string[] responses = Utils.EmptyStrArray;
- Dictionary resources = GetResourcesFromServiceIndex(out errRecord);
+ Dictionary resources = GetResourcesFromServiceIndex(out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return responses;
}
- string registrationsBaseUrl = FindRegistrationsBaseUrl(resources, out errRecord);
+ string registrationsBaseUrl = FindRegistrationsBaseUrl(resources, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return responses;
}
- responses = GetVersionedResponsesFromRegistrationsResource(registrationsBaseUrl, packageName, propertyName, isSearch, out errRecord, debugMsgs);
+ responses = GetVersionedResponsesFromRegistrationsResource(registrationsBaseUrl, packageName, propertyName, isSearch, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return Utils.EmptyStrArray;
@@ -1020,11 +1074,11 @@ private string[] GetVersionedPackageEntriesFromRegistrationsResource(string pack
/// i.e when the package Name being searched for contains wildcards or a Tag query search is performed
/// This is called by FindNameGlobbingFromNuGetRepo() and FindTagsFromNuGetRepo()
///
- private List GetVersionedPackageEntriesFromSearchQueryResource(string queryTerm, bool includePrerelease, out ErrorRecord errRecord)
+ private List GetVersionedPackageEntriesFromSearchQueryResource(string queryTerm, bool includePrerelease, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetVersionedPackageEntriesFromSearchQueryResource()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::GetVersionedPackageEntriesFromSearchQueryResource()");
List pkgEntries = new();
- Dictionary resources = GetResourcesFromServiceIndex(out errRecord);
+ Dictionary resources = GetResourcesFromServiceIndex(out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return pkgEntries;
@@ -1041,7 +1095,7 @@ private List GetVersionedPackageEntriesFromSearchQueryResource(stri
string query = $"{searchQueryServiceUrl}?q={queryTerm}&prerelease={includePrerelease}&semVerLevel=2.0.0&skip={skip}&take=100";
// Get responses for all packages that contain the required tags
- pkgEntries.AddRange(GetJsonElementArr(query, dataName, out int initialCount, out errRecord).ToList());
+ pkgEntries.AddRange(GetJsonElementArr(query, dataName, out int initialCount, out errRecord, errorMsgs, debugMsgs, verboseMsgs).ToList());
// check count (ie "totalHits") 425 ==> count/100 ~~> 4 calls ~~> + 1 = 5 calls
int count = initialCount / 100 + 1;
@@ -1050,7 +1104,7 @@ private List GetVersionedPackageEntriesFromSearchQueryResource(stri
{
skip += 100;
query = $"{searchQueryServiceUrl}?q={queryTerm}&prerelease={includePrerelease}&semVerLevel=2.0.0&skip={skip}&take=100";
- pkgEntries.AddRange(GetJsonElementArr(query, dataName, out int unneededCount, out errRecord).ToList());
+ pkgEntries.AddRange(GetJsonElementArr(query, dataName, out int unneededCount, out errRecord, errorMsgs, debugMsgs, verboseMsgs).ToList());
count--;
}
@@ -1061,12 +1115,11 @@ private List GetVersionedPackageEntriesFromSearchQueryResource(stri
/// Finds all resources present in the repository's service index.
/// For example: https://api.nuget.org/v3/index.json
///
- private Dictionary GetResourcesFromServiceIndex(out ErrorRecord errRecord)
+ private Dictionary GetResourcesFromServiceIndex(out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetResourcesFromServiceIndex()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::GetResourcesFromServiceIndex()");
Dictionary resources = new Dictionary();
- JsonElement[] resourcesArray = GetJsonElementArr($"{Repository.Uri}", resourcesName, out int totalHits, out errRecord);
+ JsonElement[] resourcesArray = GetJsonElementArr($"{Repository.Uri}", resourcesName, out int totalHits, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return resources;
@@ -1122,10 +1175,9 @@ private Dictionary GetResourcesFromServiceIndex(out ErrorRecord
/// Gets the resource of type "RegistrationBaseUrl" from the repository's resources.
/// A repository can have multiple resources of type "RegistrationsBaseUrl" so it finds the best match according to the guideline comment in the method.
///
- private string FindRegistrationsBaseUrl(Dictionary resources, out ErrorRecord errRecord)
+ private string FindRegistrationsBaseUrl(Dictionary resources, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::FindRegistrationsBaseUrl()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::FindRegistrationsBaseUrl()");
errRecord = null;
string registrationsBaseUrl = String.Empty;
@@ -1212,16 +1264,15 @@ private string FindSearchQueryService(Dictionary resources, out
/// For some packages (that we know of: JFrog repo and some packages on NuGet.org), the metadata is located under outer "items" element > "@id" element > inner "items" element
/// This requires a different search.
///
- private JsonElement[] GetMetadataElementFromIdLinkElement(JsonElement idLinkElement, string packageName, out string upperVersion, out ErrorRecord errRecord)
+ private JsonElement[] GetMetadataElementFromIdLinkElement(JsonElement idLinkElement, string packageName, out string upperVersion, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ?
- _cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetMetadataElementFromIdLinkElement()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::GetMetadataElementFromIdLinkElement()");
upperVersion = String.Empty;
JsonElement[] innerItems = new JsonElement[]{};
List innerItemsList = new List();
string metadataUri = idLinkElement.ToString();
- string response = HttpRequestCall(metadataUri, out errRecord);
+ string response = HttpRequestCall(metadataUri, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
if (errRecord.Exception is ResourceNotFoundException) {
@@ -1257,8 +1308,7 @@ private JsonElement[] GetMetadataElementFromIdLinkElement(JsonElement idLinkElem
}
else
{
- // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ?
- _cmdletPassedIn.WriteDebug($"Package with name '{packageName}' did not have 'upper' property so package versions may not be in descending order.");
+ debugMsgs.Enqueue($"Package with name '{packageName}' did not have 'upper' property so package versions may not be in descending order.");
}
foreach(JsonElement entry in innerItemsElement.EnumerateArray())
@@ -1283,10 +1333,9 @@ private JsonElement[] GetMetadataElementFromIdLinkElement(JsonElement idLinkElem
///
/// For most packages returned from V3 server protocol responses, the metadata is located under outer "items" element > inner "items" element.
///
- private JsonElement[] GetMetadataElementFromItemsElement(JsonElement itemsElement, string packageName, out ErrorRecord errRecord)
+ private JsonElement[] GetMetadataElementFromItemsElement(JsonElement itemsElement, string packageName, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetMetadataElementFromItemsElement()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::GetMetadataElementFromItemsElement()");
errRecord = null;
List innerItemsList = new List();
@@ -1316,10 +1365,9 @@ private JsonElement[] GetMetadataElementFromItemsElement(JsonElement itemsElemen
/// under outer "items" element > inner "items" element (for which we call helper method GetMetadataElementFromItemsElement()), OR
/// under outer "items" element > "@Id" element > inner "items" element (for which we call helper method GetMetadataElementFromIdLinkElement)
///
- private string[] GetMetadataElementsFromResponse(string response, string property, string packageName, out string upperVersion, out ErrorRecord errRecord)
+ private string[] GetMetadataElementsFromResponse(string response, string property, string packageName, out string upperVersion, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetMetadataElementsFromResponse()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::GetMetadataElementsFromResponse()");
errRecord = null;
upperVersion = String.Empty;
List versionedPkgResponses = new List();
@@ -1353,7 +1401,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
if (currentItem.TryGetProperty(itemsName, out JsonElement currentInnerItemsElement))
{
// Scenarios: NuGet.org majority responses
- JsonElement[] innerItemsFromItemsElement = GetMetadataElementFromItemsElement(currentInnerItemsElement, packageName, out errRecord);
+ JsonElement[] innerItemsFromItemsElement = GetMetadataElementFromItemsElement(currentInnerItemsElement, packageName, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
continue;
@@ -1365,8 +1413,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
}
else
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- // _cmdletPassedIn.WriteDebug($"Package with name '{packageName}' did not have 'upper' property so package versions may not be in descending order.");
+ debugMsgs.Enqueue($"Package with name '{packageName}' did not have 'upper' property so package versions may not be in descending order.");
}
innerItemsElements.AddRange(innerItemsFromItemsElement);
@@ -1374,7 +1421,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
else if (currentItem.TryGetProperty(idLinkName, out JsonElement idLinkElement))
{
// Scenarios: JFrog responses, some NuGet.org responses
- JsonElement[] innerItemsFromIdElement = GetMetadataElementFromIdLinkElement(idLinkElement, packageName, out upperVersion, out errRecord);
+ JsonElement[] innerItemsFromIdElement = GetMetadataElementFromIdLinkElement(idLinkElement, packageName, out upperVersion, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
continue;
@@ -1384,8 +1431,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
}
else
{
- // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ?
- _cmdletPassedIn.WriteDebug($"Metadata for package with name '{packageName}' did not have inner 'items' or '@Id' properties.");
+ debugMsgs.Enqueue($"Metadata for package with name '{packageName}' did not have inner 'items' or '@Id' properties.");
}
}
@@ -1424,8 +1470,7 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
}
else
{
- // TODO: pass in ConcurrentQueue to write out debug message. Called from the concurrent install metadata chain so cmdlet methods cannot be used here. ?
- _cmdletPassedIn.WriteDebug($"Metadata for package with name '{packageName}' was not of value kind type string or object.");
+ debugMsgs.Enqueue($"Metadata for package with name '{packageName}' was not of value kind type string or object.");
}
}
}
@@ -1452,14 +1497,13 @@ private string[] GetMetadataElementsFromResponse(string response, string propert
/// The "packageContent" property is used for download, and the value is a URI for the .nupkg file.
///
///
- private string[] GetVersionedResponsesFromRegistrationsResource(string registrationsBaseUrl, string packageName, string property, bool isSearch, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
+ private string[] GetVersionedResponsesFromRegistrationsResource(string registrationsBaseUrl, string packageName, string property, bool isSearch, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::GetVersionedResponsesFromRegistrationsResource()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::GetVersionedResponsesFromRegistrationsResource()");
List versionedResponses = new List();
var requestPkgMapping = registrationsBaseUrl.EndsWith("/") ? $"{registrationsBaseUrl}{packageName.ToLower()}/index.json" : $"{registrationsBaseUrl}/{packageName.ToLower()}/index.json";
- string pkgMappingResponse = HttpRequestCall(requestPkgMapping, out errRecord);
+ string pkgMappingResponse = HttpRequestCall(requestPkgMapping, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
if (errRecord.Exception is ResourceNotFoundException)
@@ -1475,7 +1519,7 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat
}
string upperVersion = String.Empty;
- string[] versionedResponseArr = GetMetadataElementsFromResponse(pkgMappingResponse, property, packageName, out upperVersion, out errRecord);
+ string[] versionedResponseArr = GetMetadataElementsFromResponse(pkgMappingResponse, property, packageName, out upperVersion, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return Utils.EmptyStrArray;
@@ -1490,14 +1534,14 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat
if (isSearch)
{
- if (!IsLatestVersionFirstForSearch(versionedResponseArr, out errRecord, debugMsgs))
+ if (!IsLatestVersionFirstForSearch(versionedResponseArr, out errRecord, errorMsgs, debugMsgs, verboseMsgs))
{
Array.Reverse(versionedResponseArr);
}
}
else
{
- if (!IsLatestVersionFirstForInstall(versionedResponseArr, upperVersion, out errRecord))
+ if (!IsLatestVersionFirstForInstall(versionedResponseArr, upperVersion, out errRecord, errorMsgs, debugMsgs, verboseMsgs))
{
Array.Reverse(versionedResponseArr);
}
@@ -1511,9 +1555,9 @@ private string[] GetVersionedResponsesFromRegistrationsResource(string registrat
/// ADO feeds usually return version entries in descending order, but Nuget.org repository returns them in ascending order.
/// Package versions will reflect prerelease preference, but upper version and lower version would not so we don't use them for comparison.
///
- private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out ErrorRecord errRecord, ConcurrentQueue debugMsgs = null)
+ private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- WriteDebug("In V3ServerAPICalls::IsLatestVersionFirstForSearch()", debugMsgs);
+ debugMsgs.Enqueue("In V3ServerAPICalls::IsLatestVersionFirstForSearch()");
errRecord = null;
bool latestVersionFirst = true;
int versionResponsesCount = versionedResponses.Length;
@@ -1605,27 +1649,14 @@ private bool IsLatestVersionFirstForSearch(string[] versionedResponses, out Erro
return latestVersionFirst;
}
- private void WriteDebug(string message, ConcurrentQueue debugMsgs = null)
- {
- if (debugMsgs == null)
- {
- _cmdletPassedIn.WriteDebug(message);
- }
- else
- {
- debugMsgs.Enqueue(message);
- }
- }
-
///
/// Returns true if the nupkg URI entries for each package version are arranged in descending order with respect to the package's version.
/// ADO feeds usually return version entries in descending order, but Nuget.org repository returns them in ascending order.
/// Entries do not reflect prerelease preference so all versions (including prerelease) are being considered here, so upper version (including prerelease) can be used for comparison.
///
- private bool IsLatestVersionFirstForInstall(string[] versionedResponses, string upperVersion, out ErrorRecord errRecord)
+ private bool IsLatestVersionFirstForInstall(string[] versionedResponses, string upperVersion, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::IsLatestVersionFirstForInstall()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::IsLatestVersionFirstForInstall()");
errRecord = null;
bool latestVersionFirst = true;
@@ -1701,14 +1732,14 @@ private bool IsRequiredTagSatisfied(JsonElement tagsElement, string[] tags, out
///
/// Helper method that parses response for given property and returns result for that property as a JsonElement array.
///
- private JsonElement[] GetJsonElementArr(string request, string propertyName, out int totalHits, out ErrorRecord errRecord)
+ private JsonElement[] GetJsonElementArr(string request, string propertyName, out int totalHits, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
List responseEntries = new List();
JsonElement[] entries = new JsonElement[0];
totalHits = 0;
try
{
- string response = HttpRequestCall(request, out errRecord);
+ string response = HttpRequestCall(request, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return new JsonElement[]{};
@@ -1759,17 +1790,15 @@ private JsonElement[] GetJsonElementArr(string request, string propertyName, out
///
/// Helper method that makes the HTTP request for the V3 server protocol url passed in for find APIs.
///
- private string HttpRequestCall(string requestUrlV3, out ErrorRecord errRecord)
+ private string HttpRequestCall(string requestUrlV3, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::HttpRequestCall()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::HttpRequestCall()");
errRecord = null;
string response = string.Empty;
try
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug($"Request url is '{requestUrlV3}'");
+ debugMsgs.Enqueue($"Request url is '{requestUrlV3}'");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrlV3);
response = SendV3RequestAsync(request, _sessionClient).GetAwaiter().GetResult();
@@ -1813,10 +1842,9 @@ private string HttpRequestCall(string requestUrlV3, out ErrorRecord errRecord)
///
/// Helper method that makes the HTTP request for the V3 server protocol url passed in for install APIs.
///
- private HttpContent HttpRequestCallForContent(string requestUrlV3, out ErrorRecord errRecord)
+ private HttpContent HttpRequestCallForContent(string requestUrlV3, out ErrorRecord errRecord, ConcurrentQueue errorMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("In V3ServerAPICalls::HttpRequestCallForContent()");
+ debugMsgs.Enqueue("In V3ServerAPICalls::HttpRequestCallForContent()");
errRecord = null;
HttpContent content = null;
try
@@ -1838,8 +1866,7 @@ private HttpContent HttpRequestCallForContent(string requestUrlV3, out ErrorReco
if (string.IsNullOrEmpty(content?.ToString()))
{
- // TODO: pass in ConcurrentQueue to write out debug message.
- //_cmdletPassedIn.WriteDebug("Response is empty");
+ debugMsgs.Enqueue("Response is empty");
}
return content;
From 40138f1fff01562e9cf78d12702dc59412d832ef Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Fri, 24 Jul 2026 12:22:57 -0700
Subject: [PATCH 13/24] Add concurrency for parent packages
---
src/code/FindHelper.cs | 61 +++++--
src/code/InstallHelper.cs | 349 ++++++++++++++++++++------------------
2 files changed, 223 insertions(+), 187 deletions(-)
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index 06a0e9df0..e4c9dd8d5 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -46,10 +46,6 @@ internal class FindHelper
// If running 'Install-PSResource Az, TestModule, NewTestModule', it will contain one parent and its dependencies.
private ConcurrentDictionary> _packagesFound;
- // Creates a new instance of depPkgsFound each time FindDependencyPackages() is called.
- // This will eventually return the PSResourceInfo object to the main cmdlet class.
- private ConcurrentDictionary depPkgsFound;
-
// Contains the latest found version of a particular package.
private ConcurrentDictionary _knownLatestPkgVersion;
@@ -1060,13 +1056,36 @@ private IEnumerable SearchByNames(ServerApiCall currentServer, R
// After retrieving all packages find their dependencies
if (_includeDependencies)
{
- foreach (PSResourceInfo currentPkg in parentPkgs)
+ // Resolving each parent package's dependency closure is independent work, so do it concurrently.
+ // yield return cannot be used inside Parallel.ForEach, so collect results into a thread-safe bag first.
+ ConcurrentBag dependencyPkgs = new ConcurrentBag();
+ int processorCount = Environment.ProcessorCount;
+ int maxDegreeOfParallelism = processorCount * 4;
+ if (parentPkgs.Count > processorCount)
+ {
+ Parallel.ForEach(parentPkgs, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, currentPkg =>
+ {
+ foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository))
+ {
+ dependencyPkgs.Add(pkgDep);
+ }
+ });
+ }
+ else
{
- foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository))
+ foreach (PSResourceInfo currentPkg in parentPkgs)
{
- yield return pkgDep;
+ foreach (PSResourceInfo pkgDep in FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository))
+ {
+ dependencyPkgs.Add(pkgDep);
+ }
}
}
+
+ foreach (PSResourceInfo pkgDep in dependencyPkgs)
+ {
+ yield return pkgDep;
+ }
}
}
@@ -1158,15 +1177,17 @@ private string FormatPkgVersionString(PSResourceInfo pkg)
internal IEnumerable FindDependencyPackages(ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository)
{
- depPkgsFound = new ConcurrentDictionary();
+ // Use a local instance so multiple parent packages can resolve their dependency closures concurrently
+ // without racing on shared state.
+ ConcurrentDictionary depPkgsFound = new ConcurrentDictionary();
_cmdletPassedIn.WriteDebug($"In FindHelper::FindDependencyPackages() - {currentPkg.Name}");
- FindDependencyPackagesHelper(currentServer, currentResponseUtil, currentPkg, repository);
+ FindDependencyPackagesHelper(currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound);
return depPkgsFound.Values.ToList();
}
// Method 2
- internal void FindDependencyPackagesHelper(ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository)
+ internal void FindDependencyPackagesHelper(ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository, ConcurrentDictionary depPkgsFound)
{
ConcurrentQueue errorMsgs = new ConcurrentQueue();
ConcurrentQueue verboseMsgs = new ConcurrentQueue();
@@ -1185,7 +1206,7 @@ internal void FindDependencyPackagesHelper(ServerApiCall currentServer, Response
Parallel.ForEach(currentPkg.Dependencies, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, dep =>
{
debugMsgs.Enqueue($"Finding dependency '{dep.Name}' version range '{dep.VersionRange}'");
- FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
});
}
else
@@ -1193,7 +1214,7 @@ internal void FindDependencyPackagesHelper(ServerApiCall currentServer, Response
foreach (var dep in currentPkg.Dependencies)
{
debugMsgs.Enqueue($"Finding dependency '{dep.Name}' version range '{dep.VersionRange}'");
- FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
@@ -1208,6 +1229,7 @@ private void FindDependencyPackageVersion(
ResponseUtil currentResponseUtil,
PSResourceInfo currentPkg,
PSRepositoryInfo repository,
+ ConcurrentDictionary depPkgsFound,
ConcurrentQueue errorMsgs,
ConcurrentQueue warningMsgs,
ConcurrentQueue debugMsgs,
@@ -1228,7 +1250,7 @@ private void FindDependencyPackageVersion(
else
{
// Find this version from the server
- depPkg = FindDependencyWithLowerBound(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ depPkg = FindDependencyWithLowerBound(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
else if (dep.VersionRange.HasLowerBound && dep.VersionRange.MinVersion.Equals(dep.VersionRange.MaxVersion))
@@ -1245,7 +1267,7 @@ private void FindDependencyPackageVersion(
}
else
{
- depPkg = FindDependencyWithSpecificVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ depPkg = FindDependencyWithSpecificVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
else
@@ -1261,7 +1283,7 @@ private void FindDependencyPackageVersion(
}
else
{
- depPkg = FindDependencyWithUpperBound(dep, currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ depPkg = FindDependencyWithUpperBound(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
}
@@ -1273,6 +1295,7 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
ResponseUtil currentResponseUtil,
PSResourceInfo currentPkg,
PSRepositoryInfo repository,
+ ConcurrentDictionary depPkgsFound,
ConcurrentQueue errorMsgs,
ConcurrentQueue warningMsgs,
ConcurrentQueue debugMsgs,
@@ -1351,7 +1374,7 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
// This will eventually return the PSResourceInfo object to the main cmdlet class.
debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found");
depPkgsFound.TryAdd(key, depPkg);
- FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository);
+ FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound);
}
}
}
@@ -1366,6 +1389,7 @@ private PSResourceInfo FindDependencyWithLowerBound(
ResponseUtil currentResponseUtil,
PSResourceInfo currentPkg,
PSRepositoryInfo repository,
+ ConcurrentDictionary depPkgsFound,
ConcurrentQueue errorMsgs,
ConcurrentQueue warningMsgs,
ConcurrentQueue debugMsgs,
@@ -1419,7 +1443,7 @@ private PSResourceInfo FindDependencyWithLowerBound(
// This will eventually return the PSResourceInfo object to the main cmdlet class.
debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found");
depPkgsFound.TryAdd(key, depPkg);
- FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository);
+ FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound);
}
}
}
@@ -1434,6 +1458,7 @@ private PSResourceInfo FindDependencyWithUpperBound(
ResponseUtil currentResponseUtil,
PSResourceInfo currentPkg,
PSRepositoryInfo repository,
+ ConcurrentDictionary depPkgsFound,
ConcurrentQueue errorMsgs,
ConcurrentQueue warningMsgs,
ConcurrentQueue debugMsgs,
@@ -1490,7 +1515,7 @@ private PSResourceInfo FindDependencyWithUpperBound(
// This will eventually return the PSResourceInfo object to the main cmdlet class.
debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found");
depPkgsFound.TryAdd(key, depPkg);
- FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository);
+ FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound);
}
}
}
diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs
index e3f95b616..fe01209be 100644
--- a/src/code/InstallHelper.cs
+++ b/src/code/InstallHelper.cs
@@ -516,73 +516,123 @@ private List InstallPackages(
FindHelper findHelper)
{
_cmdletPassedIn.WriteDebug("In InstallHelper::InstallPackages()");
-
+
List pkgsSuccessfullyInstalled = new();
- // Install parent package to the temp directory,
- // Get the dependencies from the installed package,
- // Install all dependencies to temp directory.
- // If a single dependency fails to install, roll back by deleting the temp directory.
+ // ---------- Phase 1 (pipeline thread): resolve each parent package and evaluate ShouldProcess ----------
+ // ShouldProcess / -WhatIf / -Confirm and Write* must run on the pipeline thread, so all gating happens
+ // here, before any parallel download work begins. Packages that pass the gate become work items.
+ List workItems = new();
foreach (var parentPackage in pkgNamesToInstall)
{
- string tempInstallPath = CreateInstallationTempPath();
-
- try
+ PSResourceInfo pkgToInstall = FindParentPackage(
+ searchVersionType: _versionType,
+ specificVersion: _nugetVersion,
+ versionRange: _versionRange,
+ pkgNameToInstall: parentPackage,
+ repository: repository,
+ currentServer: currentServer,
+ currentResponseUtil: currentResponseUtil,
+ pkgVersion: out string pkgVersion,
+ errRecord: out ErrorRecord findErrRecord);
+
+ if (findErrRecord != null)
{
- // Hashtable has the key as the package name
- // and value as a Hashtable of specific package info:
- // packageName, { version = "", isScript = "", isModule = "", pkg = "", etc. }
- // Install parent package to the temp directory.
- ConcurrentDictionary packagesHash = BeginPackageInstall(
- searchVersionType: _versionType,
- specificVersion: _nugetVersion,
- versionRange: _versionRange,
- pkgNameToInstall: parentPackage,
- repository: repository,
- currentServer: currentServer,
- currentResponseUtil: currentResponseUtil,
- tempInstallPath: tempInstallPath,
- skipDependencyCheck: skipDependencyCheck,
- packagesHash: new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase),
- warning: out string warning,
- errRecord: out ErrorRecord errRecord);
-
- // At this point all packages are installed to temp path.
- if (errRecord != null)
+ if (findErrRecord.FullyQualifiedErrorId.Equals("PackageNotFound"))
{
- if (errRecord.FullyQualifiedErrorId.Equals("PackageNotFound"))
- {
- _cmdletPassedIn.WriteVerbose(errRecord.Exception.Message);
- }
- else
- {
- _cmdletPassedIn.WriteError(errRecord);
- }
-
- continue;
+ _cmdletPassedIn.WriteVerbose(findErrRecord.Exception.Message);
}
- if (warning != null)
+ else
{
- _cmdletPassedIn.WriteWarning(warning);
+ _cmdletPassedIn.WriteError(findErrRecord);
}
- if (packagesHash.Count == 0)
- {
- continue;
- }
+ continue;
+ }
+
+ if (pkgToInstall == null)
+ {
+ continue;
+ }
+
+ // Check to see if the pkg is already installed (unless -Reinstall was specified).
+ if (!_reinstall && _packagesOnMachine.Contains($"{pkgToInstall.Name}{pkgVersion}"))
+ {
+ _cmdletPassedIn.WriteWarning($"Resource '{pkgToInstall.Name}' with version '{pkgVersion}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter");
+
+ // Remove from tracking list of packages to install.
+ _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgToInstall.Name, StringComparison.InvariantCultureIgnoreCase));
+
+ continue;
+ }
+
+ // ShouldProcess gate (handles -WhatIf / -Confirm) on the pipeline thread.
+ string shouldProcessTarget = _savePkg
+ ? $"Package to save: '{pkgToInstall.Name}', version: '{pkgVersion}'"
+ : $"Package to install: '{pkgToInstall.Name}', version: '{pkgVersion}'";
+ if (!_cmdletPassedIn.ShouldProcess(shouldProcessTarget))
+ {
+ continue;
+ }
+
+ workItems.Add(new ParentInstallWorkItem
+ {
+ PkgToInstall = pkgToInstall,
+ PkgVersion = pkgVersion,
+ TempInstallPath = CreateInstallationTempPath()
+ });
+ }
- Hashtable parentPkgInfo = packagesHash[parentPackage] as Hashtable;
- PSResourceInfo parentPkgObj = parentPkgInfo["psResourceInfoPkg"] as PSResourceInfo;
+ if (workItems.Count == 0)
+ {
+ return pkgsSuccessfullyInstalled;
+ }
+
+ // ---------- Phase 2 (parallel): download each parent + its dependencies to its own temp path ----------
+ // This is network-bound work. No pipeline-thread calls are made here; all host messages are queued
+ // per work item and drained in Phase 3. Each work item downloads into its own temp path and its own
+ // packagesHash, so there is no shared mutable state between parents.
+ int processorCount = Environment.ProcessorCount;
+ if (workItems.Count > 1)
+ {
+ int maxDegreeOfParallelism = processorCount * 4;
+ Parallel.ForEach(workItems, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, workItem =>
+ {
+ workItem.PackagesHash = DownloadParentAndDeps(
+ workItem.PkgToInstall, workItem.PkgVersion, workItem.TempInstallPath, repository,
+ currentServer, currentResponseUtil, skipDependencyCheck,
+ workItem.ErrorMsgs, workItem.WarningMsgs, workItem.DebugMsgs, workItem.VerboseMsgs,
+ out bool succeeded);
+ workItem.Succeeded = succeeded;
+ });
+ }
+ else
+ {
+ ParentInstallWorkItem workItem = workItems[0];
+ workItem.PackagesHash = DownloadParentAndDeps(
+ workItem.PkgToInstall, workItem.PkgVersion, workItem.TempInstallPath, repository,
+ currentServer, currentResponseUtil, skipDependencyCheck,
+ workItem.ErrorMsgs, workItem.WarningMsgs, workItem.DebugMsgs, workItem.VerboseMsgs,
+ out bool succeeded);
+ workItem.Succeeded = succeeded;
+ }
- // If -WhatIf is passed in, early out.
- if (_cmdletPassedIn.MyInvocation.BoundParameters.ContainsKey("WhatIf") && (SwitchParameter)_cmdletPassedIn.MyInvocation.BoundParameters["WhatIf"] == true)
+ // ---------- Phase 3 (pipeline thread): drain messages, move content to final location, record results ----------
+ // If a single dependency fails to install, roll back that parent by deleting its temp directory.
+ foreach (ParentInstallWorkItem workItem in workItems)
+ {
+ try
+ {
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, workItem.ErrorMsgs, workItem.WarningMsgs, workItem.DebugMsgs, workItem.VerboseMsgs);
+
+ if (!workItem.Succeeded || workItem.PackagesHash == null || workItem.PackagesHash.Count == 0)
{
- return pkgsSuccessfullyInstalled;
+ continue;
}
// Parent package and dependencies are now installed to temp directory.
// Try to move all package directories from temp directory to final destination.
- if (!TryMoveInstallContent(tempInstallPath, scope, packagesHash))
+ if (!TryMoveInstallContent(workItem.TempInstallPath, scope, workItem.PackagesHash))
{
_cmdletPassedIn.WriteError(new ErrorRecord(
new InvalidOperationException(),
@@ -592,9 +642,9 @@ private List InstallPackages(
}
else
{
- foreach (string pkgName in packagesHash.Keys)
+ foreach (string pkgName in workItem.PackagesHash.Keys)
{
- Hashtable pkgInfo = packagesHash[pkgName] as Hashtable;
+ Hashtable pkgInfo = workItem.PackagesHash[pkgName] as Hashtable;
pkgsSuccessfullyInstalled.Add(pkgInfo["psResourceInfoPkg"] as PSResourceInfo);
// Add each pkg to _packagesOnMachine (ie pkgs fully installed on the machine).
@@ -610,11 +660,11 @@ private List InstallPackages(
ErrorCategory.InvalidOperation,
_cmdletPassedIn));
- throw e;
+ throw;
}
finally
{
- DeleteInstallationTempPath(tempInstallPath);
+ DeleteInstallationTempPath(workItem.TempInstallPath);
}
}
@@ -622,9 +672,27 @@ private List InstallPackages(
}
///
- /// Installs a single package to the temporary path.
+ /// Tracks the per-parent state used to parallelize parent-package installation across the three phases.
+ /// Each parent has its own temp path, result hash, and message queues so there is no shared mutable state
+ /// during the parallel download phase.
+ ///
+ private sealed class ParentInstallWorkItem
+ {
+ public PSResourceInfo PkgToInstall;
+ public string PkgVersion;
+ public string TempInstallPath;
+ public ConcurrentDictionary PackagesHash;
+ public bool Succeeded;
+ public readonly ConcurrentQueue ErrorMsgs = new();
+ public readonly ConcurrentQueue WarningMsgs = new();
+ public readonly ConcurrentQueue DebugMsgs = new();
+ public readonly ConcurrentQueue VerboseMsgs = new();
+ }
+
+ ///
+ /// Resolves the parent package to install (find + version selection). Must run on the pipeline thread.
///
- private ConcurrentDictionary BeginPackageInstall(
+ private PSResourceInfo FindParentPackage(
VersionType searchVersionType,
NuGetVersion specificVersion,
VersionRange versionRange,
@@ -632,15 +700,12 @@ private ConcurrentDictionary BeginPackageInstall(
PSRepositoryInfo repository,
ServerApiCall currentServer,
ResponseUtil currentResponseUtil,
- string tempInstallPath,
- bool skipDependencyCheck,
- ConcurrentDictionary packagesHash,
- out string warning,
+ out string pkgVersion,
out ErrorRecord errRecord)
{
- _cmdletPassedIn.WriteDebug("In InstallHelper::BeginPackageInstall()");
+ _cmdletPassedIn.WriteDebug("In InstallHelper::FindParentPackage()");
FindResults responses = null;
- warning = null;
+ pkgVersion = null;
errRecord = null;
// Find the parent package that needs to be installed
@@ -652,7 +717,7 @@ private ConcurrentDictionary BeginPackageInstall(
if (findVersionGlobbingErrRecord != null || responses.IsFindResultsEmpty())
{
errRecord = findVersionGlobbingErrRecord;
- return packagesHash;
+ return null;
}
break;
@@ -664,7 +729,7 @@ private ConcurrentDictionary BeginPackageInstall(
if (findVersionErrRecord != null)
{
errRecord = findVersionErrRecord;
- return packagesHash;
+ return null;
}
break;
@@ -675,7 +740,7 @@ private ConcurrentDictionary BeginPackageInstall(
if (findNameErrRecord != null)
{
errRecord = findNameErrRecord;
- return packagesHash;
+ return null;
}
break;
@@ -721,11 +786,11 @@ private ConcurrentDictionary BeginPackageInstall(
if (pkgToInstall == null)
{
- return packagesHash;
+ return null;
}
pkgToInstall.RepositorySourceLocation = repository.Uri.ToString();
- pkgToInstall.AdditionalMetadata.TryGetValue("NormalizedVersion", out string pkgVersion);
+ pkgToInstall.AdditionalMetadata.TryGetValue("NormalizedVersion", out pkgVersion);
if (pkgVersion == null)
{
// Not all NuGet providers (e.g. Artifactory, possibly others) send NormalizedVersion in NuGet package responses.
@@ -745,117 +810,67 @@ private ConcurrentDictionary BeginPackageInstall(
}
// Check to see if the pkg is already installed (ie the pkg is installed and the version satisfies the version range provided via param)
- // TODO: can use cache for this
- if (!_reinstall)
- {
- string currPkgNameVersion = $"{pkgToInstall.Name}{pkgVersion}";
- // Use HashSet lookup instead of Contains for O(1) performance
- if (_packagesOnMachine.Contains(currPkgNameVersion))
- {
- _cmdletPassedIn.WriteWarning($"Resource '{pkgToInstall.Name}' with version '{pkgVersion}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter");
-
- // Remove from tracking list of packages to install.
- _pkgNamesToInstall.RemoveAll(x => x.Equals(pkgToInstall.Name, StringComparison.InvariantCultureIgnoreCase));
-
- return packagesHash;
- }
- }
-
- if (packagesHash.ContainsKey(pkgToInstall.Name))
- {
- return packagesHash;
- }
+ // Note: the already-installed check and ShouldProcess gate are handled by the caller on the pipeline thread.
+ return pkgToInstall;
+ }
+ ///
+ /// Downloads the parent package and its dependencies to the temporary path. Safe to run on worker threads:
+ /// all host messages are routed to the provided queues and drained by the caller on the pipeline thread.
+ ///
+ private ConcurrentDictionary DownloadParentAndDeps(
+ PSResourceInfo pkgToInstall,
+ string pkgVersion,
+ string tempInstallPath,
+ PSRepositoryInfo repository,
+ ServerApiCall currentServer,
+ ResponseUtil currentResponseUtil,
+ bool skipDependencyCheck,
+ ConcurrentQueue errorMsgs,
+ ConcurrentQueue warningMsgs,
+ ConcurrentQueue debugMsgs,
+ ConcurrentQueue verboseMsgs,
+ out bool success)
+ {
+ debugMsgs.Enqueue("In InstallHelper::DownloadParentAndDeps()");
+ ConcurrentDictionary packagesHash = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase);
- ConcurrentDictionary updatedPackagesHash = packagesHash;
-
- // -WhatIf processing.
- if (_savePkg && !_cmdletPassedIn.ShouldProcess($"Package to save: '{pkgToInstall.Name}', version: '{pkgVersion}'"))
- {
- updatedPackagesHash.TryAdd(pkgToInstall.Name, new Hashtable(StringComparer.InvariantCultureIgnoreCase)
- {
- { "isModule", "" },
- { "isScript", "" },
- { "psResourceInfoPkg", pkgToInstall },
- { "tempDirNameVersionPath", tempInstallPath },
- { "pkgVersion", "" },
- { "scriptPath", "" },
- { "installPath", "" }
- });
- }
- else if (!_cmdletPassedIn.ShouldProcess($"Package to install: '{pkgToInstall.Name}', version: '{pkgVersion}'"))
+ List parentAndDeps = new List();
+ if (!skipDependencyCheck)
{
- if (!updatedPackagesHash.ContainsKey(pkgToInstall.Name))
- {
- updatedPackagesHash.TryAdd(pkgToInstall.Name, new Hashtable(StringComparer.InvariantCultureIgnoreCase)
- {
- { "isModule", "" },
- { "isScript", "" },
- { "psResourceInfoPkg", pkgToInstall },
- { "tempDirNameVersionPath", tempInstallPath },
- { "pkgVersion", "" },
- { "scriptPath", "" },
- { "installPath", "" }
- });
- }
+ // List returned only includes dependencies, so we'll add the parent pkg to this list to pass on to installation method.
+ parentAndDeps.AddRange(_findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository));
+ debugMsgs.Enqueue("In InstallHelper::DownloadParentAndDeps(), found all dependencies");
}
- else
- {
- // Concurrent updates
- // Find all dependencies
- if (!skipDependencyCheck)
- {
- // concurrency updates
- List parentAndDeps = _findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository).ToList();
- // List returned only includes dependencies, so we'll add the parent pkg to this list to pass on to installation method
- parentAndDeps.Add(pkgToInstall);
- _cmdletPassedIn.WriteDebug("In InstallHelper::BeginPackageInstall(), found all dependencies");
+ parentAndDeps.Add(pkgToInstall);
- return InstallParentAndDependencyPackages(parentAndDeps, currentServer, tempInstallPath, packagesHash, updatedPackagesHash, pkgToInstall);
- }
- else {
- // If we don't install dependencies, we're only installing the parent pkg so we can short circut and simply install the parent pkg.
- Stream responseStream = currentServer.InstallPackage(pkgToInstall.Name, pkgVersion, true, out ErrorRecord installNameErrRecord);
-
- if (installNameErrRecord != null)
- {
- errRecord = installNameErrRecord;
- return packagesHash;
- }
- ConcurrentQueue errorMsgs = new ConcurrentQueue();
- ConcurrentQueue warningMsgs = new ConcurrentQueue();
- ConcurrentQueue debugMsgs = new ConcurrentQueue();
- ConcurrentQueue verboseMsgs = new ConcurrentQueue();
-
- bool installedToTempPathSuccessfully = _asNupkg ? TrySaveNupkgToTempPath(responseStream, tempInstallPath, pkgToInstall.Name, pkgVersion, pkgToInstall, packagesHash, out updatedPackagesHash, errorMsgs, warningMsgs, debugMsgs, verboseMsgs) :
- TryInstallToTempPath(responseStream, tempInstallPath, pkgToInstall.Name, pkgVersion, pkgToInstall, packagesHash, out updatedPackagesHash, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
-
- Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
- if (!installedToTempPathSuccessfully)
- {
- return packagesHash;
- }
- }
- }
+ ConcurrentDictionary updatedPackagesHash = InstallParentAndDependencyPackages(
+ parentAndDeps, currentServer, tempInstallPath, packagesHash, packagesHash, pkgToInstall,
+ errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ success = errorMsgs.IsEmpty;
return updatedPackagesHash;
}
- private ConcurrentDictionary InstallParentAndDependencyPackages(List parentAndDeps, ServerApiCall currentServer, string tempInstallPath, ConcurrentDictionary packagesHash, ConcurrentDictionary updatedPackagesHash, PSResourceInfo pkgToInstall)
+ private ConcurrentDictionary InstallParentAndDependencyPackages(
+ List parentAndDeps,
+ ServerApiCall currentServer,
+ string tempInstallPath,
+ ConcurrentDictionary packagesHash,
+ ConcurrentDictionary updatedPackagesHash,
+ PSResourceInfo pkgToInstall,
+ ConcurrentQueue errorMsgs,
+ ConcurrentQueue warningMsgs,
+ ConcurrentQueue debugMsgs,
+ ConcurrentQueue verboseMsgs)
{
- string warning = string.Empty;
- ConcurrentQueue errorMsgs = new ConcurrentQueue();
- ConcurrentQueue verboseMsgs = new ConcurrentQueue();
- ConcurrentQueue debugMsgs = new ConcurrentQueue();
- ConcurrentQueue warningMsgs = new ConcurrentQueue();
-
// TODO: figure out a good threshold and parallel count
int processorCount = Environment.ProcessorCount;
- _cmdletPassedIn.WriteDebug($"parentAndDeps.Count is {parentAndDeps.Count}, processor count is: {processorCount}");
+ debugMsgs.Enqueue($"parentAndDeps.Count is {parentAndDeps.Count}, processor count is: {processorCount}");
if (parentAndDeps.Count > processorCount)
{
- _cmdletPassedIn.WriteDebug($"parentAndDeps.Count is greater than processor count");
+ debugMsgs.Enqueue($"parentAndDeps.Count is greater than processor count");
// Set the maximum degree of parallelism to 32? (Invoke-Command has default of 32, that's where we got this number from)
// If installing more than 3 packages, do so concurrently
// If the number of dependencies is very small (e.g., ≤ CPU cores), parallelism may add overhead instead of improving speed.
@@ -891,8 +906,7 @@ private ConcurrentDictionary InstallParentAndDependencyPackag
}
});
- Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
- if (errorMsgs.Count > 0)
+ if (!errorMsgs.IsEmpty)
{
return packagesHash;
}
@@ -910,16 +924,13 @@ private ConcurrentDictionary InstallParentAndDependencyPackag
if (installNameErrRecord != null)
{
- _cmdletPassedIn.WriteError(installNameErrRecord);
+ errorMsgs.Enqueue(installNameErrRecord);
return packagesHash;
}
- //ErrorRecord tempSaveErrRecord = null, tempInstallErrRecord = null;
bool installedToTempPathSuccessfully = _asNupkg ? TrySaveNupkgToTempPath(responseStream, tempInstallPath, pkgToInstallName, pkgToInstallVersion, pkgToBeInstalled, packagesHash, out updatedPackagesHash, errorMsgs, warningMsgs, debugMsgs, verboseMsgs) :
TryInstallToTempPath(responseStream, tempInstallPath, pkgToInstallName, pkgToInstallVersion, pkgToBeInstalled, packagesHash, out updatedPackagesHash, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
- Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
-
if (!installedToTempPathSuccessfully)
{
return packagesHash;
From 06e8a867600df0b28662a8497fcbcab172913d0e Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Fri, 31 Jul 2026 14:31:11 -0700
Subject: [PATCH 14/24] Use ConcurrentQueue for commented out debug msg
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
src/code/V3ServerAPICalls.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/code/V3ServerAPICalls.cs b/src/code/V3ServerAPICalls.cs
index 234fc513a..0291dc5b4 100644
--- a/src/code/V3ServerAPICalls.cs
+++ b/src/code/V3ServerAPICalls.cs
@@ -750,7 +750,7 @@ private FindResults FindVersionHelper(string packageName, string version, string
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v3FindResponseType);
}
- //_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'");
+debugMsgs.Enqueue($"'{packageName}' version parsed as '{requiredVersion}'");
string[] versionedResponses = GetVersionedPackageEntriesFromRegistrationsResource(packageName, catalogEntryProperty, isSearch: true, out errRecord, errorMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
From 90d11f78611f7953352d4253626483520c753d02 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Fri, 31 Jul 2026 14:36:13 -0700
Subject: [PATCH 15/24] Bug fix for filtering on version
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
src/code/NuGetServerAPICalls.cs | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs
index d8c6b5ffe..e89b7f623 100644
--- a/src/code/NuGetServerAPICalls.cs
+++ b/src/code/NuGetServerAPICalls.cs
@@ -63,10 +63,9 @@ public override Task FindVersionAsync(string packageName, string ve
});
var filterBuilder = queryBuilder.FilterBuilder;
- // We need to explicitly add 'Id eq ' whenever $filter is used, otherwise arbitrary results are returned.
- filterBuilder.AddCriterion($"Id eq '{packageName}'");
- filterBuilder.AddCriterion($"NormalizedVersion eq '{packageName}'");
-
+// We need to explicitly add 'Id eq ' whenever $filter is used, otherwise arbitrary results are returned.
+filterBuilder.AddCriterion($"Id eq '{packageName}'");
+filterBuilder.AddCriterion($"NormalizedVersion eq '{version}'");
var requestUrl = $"{Repository.Uri}/FindPackagesById()?{queryBuilder.BuildQueryString()}";
string response = HttpRequestCallAsync(requestUrl, debugMsgs, out ErrorRecord errRecord);
FindResults findResponse = new FindResults(stringResponse: new string[] { response }, hashtableResponse: emptyHashResponses, responseType: FindResponseType);
From 40553306b00b00c99bec808a41f82848535f29ea Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Fri, 31 Jul 2026 15:27:04 -0700
Subject: [PATCH 16/24] Update formula for pagination count so it does not
over-fetch
---
src/code/NuGetServerAPICalls.cs | 10 +++++-----
src/code/V2ServerAPICalls.cs | 14 +++++++-------
src/code/V3ServerAPICalls.cs | 4 ++--
3 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs
index d8c6b5ffe..1a05df20a 100644
--- a/src/code/NuGetServerAPICalls.cs
+++ b/src/code/NuGetServerAPICalls.cs
@@ -122,7 +122,7 @@ public override FindResults FindAll(bool includePrerelease, ResourceType type, o
return new FindResults(stringResponse: responses.ToArray(), hashtableResponse: emptyHashResponses, responseType: FindResponseType);
}
- int count = initialCount / 6000;
+ int count = (int)Math.Ceiling((double)initialCount / 6000) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
@@ -166,7 +166,7 @@ public override FindResults FindTags(string[] tags, bool includePrerelease, Reso
return new FindResults(stringResponse: responses.ToArray(), hashtableResponse: emptyHashResponses, responseType: FindResponseType);
}
- int count = initialCount / 100;
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
@@ -310,7 +310,7 @@ public override FindResults FindNameGlobbing(string packageName, bool includePre
return new FindResults(stringResponse: responses.ToArray(), hashtableResponse: emptyHashResponses, responseType: FindResponseType);
}
- int count = initialCount / 100;
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
@@ -355,7 +355,7 @@ public override FindResults FindNameGlobbingWithTag(string packageName, string[]
return new FindResults(stringResponse: responses.ToArray(), hashtableResponse: emptyHashResponses, responseType: FindResponseType);
}
- int count = initialCount / 100;
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
@@ -404,7 +404,7 @@ public override FindResults FindVersionGlobbing(string packageName, VersionRange
return new FindResults(stringResponse: responses.ToArray(), hashtableResponse: emptyHashResponses, responseType: FindResponseType);
}
- int count = initialCount / 100;
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
while (count > 0)
{
diff --git a/src/code/V2ServerAPICalls.cs b/src/code/V2ServerAPICalls.cs
index 963ed5ed7..6ea3361d7 100644
--- a/src/code/V2ServerAPICalls.cs
+++ b/src/code/V2ServerAPICalls.cs
@@ -206,7 +206,7 @@ public override FindResults FindTags(string[] tags, bool includePrerelease, Reso
if (initialScriptCount != 0)
{
responses.Add(initialScriptResponse);
- int count = initialScriptCount / 100;
+ int count = (int)Math.Ceiling((double)initialScriptCount / 100) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
@@ -242,7 +242,7 @@ public override FindResults FindTags(string[] tags, bool includePrerelease, Reso
if (initialModuleCount != 0)
{
responses.Add(initialModuleResponse);
- int count = initialModuleCount / 100;
+ int count = (int)Math.Ceiling((double)initialModuleCount / 100) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
@@ -296,7 +296,7 @@ public override FindResults FindCommandOrDscResource(string[] tags, bool include
if (initialCount != 0)
{
responses.Add(initialResponse);
- int count = (int)Math.Ceiling((double)(initialCount / 100));
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
while (count > 0)
{
@@ -596,7 +596,7 @@ public override FindResults FindNameGlobbing(string packageName, bool includePre
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v2FindResponseType);
}
- int count = (int)Math.Ceiling((double)(initialCount / 100));
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
@@ -648,7 +648,7 @@ public override FindResults FindNameGlobbingWithTag(string packageName, string[]
return new FindResults(stringResponse: Utils.EmptyStrArray, hashtableResponse: emptyHashResponses, responseType: v2FindResponseType);
}
- int count = (int)Math.Ceiling((double)(initialCount / 100));
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
@@ -704,7 +704,7 @@ public override FindResults FindVersionGlobbing(string packageName, VersionRange
if (!getOnlyLatest)
{
- int count = (int)Math.Ceiling((double)(initialCount / 100));
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
while (count > 0)
{
@@ -1735,7 +1735,7 @@ public override async Task FindVersionGlobbingAsync(string packageN
if (!getOnlyLatest)
{
- int count = (int)Math.Ceiling((double)(initialCount / 100));
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
while (count > 0)
{
diff --git a/src/code/V3ServerAPICalls.cs b/src/code/V3ServerAPICalls.cs
index 234fc513a..dd0fc35f6 100644
--- a/src/code/V3ServerAPICalls.cs
+++ b/src/code/V3ServerAPICalls.cs
@@ -1097,8 +1097,8 @@ private List GetVersionedPackageEntriesFromSearchQueryResource(stri
// Get responses for all packages that contain the required tags
pkgEntries.AddRange(GetJsonElementArr(query, dataName, out int initialCount, out errRecord, errorMsgs, debugMsgs, verboseMsgs).ToList());
- // check count (ie "totalHits") 425 ==> count/100 ~~> 4 calls ~~> + 1 = 5 calls
- int count = initialCount / 100 + 1;
+ // check count (ie "totalHits") 425 ==> ceil(425/100) - 1 ~~> 4 more calls (initial page already fetched)
+ int count = (int)Math.Ceiling((double)initialCount / 100) - 1;
// if more than 100 count, loop and add response to list
while (count > 0)
{
From 205b4cbfa0d1ee3c0f4e271858db5c7e13a4d740 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Fri, 31 Jul 2026 15:34:38 -0700
Subject: [PATCH 17/24] Use InstallPackageAsync for parent install
parallelization
---
src/code/InstallHelper.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs
index fe01209be..e6a6f986c 100644
--- a/src/code/InstallHelper.cs
+++ b/src/code/InstallHelper.cs
@@ -920,11 +920,11 @@ private ConcurrentDictionary InstallParentAndDependencyPackag
{
var pkgToInstallName = pkgToBeInstalled.Name;
var pkgToInstallVersion = Utils.GetFullVersionString(pkgToBeInstalled.Version.ToString(), pkgToBeInstalled.Prerelease);
- Stream responseStream = currentServer.InstallPackage(pkgToInstallName, pkgToInstallVersion, true, out ErrorRecord installNameErrRecord);
+ // Runs on worker threads when parent installs are parallelized; use the async overload to avoid cross-thread cmdlet stream writes.
+ Stream responseStream = currentServer.InstallPackageAsync(pkgToInstallName, pkgToInstallVersion, true, errorMsgs, warningMsgs, debugMsgs, verboseMsgs).GetAwaiter().GetResult();
- if (installNameErrRecord != null)
+ if (!errorMsgs.IsEmpty)
{
- errorMsgs.Enqueue(installNameErrRecord);
return packagesHash;
}
From 169070f8ac3c44731854d440e6f211506763a3ce Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Mon, 3 Aug 2026 13:27:29 -0700
Subject: [PATCH 18/24] add concurrentqueues to appropriate places
---
src/code/FindHelper.cs | 50 +++++++++++++++++++++++++++++----------
src/code/InstallHelper.cs | 2 +-
2 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs
index e4c9dd8d5..5e773fc11 100644
--- a/src/code/FindHelper.cs
+++ b/src/code/FindHelper.cs
@@ -1176,23 +1176,51 @@ private string FormatPkgVersionString(PSResourceInfo pkg)
#region Internal Client Search Methods
internal IEnumerable FindDependencyPackages(ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository)
+ {
+ // Pipeline-thread callers: collect diagnostics locally and drain them to the cmdlet on this thread.
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+
+ var depPkgs = FindDependencyPackages(currentServer, currentResponseUtil, currentPkg, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ return depPkgs;
+ }
+
+ // Overload for worker-thread callers: diagnostics are routed to the caller-provided queues and drained by the caller on the pipeline thread.
+ internal IEnumerable FindDependencyPackages(
+ ServerApiCall currentServer,
+ ResponseUtil currentResponseUtil,
+ PSResourceInfo currentPkg,
+ PSRepositoryInfo repository,
+ ConcurrentQueue errorMsgs,
+ ConcurrentQueue warningMsgs,
+ ConcurrentQueue debugMsgs,
+ ConcurrentQueue verboseMsgs)
{
// Use a local instance so multiple parent packages can resolve their dependency closures concurrently
// without racing on shared state.
ConcurrentDictionary depPkgsFound = new ConcurrentDictionary();
- _cmdletPassedIn.WriteDebug($"In FindHelper::FindDependencyPackages() - {currentPkg.Name}");
- FindDependencyPackagesHelper(currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound);
+ debugMsgs.Enqueue($"In FindHelper::FindDependencyPackages() - {currentPkg.Name}");
+ FindDependencyPackagesHelper(currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
return depPkgsFound.Values.ToList();
}
// Method 2
- internal void FindDependencyPackagesHelper(ServerApiCall currentServer, ResponseUtil currentResponseUtil, PSResourceInfo currentPkg, PSRepositoryInfo repository, ConcurrentDictionary depPkgsFound)
+ internal void FindDependencyPackagesHelper(
+ ServerApiCall currentServer,
+ ResponseUtil currentResponseUtil,
+ PSResourceInfo currentPkg,
+ PSRepositoryInfo repository,
+ ConcurrentDictionary depPkgsFound,
+ ConcurrentQueue errorMsgs,
+ ConcurrentQueue warningMsgs,
+ ConcurrentQueue debugMsgs,
+ ConcurrentQueue verboseMsgs)
{
- ConcurrentQueue errorMsgs = new ConcurrentQueue();
- ConcurrentQueue verboseMsgs = new ConcurrentQueue();
- ConcurrentQueue debugMsgs = new ConcurrentQueue();
- ConcurrentQueue warningMsgs = new ConcurrentQueue();
debugMsgs.Enqueue("In FindHelper::FindDependencyPackagesHelper()");
if (currentPkg.Dependencies.Length > 0)
@@ -1217,8 +1245,6 @@ internal void FindDependencyPackagesHelper(ServerApiCall currentServer, Response
FindDependencyPackageVersion(dep, currentServer, currentResponseUtil, currentPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
-
- Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
@@ -1374,7 +1400,7 @@ private PSResourceInfo FindDependencyWithSpecificVersion(
// This will eventually return the PSResourceInfo object to the main cmdlet class.
debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found");
depPkgsFound.TryAdd(key, depPkg);
- FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound);
+ FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
}
@@ -1443,7 +1469,7 @@ private PSResourceInfo FindDependencyWithLowerBound(
// This will eventually return the PSResourceInfo object to the main cmdlet class.
debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found");
depPkgsFound.TryAdd(key, depPkg);
- FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound);
+ FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
}
@@ -1515,7 +1541,7 @@ private PSResourceInfo FindDependencyWithUpperBound(
// This will eventually return the PSResourceInfo object to the main cmdlet class.
debugMsgs.Enqueue($"Adding'{key}' to list of dependency packages found");
depPkgsFound.TryAdd(key, depPkg);
- FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound);
+ FindDependencyPackagesHelper(currentServer, currentResponseUtil, depPkg, repository, depPkgsFound, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
}
}
diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs
index e6a6f986c..c71cd0c6e 100644
--- a/src/code/InstallHelper.cs
+++ b/src/code/InstallHelper.cs
@@ -839,7 +839,7 @@ private ConcurrentDictionary DownloadParentAndDeps(
if (!skipDependencyCheck)
{
// List returned only includes dependencies, so we'll add the parent pkg to this list to pass on to installation method.
- parentAndDeps.AddRange(_findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository));
+ parentAndDeps.AddRange(_findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
debugMsgs.Enqueue("In InstallHelper::DownloadParentAndDeps(), found all dependencies");
}
From 2ab3a0e9c46ddb6e2d2996aed6aeac8ee5183887 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Mon, 3 Aug 2026 13:39:16 -0700
Subject: [PATCH 19/24] InstallPackageAsync now uses queue-based
InstallVersionAsync/HttpRequestCallForContentAsync, avoiding worker-thread
cmdlet stream writes.
---
src/code/NuGetServerAPICalls.cs | 86 ++++++++++++++++++++++++++++++++-
1 file changed, 85 insertions(+), 1 deletion(-)
diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs
index 1a05df20a..b8bc53121 100644
--- a/src/code/NuGetServerAPICalls.cs
+++ b/src/code/NuGetServerAPICalls.cs
@@ -521,7 +521,19 @@ public override Stream InstallPackage(string packageName, string packageVersion,
public override Task InstallPackageAsync(string packageName, string packageVersion, bool includePrerelease, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In NuGetServerAPICalls::InstallPackageAsync()");
- Stream results = InstallPackage(packageName, packageVersion, includePrerelease, out ErrorRecord errRecord);
+ Stream results = new MemoryStream();
+ if (string.IsNullOrEmpty(packageVersion))
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ exception: new ArgumentNullException($"Package version could not be found for {packageName}"),
+ "PackageVersionNullOrEmptyError",
+ ErrorCategory.InvalidArgument,
+ _cmdletPassedIn));
+
+ return Task.FromResult(results);
+ }
+
+ results = InstallVersionAsync(packageName, packageVersion, debugMsgs, out ErrorRecord errRecord);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -628,6 +640,55 @@ private HttpContent HttpRequestCallForContent(string requestUrl, out ErrorRecord
return content;
}
+ ///
+ /// Helper method that makes the HTTP request for install APIs on worker threads; enqueues diagnostics instead of writing to cmdlet streams.
+ ///
+ private HttpContent HttpRequestCallForContentAsync(string requestUrl, ConcurrentQueue debugMsgs, out ErrorRecord errRecord)
+ {
+ debugMsgs.Enqueue("In NuGetServerAPICalls::HttpRequestCallForContentAsync()");
+ errRecord = null;
+ HttpContent content = null;
+
+ try
+ {
+ debugMsgs.Enqueue($"Request url is: '{requestUrl}'");
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
+
+ content = SendRequestForContentAsync(request, _sessionClient).GetAwaiter().GetResult();
+ }
+ catch (HttpRequestException e)
+ {
+ errRecord = new ErrorRecord(
+ exception: e,
+ "HttpRequestFailure",
+ ErrorCategory.ConnectionError ,
+ this);
+ }
+ catch (ArgumentNullException e)
+ {
+ errRecord = new ErrorRecord(
+ exception: e,
+ "HttpRequestFailure",
+ ErrorCategory.InvalidData,
+ this);
+ }
+ catch (InvalidOperationException e)
+ {
+ errRecord = new ErrorRecord(
+ exception: e,
+ "HttpRequestFailure",
+ ErrorCategory.InvalidOperation,
+ this);
+ }
+
+ if (string.IsNullOrEmpty(content?.ToString()))
+ {
+ debugMsgs.Enqueue("Response is empty");
+ }
+
+ return content;
+ }
+
///
/// Helper method that makes the HTTP request for the NuGet server protocol url passed in for async find APIs.
/// This helper writes diagnostics to the provided debug queue and avoids cmdlet stream writes.
@@ -1040,6 +1101,29 @@ private Stream InstallVersion(string packageName, string version, out ErrorRecor
return response.ReadAsStreamAsync().Result;
}
+ ///
+ /// Worker-thread counterpart of InstallVersion(); enqueues diagnostics instead of writing to cmdlet streams.
+ ///
+ private Stream InstallVersionAsync(string packageName, string version, ConcurrentQueue debugMsgs, out ErrorRecord errRecord)
+ {
+ debugMsgs.Enqueue("In NuGetServerAPICalls::InstallVersionAsync()");
+ var requestUrl = $"{Repository.Uri}/Packages(Id='{packageName}',Version='{version}')/Download";
+ var response = HttpRequestCallForContentAsync(requestUrl, debugMsgs, out errRecord);
+
+ if (response is null)
+ {
+ errRecord = new ErrorRecord(
+ new Exception($"No content was returned by repository '{Repository.Name}'"),
+ "InstallFailureContentNullNuGetServer",
+ ErrorCategory.InvalidResult,
+ this);
+
+ return null;
+ }
+
+ return response.ReadAsStreamAsync().Result;
+ }
+
///
/// Helper method that makes gets 'count' property from http response string.
/// The count property is used to determine the number of total results found (for pagination).
From a993a9fde5e66372f9a7d60efdec549fdf9e163f Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Mon, 3 Aug 2026 13:43:37 -0700
Subject: [PATCH 20/24] uses concurrentQueue.IsEmpty instead of .Count()
---
src/code/InstallHelper.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs
index c71cd0c6e..4e13b4b5f 100644
--- a/src/code/InstallHelper.cs
+++ b/src/code/InstallHelper.cs
@@ -885,7 +885,7 @@ private ConcurrentDictionary InstallParentAndDependencyPackag
// add async
Stream responseStream = currentServer.InstallPackageAsync(depPkgName, depPkgVersion, true, errorMsgs, warningMsgs, debugMsgs, verboseMsgs).GetAwaiter().GetResult();
- if (errorMsgs.Count > 0)
+ if (!errorMsgs.IsEmpty)
{
verboseMsgs.Enqueue($"Error installing package '{depPkgName}'");
}
From a689796fd8279a56672f9e070abd95cd0bfb56eb Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Mon, 3 Aug 2026 14:12:19 -0700
Subject: [PATCH 21/24] implement findNameAsync in NuGetServerApi
---
src/code/NuGetServerAPICalls.cs | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs
index b8bc53121..6ca8a4c1b 100644
--- a/src/code/NuGetServerAPICalls.cs
+++ b/src/code/NuGetServerAPICalls.cs
@@ -238,7 +238,19 @@ public override FindResults FindName(string packageName, bool includePrerelease,
public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In NuGetServerAPICalls::FindNameAsync()");
- FindResults findResponse = FindName(packageName, includePrerelease, type, out ErrorRecord errRecord);
+ var queryBuilder = new NuGetV2QueryBuilder(new Dictionary{
+ { "id", $"'{packageName}'" },
+ });
+ var filterBuilder = queryBuilder.FilterBuilder;
+
+ filterBuilder.AddCriterion(includePrerelease ? "IsAbsoluteLatestVersion" : "IsLatestVersion");
+
+ // We need to explicitly add 'Id eq ' whenever $filter is used, otherwise arbitrary results are returned.
+ filterBuilder.AddCriterion($"Id eq '{packageName}'");
+
+ var requestUrl = $"{Repository.Uri}/FindPackagesById()?{queryBuilder.BuildQueryString()}";
+ string response = HttpRequestCallAsync(requestUrl, debugMsgs, out ErrorRecord errRecord);
+ FindResults findResponse = new FindResults(stringResponse: new string[] { response }, hashtableResponse: emptyHashResponses, responseType: FindResponseType);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
From 6b26aae0aa69fa4006560b1d1198be912ebef331 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Mon, 3 Aug 2026 14:38:19 -0700
Subject: [PATCH 22/24] Create async methods in ContainerRegistryServer class
---
src/code/ContainerRegistryServerAPICalls.cs | 188 +++++++++++++-------
1 file changed, 122 insertions(+), 66 deletions(-)
diff --git a/src/code/ContainerRegistryServerAPICalls.cs b/src/code/ContainerRegistryServerAPICalls.cs
index cd8c4c8be..8a6336bea 100644
--- a/src/code/ContainerRegistryServerAPICalls.cs
+++ b/src/code/ContainerRegistryServerAPICalls.cs
@@ -91,12 +91,29 @@ public ContainerRegistryServerAPICalls(PSRepositoryInfo repository, PSCmdlet cmd
public override Task FindVersionAsync(string packageName, string version, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindVersionAsync()");
- FindResults findResponse = FindVersion(packageName, version, type, out ErrorRecord errRecord);
+ if (!NuGetVersion.TryParse(version, out NuGetVersion requiredVersion))
+ {
+ errorMsgs.Enqueue(new ErrorRecord(
+ new ArgumentException($"Version {version} to be found is not a valid NuGet version."),
+ "FindNameFailure",
+ ErrorCategory.InvalidArgument,
+ this));
+
+ return Task.FromResult(emptyResponseResults);
+ }
+
+ debugMsgs.Enqueue($"'{packageName}' version parsed as '{requiredVersion}'");
+ bool includePrereleaseVersions = requiredVersion.IsPrerelease;
+
+ // for FindVersion(), need to consider the specific required version (hence VersionType.SpecificVersion and no version range)
+ Hashtable[] pkgResult = FindPackagesWithVersionHelper(packageName, VersionType.SpecificVersion, versionRange: VersionRange.None, requiredVersion: requiredVersion, includePrereleaseVersions, getOnlyLatest: false, out ErrorRecord errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
+ return Task.FromResult(emptyResponseResults);
}
+ FindResults findResponse = new FindResults(stringResponse: new string[] { }, hashtableResponse: pkgResult.ToArray(), responseType: containerRegistryFindResponseType);
return Task.FromResult(findResponse);
}
@@ -109,12 +126,16 @@ public override Task FindVersionAsync(string packageName, string ve
public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindVersionGlobbingAsync()");
- FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord);
+
+ // for FindVersionGlobbing(), need to consider all versions that match version range criteria (hence VersionType.VersionRange and no requiredVersion)
+ Hashtable[] pkgResults = FindPackagesWithVersionHelper(packageName, VersionType.VersionRange, versionRange: versionRange, requiredVersion: null, includePrerelease, getOnlyLatest: false, out ErrorRecord errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
+ return Task.FromResult(emptyResponseResults);
}
+ FindResults findResponse = new FindResults(stringResponse: new string[] { }, hashtableResponse: pkgResults.ToArray(), responseType: containerRegistryFindResponseType);
return Task.FromResult(findResponse);
}
@@ -172,9 +193,14 @@ public override FindResults FindCommandOrDscResource(string[] tags, bool include
public override FindResults FindName(string packageName, bool includePrerelease, ResourceType type, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::FindName()");
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
// for FindName(), need to consider all versions (hence VersionType.VersionRange and VersionRange.All, and no requiredVersion) but only pick latest (hence getOnlyLatest: true)
- Hashtable[] pkgResult = FindPackagesWithVersionHelper(packageName, VersionType.VersionRange, versionRange: VersionRange.All, requiredVersion: null, includePrerelease, getOnlyLatest: true, out errRecord);
+ Hashtable[] pkgResult = FindPackagesWithVersionHelper(packageName, VersionType.VersionRange, versionRange: VersionRange.All, requiredVersion: null, includePrerelease, getOnlyLatest: true, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return emptyResponseResults;
@@ -192,12 +218,16 @@ public override FindResults FindName(string packageName, bool includePrerelease,
public override Task FindNameAsync(string packageName, bool includePrerelease, ResourceType type, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In ContainerRegistryServerAPICalls::FindNameAsync()");
- FindResults findResponse = FindName(packageName, includePrerelease, type, out ErrorRecord errRecord);
+
+ // for FindName(), need to consider all versions (hence VersionType.VersionRange and VersionRange.All, and no requiredVersion) but only pick latest (hence getOnlyLatest: true)
+ Hashtable[] pkgResult = FindPackagesWithVersionHelper(packageName, VersionType.VersionRange, versionRange: VersionRange.All, requiredVersion: null, includePrerelease, getOnlyLatest: true, out ErrorRecord errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
+ return Task.FromResult(emptyResponseResults);
}
+ FindResults findResponse = new FindResults(stringResponse: new string[] { }, hashtableResponse: pkgResult.ToArray(), responseType: containerRegistryFindResponseType);
return Task.FromResult(findResponse);
}
@@ -265,9 +295,14 @@ public override FindResults FindNameGlobbingWithTag(string packageName, string[]
public override FindResults FindVersionGlobbing(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, out ErrorRecord errRecord)
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::FindVersionGlobbing()");
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
// for FindVersionGlobbing(), need to consider all versions that match version range criteria (hence VersionType.VersionRange and no requiredVersion)
- Hashtable[] pkgResults = FindPackagesWithVersionHelper(packageName, VersionType.VersionRange, versionRange: versionRange, requiredVersion: null, includePrerelease, getOnlyLatest: false, out errRecord);
+ Hashtable[] pkgResults = FindPackagesWithVersionHelper(packageName, VersionType.VersionRange, versionRange: versionRange, requiredVersion: null, includePrerelease, getOnlyLatest: false, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return emptyResponseResults;
@@ -298,9 +333,14 @@ public override FindResults FindVersion(string packageName, string version, Reso
_cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{requiredVersion}'");
bool includePrereleaseVersions = requiredVersion.IsPrerelease;
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
// for FindVersion(), need to consider the specific required version (hence VersionType.SpecificVersion and no version range)
- Hashtable[] pkgResult = FindPackagesWithVersionHelper(packageName, VersionType.SpecificVersion, versionRange: VersionRange.None, requiredVersion: requiredVersion, includePrereleaseVersions, getOnlyLatest: false, out errRecord);
+ Hashtable[] pkgResult = FindPackagesWithVersionHelper(packageName, VersionType.SpecificVersion, versionRange: VersionRange.None, requiredVersion: requiredVersion, includePrereleaseVersions, getOnlyLatest: false, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return emptyResponseResults;
@@ -399,6 +439,10 @@ private Stream InstallVersion(
string packageNameLowercase = packageName.ToLower();
string accessToken = string.Empty;
string tenantID = string.Empty;
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
try
{
@@ -415,7 +459,8 @@ private Stream InstallVersion(
return null;
}
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: false, out errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: false, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return null;
@@ -482,7 +527,7 @@ private Stream InstallVersionAsync(
return null;
}
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: false, out ErrorRecord errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: false, out ErrorRecord errRecord, errorMsgs, warningMsgs: null, debugMsgs, verboseMsgs);
if (errRecord != null)
{
errorMsgs.Enqueue(errRecord);
@@ -533,9 +578,9 @@ private Stream InstallVersionAsync(
/// If no credential provided at registration then, check if the ACR endpoint can be accessed without a token. If not, try using Azure.Identity to get the az access token, then ACR refresh token and then ACR access token.
/// Note: Access token can be empty if the repository is unauthenticated
///
- internal string GetContainerRegistryAccessToken(bool needCatalogAccess, bool isPushOperation, out ErrorRecord errRecord)
+ internal string GetContainerRegistryAccessToken(bool needCatalogAccess, bool isPushOperation, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetContainerRegistryAccessToken()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::GetContainerRegistryAccessToken()");
string accessToken = string.Empty;
string containerRegistryAccessToken = string.Empty;
string tenantID = string.Empty;
@@ -543,7 +588,7 @@ internal string GetContainerRegistryAccessToken(bool needCatalogAccess, bool isP
if (!string.IsNullOrEmpty(_cachedContainterRegistryToken))
{
- _cmdletPassedIn.WriteVerbose("Using cached container registry access token.");
+ verboseMsgs?.Enqueue("Using cached container registry access token.");
return _cachedContainterRegistryToken;
}
@@ -555,17 +600,17 @@ internal string GetContainerRegistryAccessToken(bool needCatalogAccess, bool isP
repositoryCredentialInfo,
_cmdletPassedIn);
- _cmdletPassedIn.WriteVerbose("Access token retrieved.");
+ verboseMsgs?.Enqueue("Access token retrieved.");
tenantID = repositoryCredentialInfo.SecretName;
}
else
{
// A container registry repository is determined to be unauthenticated if it allows anonymous pull access. However, push operations always require authentication.
- bool isRepositoryUnauthenticated = isPushOperation ? false : IsContainerRegistryUnauthenticated(Repository.Uri.ToString(), needCatalogAccess, out errRecord, out accessToken);
- _cmdletPassedIn.WriteInformation($"Value of isRepositoryUnauthenticated: {isRepositoryUnauthenticated}", new string[] { "PSRGContainerRegistryUnauthenticatedCheck" });
+ bool isRepositoryUnauthenticated = isPushOperation ? false : IsContainerRegistryUnauthenticated(Repository.Uri.ToString(), needCatalogAccess, out errRecord, out accessToken, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ verboseMsgs?.Enqueue($"Value of isRepositoryUnauthenticated: {isRepositoryUnauthenticated}");
- _cmdletPassedIn.WriteDebug($"Is repository unauthenticated: {isRepositoryUnauthenticated}");
+ debugMsgs?.Enqueue($"Is repository unauthenticated: {isRepositoryUnauthenticated}");
if (errRecord != null)
{
@@ -574,7 +619,7 @@ internal string GetContainerRegistryAccessToken(bool needCatalogAccess, bool isP
if (!string.IsNullOrEmpty(accessToken))
{
- _cmdletPassedIn.WriteVerbose("Anonymous access token retrieved.");
+ verboseMsgs?.Enqueue("Anonymous access token retrieved.");
return accessToken;
}
@@ -594,24 +639,24 @@ internal string GetContainerRegistryAccessToken(bool needCatalogAccess, bool isP
}
else
{
- _cmdletPassedIn.WriteVerbose("Repository is unauthenticated");
+ verboseMsgs?.Enqueue("Repository is unauthenticated");
return null;
}
}
- var containerRegistryRefreshToken = GetContainerRegistryRefreshToken(tenantID, accessToken, out errRecord);
+ var containerRegistryRefreshToken = GetContainerRegistryRefreshToken(tenantID, accessToken, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return null;
}
- containerRegistryAccessToken = GetContainerRegistryAccessTokenByRefreshToken(containerRegistryRefreshToken, out errRecord);
+ containerRegistryAccessToken = GetContainerRegistryAccessTokenByRefreshToken(containerRegistryRefreshToken, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return null;
}
- _cmdletPassedIn.WriteVerbose("Container registry access token retrieved.");
+ verboseMsgs?.Enqueue("Container registry access token retrieved.");
_cachedContainterRegistryToken = containerRegistryAccessToken;
return containerRegistryAccessToken;
@@ -620,9 +665,9 @@ internal string GetContainerRegistryAccessToken(bool needCatalogAccess, bool isP
///
/// Checks if container registry repository is unauthenticated.
///
- internal bool IsContainerRegistryUnauthenticated(string containerRegistryUrl, bool needCatalogAccess, out ErrorRecord errRecord, out string anonymousAccessToken)
+ internal bool IsContainerRegistryUnauthenticated(string containerRegistryUrl, bool needCatalogAccess, out ErrorRecord errRecord, out string anonymousAccessToken, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::IsContainerRegistryUnauthenticated()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::IsContainerRegistryUnauthenticated()");
errRecord = null;
anonymousAccessToken = string.Empty;
string endpoint = $"{containerRegistryUrl}/v2/";
@@ -664,24 +709,24 @@ internal bool IsContainerRegistryUnauthenticated(string containerRegistryUrl, bo
string url = needCatalogAccess ? String.Format(authUrlTemplate, realm, service, catalogScope) : String.Format(authUrlTemplate, realm, service, defaultScope);
- _cmdletPassedIn.WriteDebug($"Getting anonymous access token from the realm: {url}");
+ debugMsgs?.Enqueue($"Getting anonymous access token from the realm: {url}");
// we don't check the error record here because we want to return false if we get a 401 and not throw an error
- _cmdletPassedIn.WriteDebug($"Getting anonymous access token from the realm: {url}");
+ debugMsgs?.Enqueue($"Getting anonymous access token from the realm: {url}");
ErrorRecord errRecordTemp = null;
- var results = GetHttpResponseJObjectUsingContentHeaders(url, HttpMethod.Get, content, contentHeaders, out errRecordTemp);
+ var results = GetHttpResponseJObjectUsingContentHeaders(url, HttpMethod.Get, content, contentHeaders, out errRecordTemp, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (results == null)
{
- _cmdletPassedIn.WriteDebug("Failed to get access token from the realm. results is null.");
- _cmdletPassedIn.WriteDebug($"ErrorRecord: {errRecordTemp}");
+ debugMsgs?.Enqueue("Failed to get access token from the realm. results is null.");
+ debugMsgs?.Enqueue($"ErrorRecord: {errRecordTemp}");
return false;
}
if (results["access_token"] == null)
{
- _cmdletPassedIn.WriteDebug($"Failed to get access token from the realm. access_token is null. results: {results}");
+ debugMsgs?.Enqueue($"Failed to get access token from the realm. access_token is null. results: {results}");
return false;
}
@@ -719,13 +764,13 @@ internal bool IsContainerRegistryUnauthenticated(string containerRegistryUrl, bo
///
/// Given the access token retrieved from credentials, gets the refresh token.
///
- internal string GetContainerRegistryRefreshToken(string tenant, string accessToken, out ErrorRecord errRecord)
+ internal string GetContainerRegistryRefreshToken(string tenant, string accessToken, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetContainerRegistryRefreshToken()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::GetContainerRegistryRefreshToken()");
string content = string.Format(containerRegistryRefreshTokenTemplate, Registry, tenant, accessToken);
var contentHeaders = new Collection> { new KeyValuePair("Content-Type", "application/x-www-form-urlencoded") };
string exchangeUrl = string.Format(containerRegistryOAuthExchangeUrlTemplate, Registry);
- var results = GetHttpResponseJObjectUsingContentHeaders(exchangeUrl, HttpMethod.Post, content, contentHeaders, out errRecord);
+ var results = GetHttpResponseJObjectUsingContentHeaders(exchangeUrl, HttpMethod.Post, content, contentHeaders, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null || results == null || results["refresh_token"] == null)
{
return string.Empty;
@@ -737,13 +782,13 @@ internal string GetContainerRegistryRefreshToken(string tenant, string accessTok
///
/// Given the refresh token, gets the new access token with appropriate scope access permissions.
///
- internal string GetContainerRegistryAccessTokenByRefreshToken(string refreshToken, out ErrorRecord errRecord)
+ internal string GetContainerRegistryAccessTokenByRefreshToken(string refreshToken, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetContainerRegistryAccessTokenByRefreshToken()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::GetContainerRegistryAccessTokenByRefreshToken()");
string content = string.Format(containerRegistryAccessTokenTemplate, Registry, refreshToken);
var contentHeaders = new Collection> { new KeyValuePair("Content-Type", "application/x-www-form-urlencoded") };
string tokenUrl = string.Format(containerRegistryOAuthTokenUrlTemplate, Registry);
- var results = GetHttpResponseJObjectUsingContentHeaders(tokenUrl, HttpMethod.Post, content, contentHeaders, out errRecord);
+ var results = GetHttpResponseJObjectUsingContentHeaders(tokenUrl, HttpMethod.Post, content, contentHeaders, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null || results == null || results["access_token"] == null)
{
return string.Empty;
@@ -828,7 +873,7 @@ internal async Task GetContainerRegistryBlobAsync(string packageNam
/// Gets the image tags associated with the package (i.e repository in container registry terms), where the tag corresponds to the package's versions.
/// If the package version is specified search for that specific tag for the image, if the package version is "*" search for all tags for the image.
///
- internal JObject FindContainerRegistryImageTags(string packageName, string version, string containerRegistryAccessToken, out ErrorRecord errRecord)
+ internal JObject FindContainerRegistryImageTags(string packageName, string version, string containerRegistryAccessToken, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
/*
{
@@ -844,7 +889,7 @@ internal JObject FindContainerRegistryImageTags(string packageName, string versi
string resolvedVersion = string.Equals(version, "*", StringComparison.OrdinalIgnoreCase) ? null : $"/{version}";
string findImageUrl = string.Format(containerRegistryFindImageVersionUrlTemplate, Registry, packageName);
var defaultHeaders = GetDefaultHeaders(containerRegistryAccessToken);
- return GetHttpResponseJObjectUsingDefaultHeaders(findImageUrl, HttpMethod.Get, defaultHeaders, out errRecord);
+ return GetHttpResponseJObjectUsingDefaultHeaders(findImageUrl, HttpMethod.Get, defaultHeaders, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
///
@@ -864,12 +909,12 @@ internal JObject FindAllRepositories(string containerRegistryAccessToken, out Er
///
/// Get metadata for a package version.
///
- internal Hashtable GetContainerRegistryMetadata(string packageName, string exactTagVersion, string containerRegistryAccessToken, out ErrorRecord errRecord)
+ internal Hashtable GetContainerRegistryMetadata(string packageName, string exactTagVersion, string containerRegistryAccessToken, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetContainerRegistryMetadata()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::GetContainerRegistryMetadata()");
Hashtable requiredVersionResponse = new();
- JObject foundTags = FindContainerRegistryManifest(packageName, exactTagVersion, containerRegistryAccessToken, out errRecord);
+ JObject foundTags = FindContainerRegistryManifest(packageName, exactTagVersion, containerRegistryAccessToken, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return requiredVersionResponse;
@@ -898,7 +943,7 @@ internal Hashtable GetContainerRegistryMetadata(string packageName, string exact
}
*/
- ContainerRegistryInfo serverPkgInfo = GetMetadataProperty(foundTags, packageName, out errRecord);
+ ContainerRegistryInfo serverPkgInfo = GetMetadataProperty(foundTags, packageName, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return requiredVersionResponse;
@@ -959,7 +1004,7 @@ internal Hashtable GetContainerRegistryMetadata(string packageName, string exact
return requiredVersionResponse;
}
- _cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
+ debugMsgs?.Enqueue($"'{packageName}' version parsed as '{pkgVersion}'");
if (pkgVersion.ToNormalizedString() == requiredVersion.ToNormalizedString())
{
requiredVersionResponse = serverPkgInfo.ToHashtable();
@@ -983,22 +1028,22 @@ internal Hashtable GetContainerRegistryMetadata(string packageName, string exact
///
/// Get the manifest associated with the package version.
///
- internal JObject FindContainerRegistryManifest(string packageName, string version, string containerRegistryAccessToken, out ErrorRecord errRecord)
+ internal JObject FindContainerRegistryManifest(string packageName, string version, string containerRegistryAccessToken, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::FindContainerRegistryManifest()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::FindContainerRegistryManifest()");
var createManifestUrl = string.Format(containerRegistryManifestUrlTemplate, Registry, packageName, version);
- _cmdletPassedIn.WriteDebug($"GET manifest url: {createManifestUrl}");
+ debugMsgs?.Enqueue($"GET manifest url: {createManifestUrl}");
var defaultHeaders = GetDefaultHeaders(containerRegistryAccessToken);
- return GetHttpResponseJObjectUsingDefaultHeaders(createManifestUrl, HttpMethod.Get, defaultHeaders, out errRecord);
+ return GetHttpResponseJObjectUsingDefaultHeaders(createManifestUrl, HttpMethod.Get, defaultHeaders, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
}
///
/// Get metadata for the package by parsing its manifest.
///
- internal ContainerRegistryInfo GetMetadataProperty(JObject foundTags, string packageName, out ErrorRecord errRecord)
+ internal ContainerRegistryInfo GetMetadataProperty(JObject foundTags, string packageName, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetMetadataProperty()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::GetMetadataProperty()");
errRecord = null;
ContainerRegistryInfo serverPkgInfo = null;
@@ -1109,9 +1154,9 @@ internal async Task GetHttpContentResponseJObject(string url, Colle
///
/// Get response object when using default headers in the request.
///
- internal JObject GetHttpResponseJObjectUsingDefaultHeaders(string url, HttpMethod method, Collection> defaultHeaders, out ErrorRecord errRecord, bool usePagination = false)
+ internal JObject GetHttpResponseJObjectUsingDefaultHeaders(string url, HttpMethod method, Collection> defaultHeaders, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null, bool usePagination = false)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetHttpResponseJObjectUsingDefaultHeaders()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::GetHttpResponseJObjectUsingDefaultHeaders()");
try
{
errRecord = null;
@@ -1160,9 +1205,9 @@ internal JObject GetHttpResponseJObjectUsingDefaultHeaders(string url, HttpMetho
///
/// Get response object when using content headers in the request.
///
- internal JObject GetHttpResponseJObjectUsingContentHeaders(string url, HttpMethod method, string content, Collection> contentHeaders, out ErrorRecord errRecord)
+ internal JObject GetHttpResponseJObjectUsingContentHeaders(string url, HttpMethod method, string content, Collection> contentHeaders, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetHttpResponseJObjectUsingContentHeaders()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::GetHttpResponseJObjectUsingContentHeaders()");
errRecord = null;
try
{
@@ -1486,7 +1531,12 @@ internal bool PushNupkgContainerRegistry(
// Get access token (includes refresh tokens)
_cmdletPassedIn.WriteVerbose($"Get access token for container registry server.");
- var containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: true, out errRecord);
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+ var containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: true, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return false;
@@ -1942,22 +1992,22 @@ internal async Task EndUploadBlob(string location, string f
///
/// Helper method for find scenarios.
///
- private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionType versionType, VersionRange versionRange, NuGetVersion requiredVersion, bool includePrerelease, bool getOnlyLatest, out ErrorRecord errRecord)
+ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionType versionType, VersionRange versionRange, NuGetVersion requiredVersion, bool includePrerelease, bool getOnlyLatest, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::FindPackagesWithVersionHelper()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::FindPackagesWithVersionHelper()");
string accessToken = string.Empty;
string tenantID = string.Empty;
string registryUrl = Repository.Uri.ToString();
string packageNameLowercase = packageName.ToLower();
string packageNameForFind = PrependMARPrefix(packageNameLowercase);
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: false,out errRecord);
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: false, isPushOperation: false, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return emptyHashResponses;
}
- var foundTags = FindContainerRegistryImageTags(packageNameForFind, "*", containerRegistryAccessToken, out errRecord);
+ var foundTags = FindContainerRegistryImageTags(packageNameForFind, "*", containerRegistryAccessToken, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null || foundTags == null)
{
return emptyHashResponses;
@@ -1966,10 +2016,10 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
List latestVersionResponse = new List();
List allVersionsList = foundTags["tags"].ToList();
- SortedDictionary sortedQualifyingPkgs = GetPackagesWithRequiredVersion(allVersionsList, versionType, versionRange, requiredVersion, packageNameForFind, includePrerelease, out errRecord);
+ SortedDictionary sortedQualifyingPkgs = GetPackagesWithRequiredVersion(allVersionsList, versionType, versionRange, requiredVersion, packageNameForFind, includePrerelease, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null && sortedQualifyingPkgs?.Count == 0)
{
- _cmdletPassedIn.WriteDebug("No qualifying packages found for the specified criteria.");
+ debugMsgs?.Enqueue("No qualifying packages found for the specified criteria.");
return emptyHashResponses;
}
@@ -1978,7 +2028,7 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
foreach (var pkgVersionTag in pkgsInDescendingOrder)
{
string exactTagVersion = pkgVersionTag.Value.ToString();
- Hashtable metadata = GetContainerRegistryMetadata(packageNameForFind, exactTagVersion, containerRegistryAccessToken, out errRecord);
+ Hashtable metadata = GetContainerRegistryMetadata(packageNameForFind, exactTagVersion, containerRegistryAccessToken, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null || metadata.Count == 0)
{
return emptyHashResponses;
@@ -1998,9 +2048,9 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
///
/// Helper method used for find scenarios that resolves versions required from all versions found.
///
- private SortedDictionary GetPackagesWithRequiredVersion(List allPkgVersions, VersionType versionType, VersionRange versionRange, NuGetVersion specificVersion, string packageName, bool includePrerelease, out ErrorRecord errRecord)
+ private SortedDictionary GetPackagesWithRequiredVersion(List allPkgVersions, VersionType versionType, VersionRange versionRange, NuGetVersion specificVersion, string packageName, bool includePrerelease, out ErrorRecord errRecord, ConcurrentQueue errorMsgs = null, ConcurrentQueue warningMsgs = null, ConcurrentQueue debugMsgs = null, ConcurrentQueue verboseMsgs = null)
{
- _cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::GetPackagesWithRequiredVersion()");
+ debugMsgs?.Enqueue("In ContainerRegistryServerAPICalls::GetPackagesWithRequiredVersion()");
errRecord = null;
// we need NuGetVersion to sort versions by order, and string pkgVersionString (which is the exact tag from the server) to call GetContainerRegistryMetadata() later with exact version tag.
SortedDictionary sortedPkgs = new SortedDictionary(VersionComparer.Default);
@@ -2018,12 +2068,12 @@ private Hashtable[] FindPackagesWithVersionHelper(string packageName, VersionTyp
ErrorCategory.InvalidArgument,
this);
- _cmdletPassedIn.WriteError(errRecord);
- _cmdletPassedIn.WriteDebug($"Skipping package '{packageName}' with version '{pkgVersionString}' as it is not a valid NuGet version.");
+ errorMsgs?.Enqueue(errRecord);
+ debugMsgs?.Enqueue($"Skipping package '{packageName}' with version '{pkgVersionString}' as it is not a valid NuGet version.");
continue; // skip this version and continue with the next one
}
- _cmdletPassedIn.WriteDebug($"'{packageName}' version parsed as '{pkgVersion}'");
+ debugMsgs?.Enqueue($"'{packageName}' version parsed as '{pkgVersion}'");
if (isSpecificVersionSearch)
{
@@ -2063,7 +2113,12 @@ private FindResults FindPackages(string packageName, bool includePrerelease, out
{
_cmdletPassedIn.WriteDebug("In ContainerRegistryServerAPICalls::FindPackages()");
errRecord = null;
- string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: true, isPushOperation: false, out errRecord);
+ ConcurrentQueue errorMsgs = new ConcurrentQueue();
+ ConcurrentQueue warningMsgs = new ConcurrentQueue();
+ ConcurrentQueue debugMsgs = new ConcurrentQueue();
+ ConcurrentQueue verboseMsgs = new ConcurrentQueue();
+ string containerRegistryAccessToken = GetContainerRegistryAccessToken(needCatalogAccess: true, isPushOperation: false, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return emptyResponseResults;
@@ -2100,7 +2155,8 @@ private FindResults FindPackages(string packageName, bool includePrerelease, out
_cmdletPassedIn.WriteDebug($"Found repository: {repositoryName}");
- repositoriesList.AddRange(FindPackagesWithVersionHelper(repositoryName, VersionType.VersionRange, versionRange: VersionRange.All, requiredVersion: null, includePrerelease, getOnlyLatest: true, out errRecord));
+ repositoriesList.AddRange(FindPackagesWithVersionHelper(repositoryName, VersionType.VersionRange, versionRange: VersionRange.All, requiredVersion: null, includePrerelease, getOnlyLatest: true, out errRecord, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
+ Utils.WriteOutConcurrentQueue(_cmdletPassedIn, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
if (errRecord != null)
{
return emptyResponseResults;
From b4dbcc494340333e4512a31afc5441069c07cfc7 Mon Sep 17 00:00:00 2001
From: alerickson <25858831+alerickson@users.noreply.github.com>
Date: Mon, 3 Aug 2026 14:51:17 -0700
Subject: [PATCH 23/24] Implement Async methods in NuGetServer class
---
src/code/NuGetServerAPICalls.cs | 60 ++++++++++++++++++++++++++++++---
1 file changed, 55 insertions(+), 5 deletions(-)
diff --git a/src/code/NuGetServerAPICalls.cs b/src/code/NuGetServerAPICalls.cs
index 6ca8a4c1b..a24358d6f 100644
--- a/src/code/NuGetServerAPICalls.cs
+++ b/src/code/NuGetServerAPICalls.cs
@@ -87,13 +87,46 @@ public override Task FindVersionAsync(string packageName, string ve
public override Task FindVersionGlobbingAsync(string packageName, VersionRange versionRange, bool includePrerelease, ResourceType type, bool getOnlyLatest, ConcurrentQueue errorMsgs, ConcurrentQueue warningMsgs, ConcurrentQueue debugMsgs, ConcurrentQueue verboseMsgs)
{
debugMsgs.Enqueue("In NuGetServerAPICalls::FindVersionGlobbingAsync()");
- FindResults findResponse = FindVersionGlobbing(packageName, versionRange, includePrerelease, type, getOnlyLatest, out ErrorRecord errRecord);
+ List responses = new List