From 96838a791439ae18a8a7774fa915a7fc913a03b6 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Fri, 29 Oct 2021 11:33:02 -0700 Subject: [PATCH 1/2] Fix for always reinstall issue and general clean up --- LICENSE | 4 +- doBuild.ps1 | 57 +++---- src/PowerShellGet.psd1 | 2 +- src/code/FindHelper.cs | 41 +++-- src/code/GetHelper.cs | 13 +- src/code/GetInstalledPSResource.cs | 8 +- src/code/InstallHelper.cs | 241 +++++++++++++++-------------- src/code/InstallPSResource.cs | 3 +- src/code/PowerShellGet.csproj | 6 +- src/code/SavePSResource.cs | 5 +- src/code/UpdatePSResource.cs | 22 +-- 11 files changed, 201 insertions(+), 201 deletions(-) diff --git a/LICENSE b/LICENSE index 4448db3d5..4c3581d3b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -MIT License +Copyright (c) Microsoft Corporation. -Copyright (c) 2020 PowerShell Team +MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/doBuild.ps1 b/doBuild.ps1 index a024b035d..119f8d9a8 100644 --- a/doBuild.ps1 +++ b/doBuild.ps1 @@ -36,6 +36,14 @@ function DoBuild Write-Verbose -Verbose -Message "Copying help files to '$BuildOutPath'" Copy-Item -Path "${HelpPath}/${Culture}" -Dest "$BuildOutPath" -Recurse -Force + # Copy license + Write-Verbose -Verbose -Message "Copying LICENSE file to '$BuildOutPath'" + Copy-Item -Path "./LICENSE" -Dest "$BuildOutPath" + + # Copy notice + # Write-Verbose -Verbose -Message "Copying ThirdPartyNotices.txt to '$BuildOutPath'" + # Copy-Item -Path "./ThirdPartyNotices.txt" -Dest "$BuildOutPath" + # # Copy DSC resources # TODO: This should not be part of PowerShellGet build/publish and should be moved to its own project @@ -65,40 +73,21 @@ function DoBuild } # Place build results - if ($BuildFramework -eq "netstandard2.0") { - $assemblyNames = @( - 'PowerShellGet' - 'Microsoft.Extensions.Logging.Abstractions' - 'MoreLinq' - 'NuGet.Commands' - 'NuGet.Common' - 'NuGet.Configuration' - 'NuGet.Frameworks' - 'NuGet.Packaging' - 'NuGet.ProjectModel' - 'NuGet.Protocol' - 'NuGet.Repositories' - 'NuGet.Versioning' - 'Newtonsoft.Json' - ) - } elseif ($BuildFramework -eq 'net472') { - $assemblyNames = @( - 'PowerShellGet' - 'Microsoft.Extensions.Logging.Abstractions' - 'MoreLinq' - 'NuGet.Commands' - 'NuGet.Common' - 'NuGet.Configuration' - 'NuGet.Frameworks' - 'NuGet.Packaging' - 'NuGet.ProjectModel' - 'NuGet.Protocol' - 'NuGet.Repositories' - 'NuGet.Versioning' - 'Newtonsoft.Json' - 'System.Security.Principal.Windows' - ) - } + $assemblyNames = @( + 'PowerShellGet' + 'Microsoft.Extensions.Logging.Abstractions' + 'MoreLinq' + 'NuGet.Commands' + 'NuGet.Common' + 'NuGet.Configuration' + 'NuGet.Frameworks' + 'NuGet.Packaging' + 'NuGet.ProjectModel' + 'NuGet.Protocol' + 'NuGet.Repositories' + 'NuGet.Versioning' + 'Newtonsoft.Json' + ) $buildSuccess = $true foreach ($fileName in $assemblyNames) diff --git a/src/PowerShellGet.psd1 b/src/PowerShellGet.psd1 index 6b4bcc2a6..90ef61a52 100644 --- a/src/PowerShellGet.psd1 +++ b/src/PowerShellGet.psd1 @@ -3,7 +3,7 @@ @{ RootModule = './netstandard2.0/PowerShellGet.dll' - ModuleVersion = '3.0.11' + ModuleVersion = '3.0.12' GUID = '1d73a601-4a6c-43c5-ba3f-619b18bbb404' Author = 'Microsoft Corporation' CompanyName = 'Microsoft Corporation' diff --git a/src/code/FindHelper.cs b/src/code/FindHelper.cs index 8ed8db1cc..143590345 100644 --- a/src/code/FindHelper.cs +++ b/src/code/FindHelper.cs @@ -1,23 +1,23 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using MoreLinq.Extensions; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; using System; using System.Collections.Generic; using System.Data; -using Dbg = System.Diagnostics.Debug; using System.Linq; using System.Management.Automation; using System.Net; using System.Net.Http; using System.Threading; -using MoreLinq.Extensions; -using Microsoft.PowerShell.PowerShellGet.UtilClasses; -using static Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo; -using NuGet.Common; -using NuGet.Configuration; -using NuGet.Protocol; -using NuGet.Protocol.Core.Types; -using NuGet.Versioning; + +using Dbg = System.Diagnostics.Debug; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -26,6 +26,8 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets /// internal class FindHelper { + #region Members + private CancellationToken _cancellationToken; private readonly PSCmdlet _cmdletPassedIn; private List _pkgsLeftToFind; @@ -50,12 +52,22 @@ internal class FindHelper private const int SearchAsyncMaxReturned = 5990; private const int GalleryMax = 12000; + #endregion + + #region Constructor + + private FindHelper() { } + public FindHelper(CancellationToken cancellationToken, PSCmdlet cmdletPassedIn) { _cancellationToken = cancellationToken; _cmdletPassedIn = cmdletPassedIn; } + #endregion + + #region Public methods + public IEnumerable FindByResourceName( string[] name, ResourceType type, @@ -175,7 +187,11 @@ public IEnumerable FindByResourceName( } } - public IEnumerable SearchFromRepository( + #endregion + + #region Private methods + + private IEnumerable SearchFromRepository( string repositoryName, Uri repositoryUrl) { @@ -255,7 +271,7 @@ public IEnumerable SearchFromRepository( } } - public IEnumerable SearchAcrossNamesInRepository( + private IEnumerable SearchAcrossNamesInRepository( string repositoryName, PackageSearchResource pkgSearchResource, PackageMetadataResource pkgMetadataResource, @@ -637,5 +653,6 @@ SourceCacheContext sourceCacheContext } } } + #endregion } -} \ No newline at end of file +} diff --git a/src/code/GetHelper.cs b/src/code/GetHelper.cs index 18cd5ab29..213569cf5 100644 --- a/src/code/GetHelper.cs +++ b/src/code/GetHelper.cs @@ -1,16 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; using System; using System.Collections.Generic; -using Dbg = System.Diagnostics.Debug; using System.IO; -using System.Linq; using System.Management.Automation; -using System.Threading; -using MoreLinq.Extensions; -using Microsoft.PowerShell.PowerShellGet.UtilClasses; + +using Dbg = System.Diagnostics.Debug; using static Microsoft.PowerShell.PowerShellGet.UtilClasses.PSResourceInfo; -using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -38,7 +37,7 @@ public GetHelper(PSCmdlet cmdletPassedIn) #region Public methods - public IEnumerable FilterPkgPaths( + public IEnumerable GetPackagesFromPath( string[] name, VersionRange versionRange, List pathsToSearch) diff --git a/src/code/GetInstalledPSResource.cs b/src/code/GetInstalledPSResource.cs index dfcd7202b..612940dc1 100644 --- a/src/code/GetInstalledPSResource.cs +++ b/src/code/GetInstalledPSResource.cs @@ -1,13 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; using System; using System.Collections.Generic; -using System.IO; using System.Management.Automation; -using System.Threading; -using Microsoft.PowerShell.PowerShellGet.UtilClasses; -using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -130,7 +128,7 @@ protected override void ProcessRecord() } GetHelper getHelper = new GetHelper(this); - foreach (PSResourceInfo pkg in getHelper.FilterPkgPaths(namesToSearch, _versionRange, _pathsToSearch)) + foreach (PSResourceInfo pkg in getHelper.GetPackagesFromPath(namesToSearch, _versionRange, _pathsToSearch)) { WriteObject(pkg); } diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index ed5d89a47..f49e30686 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -1,5 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using MoreLinq.Extensions; +using NuGet.Common; +using NuGet.Configuration; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Packaging.PackageExtraction; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; using System; using System.Collections; using System.Collections.Generic; @@ -10,16 +21,6 @@ using System.Net; using System.Text.RegularExpressions; using System.Threading; -using MoreLinq.Extensions; -using NuGet.Common; -using NuGet.Configuration; -using NuGet.Packaging; -using NuGet.Packaging.Core; -using NuGet.Packaging.PackageExtraction; -using NuGet.Protocol; -using NuGet.Protocol.Core.Types; -using NuGet.Versioning; -using Microsoft.PowerShell.PowerShellGet.UtilClasses; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -28,33 +29,37 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets /// internal class InstallHelper : PSCmdlet { + #region Members + + private const string MsgRepositoryNotTrusted = "Untrusted repository"; + private const string MsgInstallUntrustedPackage = "You are installing the modules from an untrusted repository. If you trust this repository, change its Trusted value by running the Set-PSResourceRepository cmdlet. Are you sure you want to install the PSresource from '{0}' ?"; + private CancellationToken _cancellationToken; - private readonly bool _updatePkg; private readonly bool _savePkg; private readonly PSCmdlet _cmdletPassedIn; - List _pathsToInstallPkg; - VersionRange _versionRange; - bool _prerelease; - bool _acceptLicense; - bool _quiet; - bool _reinstall; - bool _force; - bool _trustRepository; - bool _noClobber; - PSCredential _credential; - string _specifiedPath; - bool _asNupkg; - bool _includeXML; - - public InstallHelper(bool updatePkg, bool savePkg, PSCmdlet cmdletPassedIn) + private List _pathsToInstallPkg; + private VersionRange _versionRange; + private bool _prerelease; + private bool _acceptLicense; + private bool _quiet; + private bool _reinstall; + private bool _force; + private bool _trustRepository; + private PSCredential _credential; + private string _specifiedPath; + private bool _asNupkg; + private bool _includeXML; + + #endregion + + #region Public methods + + public InstallHelper(bool savePkg, PSCmdlet cmdletPassedIn) { - // Define the cancellation token. CancellationTokenSource source = new CancellationTokenSource(); - _cancellationToken = source.Token; - - this._updatePkg = updatePkg; - this._savePkg = savePkg; - this._cmdletPassedIn = cmdletPassedIn; + _cancellationToken = source.Token; + _savePkg = savePkg; + _cmdletPassedIn = cmdletPassedIn; } public void InstallPackages( @@ -67,7 +72,6 @@ public void InstallPackages( bool reinstall, bool force, bool trustRepository, - bool noClobber, PSCredential credential, string requiredResourceFile, string requiredResourceJson, @@ -78,7 +82,7 @@ public void InstallPackages( List pathsToInstallPkg) { _cmdletPassedIn.WriteVerbose(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " + - "AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}'; NoClobber: '{8}';", + "AcceptLicense: '{4}'; Quiet: '{5}'; Reinstall: '{6}'; TrustRepository: '{7}';", string.Join(",", names), (versionRange != null ? versionRange.OriginalString : string.Empty), prerelease.ToString(), @@ -86,8 +90,7 @@ public void InstallPackages( acceptLicense.ToString(), quiet.ToString(), reinstall.ToString(), - trustRepository.ToString(), - noClobber.ToString())); + trustRepository.ToString())); _versionRange = versionRange; _prerelease = prerelease; @@ -96,7 +99,6 @@ public void InstallPackages( _reinstall = reinstall; _force = force; _trustRepository = trustRepository; - _noClobber = noClobber; _credential = credential; _specifiedPath = specifiedPath; _asNupkg = asNupkg; @@ -107,27 +109,30 @@ public void InstallPackages( ProcessRepositories(names, repository, _trustRepository, _credential); } + #endregion + + #region Private methods + // This method calls iterates through repositories (by priority order) to search for the pkgs to install - public void ProcessRepositories(string[] packageNames, string[] repository, bool trustRepository, PSCredential credential) + private void ProcessRepositories(string[] packageNames, string[] repository, bool trustRepository, PSCredential credential) { var listOfRepositories = RepositorySettings.Read(repository, out string[] _); - List packagesToInstall = packageNames.ToList(); + List pckgNamesToInstall = packageNames.ToList(); var yesToAll = false; var noToAll = false; - var repositoryIsNotTrusted = "Untrusted repository"; - var queryInstallUntrustedPackage = "You are installing the modules from an untrusted repository. If you trust this repository, change its Trusted value by running the Set-PSResourceRepository cmdlet. Are you sure you want to install the PSresource from '{0}' ?"; + var findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn); foreach (var repo in listOfRepositories) { // If no more packages to install, then return - if (!packagesToInstall.Any()) return; + if (!pckgNamesToInstall.Any()) return; - var sourceTrusted = false; string repoName = repo.Name; _cmdletPassedIn.WriteVerbose(string.Format("Attempting to search for packages in '{0}'", repoName)); // Source is only trusted if it's set at the repository level to be trusted, -TrustRepository flag is true, -Force flag is true // OR the user issues trust interactively via console. + var sourceTrusted = true; if (repo.Trusted == false && !trustRepository && !_force) { _cmdletPassedIn.WriteVerbose("Checking if untrusted repository should be used"); @@ -135,89 +140,74 @@ public void ProcessRepositories(string[] packageNames, string[] repository, bool if (!(yesToAll || noToAll)) { // Prompt for installation of package from untrusted repository - var message = string.Format(CultureInfo.InvariantCulture, queryInstallUntrustedPackage, repoName); - sourceTrusted = _cmdletPassedIn.ShouldContinue(message, repositoryIsNotTrusted, true, ref yesToAll, ref noToAll); + var message = string.Format(CultureInfo.InvariantCulture, MsgInstallUntrustedPackage, repoName); + sourceTrusted = _cmdletPassedIn.ShouldContinue(message, MsgRepositoryNotTrusted, true, ref yesToAll, ref noToAll); } } - else - { - sourceTrusted = true; - } - if (sourceTrusted || yesToAll) + if (!sourceTrusted && !yesToAll) { - _cmdletPassedIn.WriteVerbose("Untrusted repository accepted as trusted source."); + continue; + } - // If it can't find the pkg in one repository, it'll look for it in the next repo in the list - var isLocalRepo = repo.Url.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase); + _cmdletPassedIn.WriteVerbose("Untrusted repository accepted as trusted source."); + // If it can't find the pkg in one repository, it'll look for it in the next repo in the list + var isLocalRepo = repo.Url.AbsoluteUri.StartsWith(Uri.UriSchemeFile + Uri.SchemeDelimiter, StringComparison.OrdinalIgnoreCase); - var findHelper = new FindHelper(_cancellationToken, _cmdletPassedIn); - // Finds parent packages and dependencies - IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( - name: packageNames, - type: ResourceType.None, - version: _versionRange != null ? _versionRange.OriginalString : null, - prerelease: _prerelease, - tag: null, - repository: new string[] { repoName }, - credential: credential, - includeDependencies: true); + // Finds parent packages and dependencies + IEnumerable pkgsFromRepoToInstall = findHelper.FindByResourceName( + name: packageNames, + type: ResourceType.None, + version: _versionRange != null ? _versionRange.OriginalString : null, + prerelease: _prerelease, + tag: null, + repository: new string[] { repoName }, + credential: credential, + includeDependencies: true); - foreach (PSResourceInfo a in pkgsFromRepoToInstall) - { - var test = a; - _cmdletPassedIn.WriteVerbose(a.Version.ToString()); - } - - // Select the first package from each name group, which is guaranteed to be the latest version. - // We should only have one version returned for each package name - // e.g.: - // PackageA (version 1.0) - // PackageB (version 2.0) - // PackageC (version 1.0) - pkgsFromRepoToInstall = pkgsFromRepoToInstall.GroupBy( - m => new { m.Name }).Select( - group => group.First()).ToList(); - - if (!pkgsFromRepoToInstall.Any()) - { - _cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); - // Check in the next repository - continue; - } + if (!pkgsFromRepoToInstall.Any()) + { + _cmdletPassedIn.WriteVerbose(string.Format("None of the specified resources were found in the '{0}' repository.", repoName)); + // Check in the next repository + continue; + } - // Check to see if the pkgs (including dependencies) are already installed (ie the pkg is installed and the version satisfies the version range provided via param) - if (!_reinstall) - { - // Removes all of the names that are already installed from the list of names to search for - pkgsFromRepoToInstall = FilterByInstalledPkgs(pkgsFromRepoToInstall); - } + // Select the first package from each name group, which is guaranteed to be the latest version. + // We should only have one version returned for each package name + // e.g.: + // PackageA (version 1.0) + // PackageB (version 2.0) + // PackageC (version 1.0) + pkgsFromRepoToInstall = pkgsFromRepoToInstall.GroupBy( + m => new { m.Name }).Select( + group => group.First()).ToList(); + + // Check to see if the pkgs (including dependencies) are already installed (ie the pkg is installed and the version satisfies the version range provided via param) + if (!_reinstall) + { + // Removes all of the names that are already installed from the list of names to search for + pkgsFromRepoToInstall = FilterByInstalledPkgs(pkgsFromRepoToInstall); + } - if (!pkgsFromRepoToInstall.Any()) - { - continue; - } + if (!pkgsFromRepoToInstall.Any()) + { + continue; + } - List pkgsInstalled = InstallPackage(pkgsFromRepoToInstall, repoName, repo.Url.AbsoluteUri, credential, isLocalRepo); + List pkgsInstalled = InstallPackage(pkgsFromRepoToInstall, repoName, repo.Url.AbsoluteUri, credential, isLocalRepo); - foreach (string name in pkgsInstalled) - { - packagesToInstall.Remove(name); - } + foreach (string name in pkgsInstalled) + { + pckgNamesToInstall.Remove(name); } } } // Check if any of the pkg versions are already installed, if they are we'll remove them from the list of packages to install - public IEnumerable FilterByInstalledPkgs(IEnumerable packagesToInstall) + private IEnumerable FilterByInstalledPkgs(IEnumerable packages) { - List pkgNames = new List(); - foreach (var pkg in packagesToInstall) - { - pkgNames.Add(pkg.Name); - } - + // Create list of installation paths to search. List _pathsToSearch = new List(); GetHelper getHelper = new GetHelper(_cmdletPassedIn); // _pathsToInstallPkg will only contain the paths specified within the -Scope param (if applicable) @@ -232,21 +222,34 @@ public IEnumerable FilterByInstalledPkgs(IEnumerable pkgsAlreadyInstalled = getHelper.FilterPkgPaths(pkgNames.ToArray(), _versionRange, _pathsToSearch); + var filteredPackages = new Dictionary(); + foreach (var pkg in packages) + { + filteredPackages.Add(pkg.Name, pkg); + } - // If any pkg versions are already installed, write a message saying it is already installed and continue processing other pkg names - if (pkgsAlreadyInstalled.Any()) + // Get currently installed packages. + IEnumerable pkgsAlreadyInstalled = getHelper.GetPackagesFromPath( + name: filteredPackages.Keys.ToArray(), + versionRange: _versionRange, + pathsToSearch: _pathsToSearch); + if (!pkgsAlreadyInstalled.Any()) { - foreach (PSResourceInfo pkg in pkgsAlreadyInstalled) - { - _cmdletPassedIn.WriteWarning(string.Format("Resource '{0}' with version '{1}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter", pkg.Name, pkg.Version)); + return packages; + } - // remove this pkg from the list of pkg names install - packagesToInstall.ToList().Remove(pkg); - } + // Remove from list package versions that are already installed. + foreach (PSResourceInfo pkg in pkgsAlreadyInstalled) + { + _cmdletPassedIn.WriteWarning( + string.Format("Resource '{0}' with version '{1}' is already installed. If you would like to reinstall, please run the cmdlet again with the -Reinstall parameter", + pkg.Name, + pkg.Version)); + + filteredPackages.Remove(pkg.Name); } - return packagesToInstall; + return filteredPackages.Values.ToArray(); } private List InstallPackage(IEnumerable pkgsToInstall, string repoName, string repoUrl, PSCredential credential, bool isLocalRepo) @@ -705,5 +708,7 @@ private void MoveFilesIntoInstallPath( Utils.MoveFiles(scriptPath, Path.Combine(finalModuleVersionDir, p.Name + ".ps1")); } } + + #endregion } } diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 42be06c52..cc2021d92 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -125,7 +125,7 @@ protected override void BeginProcessing() protected override void ProcessRecord() { - var installHelper = new InstallHelper(updatePkg: false, savePkg: false, cmdletPassedIn: this); + var installHelper = new InstallHelper(savePkg: false, cmdletPassedIn: this); switch (ParameterSetName) { case NameParameterSet: @@ -235,7 +235,6 @@ private void ProcessInstallHelper(InstallHelper installHelper, string[] pkgNames reinstall: Reinstall, force: false, trustRepository: TrustRepository, - noClobber: false, credential: Credential, requiredResourceFile: null, requiredResourceJson: null, diff --git a/src/code/PowerShellGet.csproj b/src/code/PowerShellGet.csproj index 2dfd10cbe..9452ccff4 100644 --- a/src/code/PowerShellGet.csproj +++ b/src/code/PowerShellGet.csproj @@ -5,9 +5,9 @@ Library PowerShellGet PowerShellGet - 3.0.11.0 - 3.0.11 - 3.0.11 + 3.0.12.0 + 3.0.12 + 3.0.12 netstandard2.0 8.0 diff --git a/src/code/SavePSResource.cs b/src/code/SavePSResource.cs index be5180150..dd8bf7d84 100644 --- a/src/code/SavePSResource.cs +++ b/src/code/SavePSResource.cs @@ -140,7 +140,7 @@ protected override void BeginProcessing() protected override void ProcessRecord() { - var installHelper = new InstallHelper(updatePkg: false, savePkg: true, cmdletPassedIn: this); + var installHelper = new InstallHelper(savePkg: true, cmdletPassedIn: this); switch (ParameterSetName) { case NameParameterSet: @@ -234,8 +234,7 @@ private void ProcessSaveHelper(InstallHelper installHelper, string[] pkgNames, b quiet: true, reinstall: true, force: false, - trustRepository: TrustRepository, - noClobber: false, + trustRepository: TrustRepository, credential: Credential, requiredResourceFile: null, requiredResourceJson: null, diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 0cf0a8fe5..fe2eb92a4 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -1,16 +1,12 @@ -using System.Collections.Specialized; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; using System; using System.Collections.Generic; -using Dbg = System.Diagnostics.Debug; using System.Linq; using System.Management.Automation; -using System.Text; -using System.Threading; -using Microsoft.PowerShell.PowerShellGet.UtilClasses; -using NuGet.Versioning; namespace Microsoft.PowerShell.PowerShellGet.Cmdlets { @@ -103,9 +99,9 @@ public sealed class UpdatePSResource : PSCmdlet #region Override Methods protected override void BeginProcessing() - { - // Create a respository story (the PSResourceRepository.xml file) if it does not already exist - // This is to create a better experience for those who have just installed v3 and want to get up and running quickly + { + // Create a respository story (the PSResourceRepository.xml file) if it does not already exist + // This is to create a better experience for those who have just installed v3 and want to get up and running quickly RepositorySettings.CheckRepositoryStore(); _pathsToInstallPkg = Utils.GetAllInstallationPaths(this, Scope); @@ -144,7 +140,6 @@ protected override void ProcessRecord() } InstallHelper installHelper = new InstallHelper( - updatePkg: true, savePkg: false, cmdletPassedIn: this); @@ -155,10 +150,9 @@ protected override void ProcessRecord() repository: Repository, acceptLicense: AcceptLicense, quiet: Quiet, - reinstall: false, + reinstall: true, force: Force, trustRepository: TrustRepository, - noClobber: false, credential: Credential, requiredResourceFile: null, requiredResourceJson: null, @@ -211,7 +205,7 @@ private string[] ProcessPackageNameWildCards(string[] namesToProcess, VersionRan GetHelper getHelper = new GetHelper( cmdletPassedIn: this); - namesToProcess = getHelper.FilterPkgPaths( + namesToProcess = getHelper.GetPackagesFromPath( name: namesToProcess, versionRange: versionRange, pathsToSearch: Utils.GetAllResourcePaths(this)).Select(p => p.Name).ToArray(); @@ -221,4 +215,4 @@ private string[] ProcessPackageNameWildCards(string[] namesToProcess, VersionRan } #endregion } -} \ No newline at end of file +} From 53aec2fae9cf6cd526f4576dd90b1ff86d10171a Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Mon, 1 Nov 2021 15:03:50 -0700 Subject: [PATCH 2/2] Added restore option for reinstall, in case the install fails --- src/code/InstallHelper.cs | 18 +++++--- src/code/Utils.cs | 70 ++++++++++++++++++++++++++++++++ test/InstallPSResource.Tests.ps1 | 26 ++++++++++++ 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index f49e30686..7edd67461 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -442,7 +442,14 @@ private List InstallPackage(IEnumerable pkgsToInstall, s } catch (Exception e) { - _cmdletPassedIn.WriteVerbose(string.Format("Unable to successfully install package '{0}': '{1}'", p.Name, e.Message)); + _cmdletPassedIn.WriteError( + new ErrorRecord( + new PSInvalidOperationException( + message: $"Unable to successfully install package '{p.Name}': '{e.Message}'", + innerException: e), + "InstallPackageFailed", + ErrorCategory.InvalidOperation, + _cmdletPassedIn)); } finally { @@ -668,12 +675,13 @@ private void MoveFilesIntoInstallPath( { _cmdletPassedIn.WriteVerbose(string.Format("Temporary module version directory is: '{0}'", tempModuleVersionDir)); - // At this point if if (Directory.Exists(finalModuleVersionDir)) { - // Delete the directory path before replacing it with the new module - _cmdletPassedIn.WriteVerbose(string.Format("Attempting to delete '{0}'", finalModuleVersionDir)); - Directory.Delete(finalModuleVersionDir, true); + // Delete the directory path before replacing it with the new module. + // If deletion fails (usually due to binary file in use), then attempt restore so that the currently + // installed module is not corrupted. + _cmdletPassedIn.WriteVerbose(string.Format("Attempting to delete with restore on failure.'{0}'", finalModuleVersionDir)); + Utils.DeleteDirectoryWithRestore(finalModuleVersionDir); } _cmdletPassedIn.WriteVerbose(string.Format("Attempting to move '{0}' to '{1}'", tempModuleVersionDir, finalModuleVersionDir)); diff --git a/src/code/Utils.cs b/src/code/Utils.cs index aaca16bbc..ecfbc62b0 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -457,6 +457,51 @@ public static void WriteVerboseOnCmdlet( #region Directory and File + /// + /// Deletes a directory and its contents. + /// Attempts to restore the directory and contents if deletion fails. + /// + public static void DeleteDirectoryWithRestore(string dirPath) + { + string tempDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + + try + { + // Create temporary directory for restore operation if needed. + CopyDirContents(dirPath, tempDirPath, overwrite: true); + + try + { + DeleteDirectory(dirPath); + } + catch (Exception ex) + { + // Delete failed. Attempt to restore the saved directory content. + try + { + RestoreDirContents(tempDirPath, dirPath); + } + catch (Exception exx) + { + throw new PSInvalidOperationException( + $"Cannot remove package path {dirPath}. An attempt to restore the old package has failed with error: {exx.Message}", + ex); + } + + throw new PSInvalidOperationException( + $"Cannot remove package path {dirPath}. The previous package contents have been restored.", + ex); + } + } + finally + { + if (Directory.Exists(tempDirPath)) + { + DeleteDirectory(tempDirPath); + } + } + } + /// /// Deletes a directory and its contents /// This is a workaround for .NET Directory.Delete(), which can fail with WindowsPowerShell @@ -535,6 +580,31 @@ private static void CopyDirContents( } } + private static void RestoreDirContents( + string sourceDirPath, + string destDirPath) + { + if (!Directory.Exists(destDirPath)) + { + Directory.CreateDirectory(destDirPath); + } + + foreach (string filePath in Directory.GetFiles(sourceDirPath)) + { + string destFilePath = Path.Combine(destDirPath, Path.GetFileName(filePath)); + if (!File.Exists(destFilePath)) + { + File.Copy(filePath, destFilePath); + } + } + + foreach (string srcSubDirPath in Directory.GetDirectories(sourceDirPath)) + { + string destSubDirPath = Path.Combine(destDirPath, Path.GetFileName(srcSubDirPath)); + RestoreDirContents(srcSubDirPath, destSubDirPath); + } + } + #endregion } } diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index fd8be4ba2..8762290a9 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -196,6 +196,32 @@ Describe 'Test Install-PSResource for Module' { $pkg.Version | Should -Be "1.3.0" } + It "Restore resource after reinstall fails" { + Install-PSResource -Name "TestModule" -Repository $TestGalleryName + $pkg = Get-Module "TestModule" -ListAvailable + $pkg.Name | Should -Be "TestModule" + $pkg.Version | Should -Be "1.3.0" + + $resourcePath = Split-Path -Path $pkg.Path -Parent + $resourceFiles = Get-ChildItem -Path $resourcePath -Recurse + + # Lock resource file to prevent reinstall from succeeding. + $fs = [System.IO.File]::Open($resourceFiles[0].FullName, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) + try + { + # Reinstall of resource should fail with one of its files locked. + Install-PSResource -Name "TestModule" -Repository $TestGalleryName -Reinstall -ErrorVariable ev -ErrorAction Silent + $ev.FullyQualifiedErrorId | Should -BeExactly 'InstallPackageFailed,Microsoft.PowerShell.PowerShellGet.Cmdlets.InstallPSResource' + } + finally + { + $fs.Close() + } + + # Verify that resource module has been restored. + (Get-ChildItem -Path $resourcePath -Recurse).Count | Should -BeExactly $resourceFiles.Count + } + It "Install resource that requires accept license with -AcceptLicense flag" { Install-PSResource -Name "testModuleWithlicense" -Repository $TestGalleryName -AcceptLicense $pkg = Get-InstalledPSResource "testModuleWithlicense"