Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 45 additions & 17 deletions src/code/InstallHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -874,8 +874,27 @@ private ConcurrentDictionary<string, Hashtable> 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, errorMsgs, warningMsgs, debugMsgs, verboseMsgs));
IEnumerable<PSResourceInfo> depPkgs = _findHelper.FindDependencyPackages(currentServer, currentResponseUtil, pkgToInstall, repository, errorMsgs, warningMsgs, debugMsgs, verboseMsgs);
debugMsgs.Enqueue("In InstallHelper::DownloadParentAndDeps(), found all dependencies");

// Do not reinstall dependencies that are already installed, unless -Reinstall was specified or an explicit
// -TargetFramework/-RuntimeIdentifier requires re-downloading to merge additional platform content.
// _packagesOnMachine is only mutated on the pipeline thread after this parallel phase, so reading it here is safe.
bool skipInstalledDeps = !_reinstall && string.IsNullOrEmpty(_targetFramework) && string.IsNullOrEmpty(_runtimeIdentifier);
foreach (PSResourceInfo depPkg in depPkgs)
{
if (skipInstalledDeps && _packagesOnMachine != null)
{
string depPkgVersion = string.IsNullOrEmpty(depPkg.Prerelease) ? depPkg.Version.ToString() : $"{depPkg.Version}-{depPkg.Prerelease}";
if (_packagesOnMachine.Contains($"{depPkg.Name}{depPkgVersion}"))
{
verboseMsgs.Enqueue($"Dependency '{depPkg.Name}' with version '{depPkgVersion}' is already installed.");
continue;
}
}

parentAndDeps.Add(depPkg);
}
}

