From 7baa6a321de2602796fec66600a3187ece676422 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Thu, 18 Nov 2021 12:59:00 -0800 Subject: [PATCH 1/3] Add -SkipDependencyCheck to install/save/update cmdlets --- src/code/InstallHelper.cs | 13 ++++++++++--- src/code/InstallPSResource.cs | 9 ++++++++- src/code/SavePSResource.cs | 10 +++++++++- src/code/UpdatePSResource.cs | 10 +++++++++- test/InstallPSResource.Tests.ps1 | 17 ++++++++++++++--- test/SavePSResource.Tests.ps1 | 7 +++++++ 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/code/InstallHelper.cs b/src/code/InstallHelper.cs index a08011eff..c68ce4529 100644 --- a/src/code/InstallHelper.cs +++ b/src/code/InstallHelper.cs @@ -79,6 +79,7 @@ public void InstallPackages( string specifiedPath, bool asNupkg, bool includeXML, + bool skipDependencyCheck, List pathsToInstallPkg) { _cmdletPassedIn.WriteVerbose(string.Format("Parameters passed in >>> Name: '{0}'; Version: '{1}'; Prerelease: '{2}'; Repository: '{3}'; " + @@ -123,7 +124,12 @@ public void InstallPackages( } // Go through the repositories and see which is the first repository to have the pkg version available - ProcessRepositories(names, repository, _trustRepository, _credential); + ProcessRepositories( + packageNames: names, + repository: repository, + trustRepository: _trustRepository, + credential: _credential, + skipDependencyCheck: skipDependencyCheck); } #endregion @@ -135,7 +141,8 @@ private void ProcessRepositories( string[] packageNames, string[] repository, bool trustRepository, - PSCredential credential) + PSCredential credential, + bool skipDependencyCheck) { var listOfRepositories = RepositorySettings.Read(repository, out string[] _); List pckgNamesToInstall = packageNames.ToList(); @@ -185,7 +192,7 @@ private void ProcessRepositories( tag: null, repository: new string[] { repoName }, credential: credential, - includeDependencies: true); + includeDependencies: !skipDependencyCheck); if (!pkgsFromRepoToInstall.Any()) { diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index d08c42efd..71a529901 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -15,7 +15,6 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets /// The Install-PSResource cmdlet installs a resource. /// It returns nothing. /// - [Cmdlet(VerbsLifecycle.Install, "PSResource", DefaultParameterSetName = "NameParameterSet", SupportsShouldProcess = true)] public sealed class InstallPSResource : PSCmdlet @@ -106,6 +105,13 @@ class InstallPSResource : PSCmdlet [ValidateNotNullOrEmpty] public PSResourceInfo InputObject { get; set; } + /// + /// Skips the check for resource dependencies, so that only found resources are installed, + /// and not any resources the found resource depends on. + /// + [Parameter] + public SwitchParameter SkipDependencyCheck { get; set; } + #endregion #region Members @@ -250,6 +256,7 @@ private void ProcessInstallHelper(string[] pkgNames, bool pkgPrerelease, string[ specifiedPath: null, asNupkg: false, includeXML: true, + skipDependencyCheck: SkipDependencyCheck, pathsToInstallPkg: _pathsToInstallPkg); } diff --git a/src/code/SavePSResource.cs b/src/code/SavePSResource.cs index 64e9e98bc..d2b31d9c0 100644 --- a/src/code/SavePSResource.cs +++ b/src/code/SavePSResource.cs @@ -123,6 +123,13 @@ public string Path [ValidateNotNullOrEmpty] public PSResourceInfo InputObject { get; set; } + /// + /// Skips the check for resource dependencies, so that only found resources are saved, + /// and not any resources the found resource depends on. + /// + [Parameter] + public SwitchParameter SkipDependencyCheck { get; set; } + #endregion #region Method overrides @@ -243,7 +250,8 @@ private void ProcessSaveHelper(string[] pkgNames, bool pkgPrerelease, string[] p noClobber: false, specifiedPath: _path, asNupkg: false, - includeXML: false, + includeXML: false, + skipDependencyCheck: SkipDependencyCheck, pathsToInstallPkg: new List { _path } ); } diff --git a/src/code/UpdatePSResource.cs b/src/code/UpdatePSResource.cs index 86b811888..ba4bec2b6 100644 --- a/src/code/UpdatePSResource.cs +++ b/src/code/UpdatePSResource.cs @@ -97,6 +97,13 @@ public sealed class UpdatePSResource : PSCmdlet [Parameter] public SwitchParameter Force { get; set; } + /// + /// Skips the check for resource dependencies, so that only found resources are updated, + /// and not any resources the found resource depends on. + /// + [Parameter] + public SwitchParameter SkipDependencyCheck { get; set; } + #endregion #region Override Methods @@ -166,6 +173,7 @@ protected override void ProcessRecord() specifiedPath: null, asNupkg: false, includeXML: true, + skipDependencyCheck: SkipDependencyCheck, pathsToInstallPkg: _pathsToInstallPkg); } @@ -253,7 +261,7 @@ private string[] ProcessPackageNames( tag: null, repository: Repository, credential: Credential, - includeDependencies: false)) + includeDependencies: !SkipDependencyCheck)) { if (!repositoryPackages.ContainsKey(foundResource.Name)) { diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index 45d12d99f..949c814b7 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -103,14 +103,14 @@ Describe 'Test Install-PSResource for Module' { } It "Install resource when given Name, Version '*', should install the latest version" { - $pkg = Install-PSResource -Name "TestModule" -Version "*" -Repository $TestGalleryName + Install-PSResource -Name "TestModule" -Version "*" -Repository $TestGalleryName $pkg = Get-Module "TestModule" -ListAvailable $pkg.Name | Should -Be "TestModule" $pkg.Version | Should -Be "1.3.0" } It "Install resource with latest (including prerelease) version given Prerelease parameter" { - $pkg = Install-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName + Install-PSResource -Name "TestModulePrerelease" -Prerelease -Repository $TestGalleryName $pkg = Get-Module "TestModulePrerelease" -ListAvailable $pkg.Name | Should -Be "TestModulePrerelease" $pkg.Version | Should -Be "0.0.1" @@ -118,7 +118,7 @@ Describe 'Test Install-PSResource for Module' { } It "Install a module with a dependency" { - $pkg = Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName + Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName $pkg = Get-Module "PSGetTestModule" -ListAvailable $pkg.Name | Should -Be "PSGetTestModule" $pkg.Version | Should -Be "2.0.2" @@ -129,6 +129,17 @@ Describe 'Test Install-PSResource for Module' { $pkg.Version | Should -Be "1.0.0" } + It "Install a module with a dependency and skip installing the dependency" { + Install-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName -SkipDependencyCheck + $pkg = Get-Module "PSGetTestModule" -ListAvailable + $pkg.Name | Should -Be "PSGetTestModule" + $pkg.Version | Should -Be "2.0.2" + $pkg.PrivateData.PSData.Prerelease | Should -Be "-alpha1" + + $pkg = Get-Module "PSGetTestDependency1" -ListAvailable + $pkg | Should -BeNullOrEmpty + } + It "Install resource via InputObject by piping from Find-PSresource" { Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Install-PSResource $pkg = Get-Module "TestModule" -ListAvailable diff --git a/test/SavePSResource.Tests.ps1 b/test/SavePSResource.Tests.ps1 index 910597f7e..59b1f7d43 100644 --- a/test/SavePSResource.Tests.ps1 +++ b/test/SavePSResource.Tests.ps1 @@ -129,6 +129,13 @@ Describe 'Test Save-PSResource for PSResources' { (Get-ChildItem $pkgDirs[1].FullName).Count | Should -Be 1 } + It "Save a module with a dependency and skip saving the dependency" { + Save-PSResource -Name "PSGetTestModule" -Prerelease -Repository $TestGalleryName -Path $SaveDir -SkipDependencyCheck + $pkgDirs = Get-ChildItem -Path $SaveDir | Where-Object { $_.Name -eq "PSGetTestModule" -or $_.Name -eq "PSGetTestDependency1" } + $pkgDirs.Count | Should -Be 1 + (Get-ChildItem $pkgDirs[0].FullName).Count | Should -Be 1 + } + It "Save resource via InputObject by piping from Find-PSresource" { Find-PSResource -Name "TestModule" -Repository $TestGalleryName | Save-PSResource -Path $SaveDir $pkgDir = Get-ChildItem -Path $SaveDir | Where-Object Name -eq "TestModule" From 40596fbddae9bf50be0be84cfa5452c77bb71ba1 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Thu, 18 Nov 2021 13:40:44 -0800 Subject: [PATCH 2/3] Change -Force to -SkipDependencyCheck --- src/code/UninstallPSResource.cs | 36 +++++++++++++++++------------- test/UninstallPSResource.Tests.ps1 | 29 ++++++++++++------------ 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/code/UninstallPSResource.cs b/src/code/UninstallPSResource.cs index 4c2be49d9..05aa4e886 100644 --- a/src/code/UninstallPSResource.cs +++ b/src/code/UninstallPSResource.cs @@ -43,10 +43,10 @@ public sealed class UninstallPSResource : PSCmdlet public PSResourceInfo InputObject { get; set; } /// + /// Skips check to see if other resources are dependent on the resource being uninstalled. /// - [Parameter(ParameterSetName = NameParameterSet)] - [Parameter(ParameterSetName = InputObjectParameterSet)] - public SwitchParameter Force { get; set; } + [Parameter] + public SwitchParameter SkipDependencyCheck { get; set; } #endregion @@ -212,9 +212,9 @@ private bool UninstallModuleHelper(string pkgPath, string pkgName, out ErrorReco errRecord = null; var successfullyUninstalledPkg = false; - // if -Force is not specified and the pkg is a dependency for another package, + // if -SkipDependencyCheck is not specified and the pkg is a dependency for another package, // an error will be written and we return false - if (!Force && CheckIfDependency(pkgName, out errRecord)) + if (!SkipDependencyCheck && CheckIfDependency(pkgName, out errRecord)) { return false; } @@ -301,9 +301,8 @@ private bool UninstallScriptHelper(string pkgPath, string pkgName, out ErrorReco return successfullyUninstalledPkg; } - private bool CheckIfDependency(string pkgName, out ErrorRecord errRecord) + private bool CheckIfDependency(string pkgName, out ErrorRecord errorRecord) { - errRecord = null; // this is a primitive implementation // TODO: implement a dependencies database for querying dependency info // cannot uninstall a module if another module is dependent on it @@ -317,18 +316,21 @@ private bool CheckIfDependency(string pkgName, out ErrorRecord errRecord) // RequiredModules is collection of PSModuleInfo objects that need to be iterated through to see if any of them are the pkg we're trying to uninstall // If we anything from the final call gets returned, there is a dependency on this pkg. IEnumerable pkgsWithRequiredModules = new List(); + errorRecord = null; try { pkgsWithRequiredModules = results.Where( - p => ((ReadOnlyCollection)p.Properties["RequiredModules"].Value).Where( + pkg => ((ReadOnlyCollection)pkg.Properties["RequiredModules"].Value).Where( rm => rm.Name.Equals(pkgName, StringComparison.InvariantCultureIgnoreCase)).Any()); } catch (Exception e) { - var exMessage = String.Format("Error checking if resource is a dependency: {0}. If you would still like to uninstall, rerun the command with -Force", e.Message); - var ex = new ArgumentException(exMessage); - var DependencyCheckError = new ErrorRecord(ex, "DependencyCheckError", ErrorCategory.OperationStopped, null); - errRecord = DependencyCheckError; + errorRecord = new ErrorRecord( + new PSInvalidOperationException( + $"Error checking if resource is a dependency: {e.Message}."), + "UninstallPSResourceDependencyCheckError", + ErrorCategory.InvalidOperation, + null); } if (pkgsWithRequiredModules.Any()) @@ -336,10 +338,12 @@ private bool CheckIfDependency(string pkgName, out ErrorRecord errRecord) var uniquePkgNames = pkgsWithRequiredModules.Select(p => p.Properties["Name"].Value).Distinct().ToArray(); var strUniquePkgNames = string.Join(",", uniquePkgNames); - var exMessage = String.Format("Cannot uninstall '{0}', the following package(s) take a dependency on this package: {1}. If you would still like to uninstall, rerun the command with -Force", pkgName, strUniquePkgNames); - var ex = new ArgumentException(exMessage); - var PackageIsaDependency = new ErrorRecord(ex, "PackageIsaDependency", ErrorCategory.OperationStopped, null); - errRecord = PackageIsaDependency; + errorRecord = new ErrorRecord( + new PSInvalidOperationException( + $"Cannot uninstall '{pkgName}'. The following package(s) take a dependency on this package: {strUniquePkgNames}. If you would still like to uninstall, rerun the command with -SkipDependencyCheck"), + "UninstallPSResourcePackageIsaDependency", + ErrorCategory.InvalidOperation, + null); return true; } diff --git a/test/UninstallPSResource.Tests.ps1 b/test/UninstallPSResource.Tests.ps1 index c5b23a9c7..94acd9947 100644 --- a/test/UninstallPSResource.Tests.ps1 +++ b/test/UninstallPSResource.Tests.ps1 @@ -11,7 +11,7 @@ Describe 'Test Uninstall-PSResource for Modules' { $testModuleName = "test_module" $testScriptName = "test_script" Get-NewPSResourceRepositoryFile - $res = Uninstall-PSResource -name ContosoServer -Version "*" + Uninstall-PSResource -name ContosoServer -Version "*" } BeforeEach{ $null = Install-PSResource ContosoServer -Repository $TestGalleryName -TrustRepository -WarningAction SilentlyContinue @@ -21,7 +21,7 @@ Describe 'Test Uninstall-PSResource for Modules' { } It "Uninstall a specific module by name" { - $res = Uninstall-PSResource -name ContosoServer + Uninstall-PSResource -name ContosoServer Get-Module ContosoServer -ListAvailable | Should -Be $null } @@ -38,20 +38,20 @@ Describe 'Test Uninstall-PSResource for Modules' { It "Uninstall a list of modules by name" { $null = Install-PSResource BaseTestPackage -Repository $TestGalleryName -TrustRepository -WarningAction SilentlyContinue - $res = Uninstall-PSResource -Name BaseTestPackage, ContosoServer + Uninstall-PSResource -Name BaseTestPackage, ContosoServer Get-Module ContosoServer, BaseTestPackage -ListAvailable | Should -be $null } It "Uninstall a specific script by name" { $null = Install-PSResource Test-RPC -Repository $TestGalleryName -TrustRepository -WarningAction SilentlyContinue - $pkg = Uninstall-PSResource -name Test-RPC + Uninstall-PSResource -name Test-RPC } It "Uninstall a list of scripts by name" { $null = Install-PSResource adsql, airoute -Repository $TestGalleryName -TrustRepository -WarningAction SilentlyContinue - $pkg = Uninstall-PSResource -Name adsql, airoute + Uninstall-PSResource -Name adsql, airoute } It "Uninstall a module when given name and specifying all versions" { @@ -59,7 +59,7 @@ Describe 'Test Uninstall-PSResource for Modules' { $null = Install-PSResource ContosoServer -Repository $TestGalleryName -Version "1.5.0" -TrustRepository -WarningAction SilentlyContinue $null = Install-PSResource ContosoServer -Repository $TestGalleryName -Version "2.0.0" -TrustRepository -WarningAction SilentlyContinue - $res = Uninstall-PSResource -Name ContosoServer -version "*" + Uninstall-PSResource -Name ContosoServer -version "*" $pkgs = Get-Module ContosoServer -ListAvailable $pkgs.Version | Should -Not -Contain "1.0.0" $pkgs.Version | Should -Not -Contain "1.5.0" @@ -72,7 +72,7 @@ Describe 'Test Uninstall-PSResource for Modules' { $null = Install-PSResource ContosoServer -Repository $TestGalleryName -Version "1.5.0" -TrustRepository -WarningAction SilentlyContinue $null = Install-PSResource ContosoServer -Repository $TestGalleryName -Version "2.0.0" -TrustRepository -WarningAction SilentlyContinue - $res = Uninstall-PSResource -Name ContosoServer + Uninstall-PSResource -Name ContosoServer $pkgs = Get-Module ContosoServer -ListAvailable $pkgs.Version | Should -Not -Contain "1.0.0" $pkgs.Version | Should -Not -Contain "1.5.0" @@ -85,7 +85,7 @@ Describe 'Test Uninstall-PSResource for Modules' { $null = Install-PSResource ContosoServer -Repository $TestGalleryName -Version "1.5.0" -TrustRepository -WarningAction SilentlyContinue $null = Install-PSResource ContosoServer -Repository $TestGalleryName -Version "2.0.0" -TrustRepository -WarningAction SilentlyContinue - $res = Uninstall-PSResource -Name "ContosoServer" -Version "1.0.0" + Uninstall-PSResource -Name "ContosoServer" -Version "1.0.0" $pkgs = Get-Module ContosoServer -ListAvailable $pkgs.Version | Should -Not -Contain "1.0.0" } @@ -106,7 +106,7 @@ Describe 'Test Uninstall-PSResource for Modules' { $null = Install-PSResource ContosoServer -Repository $TestGalleryName -Version "1.5.0" -TrustRepository -WarningAction SilentlyContinue $null = Install-PSResource ContosoServer -Repository $TestGalleryName -Version "2.0.0" -TrustRepository -WarningAction SilentlyContinue - $res = Uninstall-PSResource -Name ContosoServer -Version $Version + Uninstall-PSResource -Name ContosoServer -Version $Version $pkgs = Get-Module ContosoServer -ListAvailable $pkgs.Version | Should -Not -Contain $Version } @@ -169,8 +169,7 @@ Describe 'Test Uninstall-PSResource for Modules' { } It "Uninstall module using -WhatIf, should not uninstall the module" { - $res = Uninstall-PSResource -Name "ContosoServer" -WhatIf - + Uninstall-PSResource -Name "ContosoServer" -WhatIf $pkg = Get-Module ContosoServer -ListAvailable $pkg.Version | Should -Be "2.5" } @@ -183,16 +182,16 @@ Describe 'Test Uninstall-PSResource for Modules' { $pkg = Get-Module "RequiredModule1" -ListAvailable $pkg | Should -Not -Be $null - $ev | Should -Be "Cannot uninstall 'RequiredModule1', the following package(s) take a dependency on this package: test_module. If you would still like to uninstall, rerun the command with -Force" + $ev.FullyQualifiedErrorId | Should -BeExactly 'UninstallPSResourcePackageIsaDependency,Microsoft.PowerShell.PowerShellGet.Cmdlets.UninstallPSResource' } - It "Uninstall module that is a dependency for another module using -Force" { + It "Uninstall module that is a dependency for another module using -SkipDependencyCheck" { $null = Install-PSResource "test_module" -Repository $TestGalleryName -TrustRepository -WarningAction SilentlyContinue - $res = Uninstall-PSResource -Name "RequiredModule1" -Force + Uninstall-PSResource -Name "RequiredModule1" -SkipDependencyCheck $pkg = Get-Module "RequiredModule1" -ListAvailable - $pkg | Should -Be $null + $pkg | Should -BeNullOrEmpty } It "Uninstall PSResourceInfo object piped in" { From f91bc60e5d9e8e980d4975fe4f11a54f34b4d986 Mon Sep 17 00:00:00 2001 From: Paul Higinbotham Date: Thu, 18 Nov 2021 14:10:09 -0800 Subject: [PATCH 3/3] Fix tests --- test/InstallPSResource.Tests.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index 949c814b7..5f9298991 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -17,7 +17,7 @@ Describe 'Test Install-PSResource for Module' { AfterEach { Uninstall-PSResource "TestModule", "TestModule99", "myTestModule", "myTestModule2", "testModulePrerelease", "testModuleWithlicense","PSGetTestModule", "PSGetTestDependency1", "TestFindModule","ClobberTestModule1", - "ClobberTestModule2" -Force -ErrorAction SilentlyContinue + "ClobberTestModule2" -SkipDependencyCheck -ErrorAction SilentlyContinue } AfterAll { @@ -320,7 +320,7 @@ Describe 'Test Install-PSResource for interactive and root user scenarios' { } AfterEach { - Uninstall-PSResource "TestModule", "testModuleWithlicense" -Force -ErrorAction SilentlyContinue + Uninstall-PSResource "TestModule", "testModuleWithlicense" -SkipDependencyCheck -ErrorAction SilentlyContinue } AfterAll {