parentAndDeps.Add(pkgToInstall);
Expand Down Expand Up @@ -1068,7 +1087,7 @@ private bool TryInstallToTempPath(
var tempDirNameVersion = Path.Combine(tempInstallPath, pkgName, pkgVersion);
Directory.CreateDirectory(tempDirNameVersion);

if (!TryExtractToDirectory(pathToFile, tempDirNameVersion, out ErrorRecord error))
if (!TryExtractToDirectory(pathToFile, tempDirNameVersion, warningMsgs, debugMsgs, out ErrorRecord error))
Comment thread
alerickson marked this conversation as resolved.
{
Utils.EnqueueIfNotNull(errorMsgs, error);
return false;
Expand Down Expand Up @@ -1296,7 +1315,16 @@ private bool TrySaveNupkgToTempPath(
/// - Root-level RID folder entries (e.g., win-x64/) based on the current platform's RID
/// - lib/{tfm}/ entries to only extract the best matching Target Framework Moniker
/// </summary>
private bool TryExtractToDirectory(string zipPath, string extractPath, out ErrorRecord error)
/// <remarks>
/// This method may run on a background thread (via Parallel.ForEach), so it must not call
/// cmdlet Write* methods directly. Messages are enqueued and written out by the pipeline thread.
/// </remarks>
private bool TryExtractToDirectory(
string zipPath,
string extractPath,
ConcurrentQueue<string> warningMsgs,
ConcurrentQueue<string> debugMsgs,
out ErrorRecord error)
{
error = null;
// Normalize the path
Expand All @@ -1321,24 +1349,24 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
bestLibFramework = NuGetFramework.ParseFolder(_targetFramework);
if (bestLibFramework == null || bestLibFramework.IsUnsupported)
{
_cmdletPassedIn.WriteDebug($"Could not parse specified TargetFramework '{_targetFramework}', falling back to auto-detection.");
bestLibFramework = GetBestLibFramework(archive);
debugMsgs.Enqueue($"Could not parse specified TargetFramework '{_targetFramework}', falling back to auto-detection.");
bestLibFramework = GetBestLibFramework(archive, debugMsgs);
}
else
{
_cmdletPassedIn.WriteDebug($"Using user-specified TargetFramework: {bestLibFramework.GetShortFolderName()}");
debugMsgs.Enqueue($"Using user-specified TargetFramework: {bestLibFramework.GetShortFolderName()}");
}
}
else
{
bestLibFramework = GetBestLibFramework(archive);
bestLibFramework = GetBestLibFramework(archive, debugMsgs);
}

// Warn if TFM filtering is skipping assemblies for a different .NET lineage
// (e.g., installing on PS7/.NET 8 but package also has net472 for WinPS 5.1)
if (bestLibFramework != null)
{
WarnIfCrossLineageTfmSkipped(archive, bestLibFramework);
WarnIfCrossLineageTfmSkipped(archive, bestLibFramework, warningMsgs);
}

foreach (ZipArchiveEntry entry in archive.Entries.Where(entry => entry.CompressedLength > 0))
Expand All @@ -1351,15 +1379,15 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error

if (!includeEntry)
{
_cmdletPassedIn.WriteDebug($"Skipping runtime entry not matching target platform: {entry.FullName}");
debugMsgs.Enqueue($"Skipping runtime entry not matching target platform: {entry.FullName}");
continue;
}
}

// TFM filtering: for lib/ entries, only extract the best matching TFM
if (bestLibFramework != null && !ShouldIncludeLibEntry(entry.FullName, bestLibFramework))
{
_cmdletPassedIn.WriteDebug($"Skipping lib entry not matching target framework: {entry.FullName}");
debugMsgs.Enqueue($"Skipping lib entry not matching target framework: {entry.FullName}");
continue;
}

Expand Down Expand Up @@ -1412,7 +1440,7 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
.Select(s => s[1])
.Distinct(StringComparer.OrdinalIgnoreCase);
string available = string.Join(", ", availableTfms);
_cmdletPassedIn.WriteWarning(
warningMsgs.Enqueue(
$"The specified TargetFramework '{_targetFramework}' was not found in this package. " +
$"No lib/ assemblies were installed. Available TFMs: {(string.IsNullOrEmpty(available) ? "none" : available)}");
}
Expand All @@ -1427,7 +1455,7 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
if (!hasMatchingRid)
{
string available = string.Join(", ", availableRids);
_cmdletPassedIn.WriteWarning(
warningMsgs.Enqueue(
$"The specified RuntimeIdentifier '{_runtimeIdentifier}' was not found in this package. " +
$"No platform-specific assets were installed. Available RIDs: {(string.IsNullOrEmpty(available) ? "none" : available)}");
}
Expand All @@ -1454,7 +1482,7 @@ private bool TryExtractToDirectory(string zipPath, string extractPath, out Error
/// </summary>
/// <param name="archive">The zip archive to analyze.</param>
/// <returns>The best matching NuGetFramework, or null if no lib/ folders exist or no match is found.</returns>
private NuGetFramework GetBestLibFramework(ZipArchive archive)
private NuGetFramework GetBestLibFramework(ZipArchive archive, ConcurrentQueue<string> debugMsgs)
{
// Collect all TFMs from lib/ folder entries
var libFrameworks = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
Expand Down Expand Up @@ -1504,14 +1532,14 @@ private NuGetFramework GetBestLibFramework(ZipArchive archive)

if (bestMatch != null)
{
_cmdletPassedIn.WriteDebug($"Selected best matching TFM: {bestMatch.GetShortFolderName()} (from {string.Join(", ", libFrameworks)})");
debugMsgs.Enqueue($"Selected best matching TFM: {bestMatch.GetShortFolderName()} (from {string.Join(", ", libFrameworks)})");
}

return bestMatch;
}
catch (Exception e)
{
_cmdletPassedIn.WriteDebug($"TFM selection failed, extracting all lib/ folders: {e.Message}");
debugMsgs.Enqueue($"TFM selection failed, extracting all lib/ folders: {e.Message}");
return null;
}
}
Expand Down Expand Up @@ -1609,7 +1637,7 @@ private static NuGetFramework GetCurrentFramework()
/// .NET Framework assemblies (net472), those will be skipped — which means the module
/// won't work if later used on Windows PowerShell 5.1.
/// </summary>
private void WarnIfCrossLineageTfmSkipped(ZipArchive archive, NuGetFramework bestFramework)
private void WarnIfCrossLineageTfmSkipped(ZipArchive archive, NuGetFramework bestFramework, ConcurrentQueue<string> warningMsgs)
{
try
{
Expand Down Expand Up @@ -1653,7 +1681,7 @@ private void WarnIfCrossLineageTfmSkipped(ZipArchive archive, NuGetFramework bes
string skippedList = string.Join(", ", skippedLineageTfms);
string otherHost = bestIsNetCore ? "Windows PowerShell 5.1" : "PowerShell 7+";
string skippedTfm = skippedLineageTfms.First();
_cmdletPassedIn.WriteWarning(
warningMsgs.Enqueue(
$"This package contains assemblies for {skippedList} which were not installed because " +
$"the current runtime selected {bestFramework.GetShortFolderName()}. " +
$"If you also use this module on {otherHost}, run: Install-PSResource -Name <ModuleName> -TargetFramework '{skippedTfm}'");
Expand Down
Loading