From e83925000eb00d33963ac1404750d826277fcdcb Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Thu, 20 Jul 2023 21:14:37 -0700 Subject: [PATCH 1/7] Add dependency check logic for validating version --- src/code/UninstallPSResource.cs | 78 +++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/src/code/UninstallPSResource.cs b/src/code/UninstallPSResource.cs index 9bf85fa7f..9e0ed90a5 100644 --- a/src/code/UninstallPSResource.cs +++ b/src/code/UninstallPSResource.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Management.Automation; +using System.Collections; namespace Microsoft.PowerShell.PSResourceGet.Cmdlets { @@ -179,7 +180,7 @@ private bool UninstallPkgHelper(out List errRecords) { var successfullyUninstalled = false; GetHelper getHelper = new GetHelper(this); - List dirsToDelete = getHelper.FilterPkgPathsByName(Name, _pathsToSearch); + List dirsToDelete = getHelper.FilterPkgPathsByName(Name, _pathsToSearch); int totalDirs = dirsToDelete.Count; errRecords = new List(); @@ -268,17 +269,17 @@ private bool UninstallModuleHelper(string pkgPath, string pkgName, out ErrorReco { errRecord = null; var successfullyUninstalledPkg = false; + DirectoryInfo dir = new DirectoryInfo(pkgPath); + Uri uri = new Uri(dir.FullName); + string version = uri.Segments.Last(); // if -SkipDependencyCheck is not specified and the pkg is a dependency for another package, // an error will be written and we return false - if (!SkipDependencyCheck && CheckIfDependency(pkgName, out errRecord)) + if (!SkipDependencyCheck && CheckIfDependency(pkgName, version, out errRecord)) { return false; } - DirectoryInfo dir = new DirectoryInfo(pkgPath); - Uri uri = new Uri(dir.FullName); - string version = uri.Segments.Last(); if (!ShouldProcess(string.Format("Uninstall resource '{0}', version '{1}', from path '{2}'", pkgName, version, dir.FullName))) { WriteVerbose("ShouldProcess is set to false."); @@ -374,8 +375,9 @@ private bool UninstallScriptHelper(string pkgPath, string pkgName, out ErrorReco return successfullyUninstalledPkg; } - private bool CheckIfDependency(string pkgName, out ErrorRecord errorRecord) + private bool CheckIfDependency(string pkgName, string version, out ErrorRecord errorRecord) { + // Checking if a specific package version is a dependency anywhere // 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 @@ -389,24 +391,23 @@ private bool CheckIfDependency(string pkgName, out ErrorRecord errorRecord) } // _dependentModules is a collection of PSModuleInfo objects that contain a property listing module dependencies, "RequiredModules". - // 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. - HashSet uniquePackageNames = new(); + List parentPackages = new(); errorRecord = null; try { for (int i = 0; i < _dependentModules.Count; i++) { - var reqModules = _dependentModules[i].RequiredModules; + var reqModule = _dependentModules[i].RequiredModules; - for (int j = 0; j < reqModules.Count; j++) + for (int j = 0; j < reqModule.Count; j++) { - if (reqModules[j].Name.Equals(pkgName, StringComparison.InvariantCultureIgnoreCase)) + if (reqModule[j].Name.Equals(pkgName, StringComparison.InvariantCultureIgnoreCase)) { - uniquePackageNames.Add(_dependentModules[i].Name); + parentPackages.Add(_dependentModules[i]); } } } @@ -420,17 +421,58 @@ private bool CheckIfDependency(string pkgName, out ErrorRecord errorRecord) null); } - if (uniquePackageNames.Count > 0) + bool dependencyExists = false; + List parentsOfDependency = new List(); + Version systemVersion = null; + for (int i = 0; i < parentPackages.Count; i++) { - var strUniquePkgNames = string.Join(",", uniquePackageNames.ToArray()); + var parentPkg = parentPackages[i] as PSModuleInfo; + + for (int j = 0; j < parentPkg.RequiredModules.Count; j++) + { + var pkgToUninstall = parentPkg.RequiredModules[j] as PSModuleInfo; + if (string.Equals(pkgToUninstall.Name, pkgName, StringComparison.InvariantCultureIgnoreCase)) { + // then check verison + if (pkgToUninstall.Version == null) + { + // Any version works as a dependency, so only one version needs to be available. + _pwsh ??= System.Management.Automation.PowerShell.Create(); + _pwsh.Commands.Clear(); + + // Get all versions of the package to be uninstalled + Collection pkgVersions = _pwsh.AddCommand("Microsoft.PowerShell.Core\\Get-Module").AddParameters( + new Hashtable() { + { "Name", pkgName }, + { "ListAvailable", true } + }).Invoke(); + + if (pkgVersions.Count == 1) + { + parentsOfDependency.Add(parentPkg.Name); + dependencyExists = true; + } + } + else { + if (System.Version.TryParse(version, out systemVersion) && pkgToUninstall.Version.CompareTo(systemVersion) == 0) + { + // The required version OR module version is the version we're attempting to uninstall. + parentsOfDependency.Add(parentPkg.Name); + dependencyExists = true; + } + } + } + } + } + if (dependencyExists) + { + string versionMsg = systemVersion == null ? string.Empty : $"version '{systemVersion}' of "; errorRecord = new ErrorRecord( new PSInvalidOperationException( - $"Cannot uninstall '{pkgName}'. This package depends on the following package(s): '{strUniquePkgNames}'. If you would still like to uninstall, re-run the command with -SkipDependencyCheck."), - - "UninstallPSResourcePackageIsaDependency", + $"Cannot uninstall {versionMsg}'{pkgName}'. This package is a dependency for: '{string.Join(", ", parentsOfDependency)}'. If you would still like to uninstall, re-run the command with -SkipDependencyCheck."), + "UninstallPSResourcePackageIsADependency", ErrorCategory.InvalidOperation, - null); + this); return true; } From ab8abcc3fbc22e2f5989458c3dd4c87f8f34d497 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Thu, 20 Jul 2023 22:32:58 -0700 Subject: [PATCH 2/7] add test --- .../UninstallPSResource.Tests.ps1 | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 index 7231b734f..117a95345 100644 --- a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 +++ b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 @@ -257,7 +257,24 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { $pkg = Get-InstalledPSResource "RequiredModule1" $pkg | Should -Not -Be $null - $ev.FullyQualifiedErrorId | Should -BeExactly 'UninstallPSResourcePackageIsaDependency,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource' + $ev.FullyQualifiedErrorId | Should -BeExactly 'UninstallPSResourcePackageIsADependency,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource' + } + + It "Do not uninstall specific module version that is dependency for another module" { + $null = Install-PSResource "test_module" -Repository $PSGalleryName -TrustRepository -SkipDependencyCheck -Version "1.0.0.0" + $null = Install-PSResource "test_module" -Repository $PSGalleryName -TrustRepository -SkipDependencyCheck -Version "3.0.0.0" + $null = Install-PSResource "test_module" -Repository $PSGalleryName -TrustRepository -SkipDependencyCheck -Version "5.0.0.0" + $null = Install-PSResource "ModuleWithDependency" -Repository $PSGalleryName -TrustRepository -SkipDependencyCheck + + $pkg = Get-InstalledPSResource "test_module" + $pkg | Should -Not -Be $null + $pkg.Count | Should -BeGreaterOrEqual 3 + Uninstall-PSResource -Name "test_module" -Version "*" -ErrorVariable ev -ErrorAction SilentlyContinue + + $pkg = Get-InstalledPSResource "test_module" + $pkg | Should -Not -Be $null + $pkg.Count | Should -Be 1 + $ev.FullyQualifiedErrorId | Should -BeExactly 'UninstallPSResourcePackageIsADependency,Microsoft.PowerShell.PSResourceGet.Cmdlets.UninstallPSResource' } It "Uninstall module that is a dependency for another module using -SkipDependencyCheck" { From a8746bb2c2d67946a370bee65c0febac3312fdad Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:35:46 -0700 Subject: [PATCH 3/7] update tests --- test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 index 117a95345..f6b8cade6 100644 --- a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 +++ b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 @@ -22,6 +22,7 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { AfterEach { Uninstall-PSResource -Name $testModuleName -Version "*" -ErrorAction SilentlyContinue -SkipDependencyCheck + Uninstall-PSResource -Name "test_module" -Version "*" -ErrorAction SilentlyContinue -SkipDependencyCheck } AfterAll { @@ -250,7 +251,10 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { } It "Do not Uninstall module that is a dependency for another module" { - $null = Install-PSResource "test_module" -Repository $PSGalleryName -TrustRepository -WarningAction SilentlyContinue + Install-PSResource "test_module" -Repository $PSGalleryName -TrustRepository -Version "3.0.0" -Reinstall + + $pkg = Get-InstalledPSResource "RequiredModule1" + $pkg | Should -Not -Be $null Uninstall-PSResource -Name "RequiredModule1" -ErrorVariable ev -ErrorAction SilentlyContinue From 64977553c7b9b9a9c57f7e2217224319962b14d3 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Fri, 21 Jul 2023 23:45:23 -0700 Subject: [PATCH 4/7] add debugging to tests --- test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 index f6b8cade6..7c266e966 100644 --- a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 +++ b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 @@ -253,8 +253,12 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { It "Do not Uninstall module that is a dependency for another module" { Install-PSResource "test_module" -Repository $PSGalleryName -TrustRepository -Version "3.0.0" -Reinstall + $pkg = Get-InstalledPSResource "test_module" + write-host "$pkg" + $pkg = Get-InstalledPSResource "RequiredModule1" $pkg | Should -Not -Be $null + write-host "$pkg" Uninstall-PSResource -Name "RequiredModule1" -ErrorVariable ev -ErrorAction SilentlyContinue From 7d7844563e9f9490e73f2074901f30dc28c8b267 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Sat, 22 Jul 2023 00:02:39 -0700 Subject: [PATCH 5/7] update tests --- test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 index 7c266e966..88ad21084 100644 --- a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 +++ b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 @@ -254,11 +254,13 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { Install-PSResource "test_module" -Repository $PSGalleryName -TrustRepository -Version "3.0.0" -Reinstall $pkg = Get-InstalledPSResource "test_module" - write-host "$pkg" + write-host "$($pkg.Name)" + write-host "$($pkg.Version)" $pkg = Get-InstalledPSResource "RequiredModule1" $pkg | Should -Not -Be $null - write-host "$pkg" + write-host "$($pkg.Name)" + write-host "$($pkg.Version)" Uninstall-PSResource -Name "RequiredModule1" -ErrorVariable ev -ErrorAction SilentlyContinue From 7727a237766bcff2574c0b1ad026b94a42c6cba2 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Sat, 22 Jul 2023 00:44:24 -0700 Subject: [PATCH 6/7] add more debug statements --- .../UninstallPSResource.Tests.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 index 88ad21084..31aed9f97 100644 --- a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 +++ b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 @@ -33,7 +33,7 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { Uninstall-PSResource -name $testModuleName -SkipDependencyCheck Get-InstalledPSResource $testModuleName | Should -BeNullOrEmpty } - +<# $testCases = @{Name="Test?Module"; ErrorId="ErrorFilteringNamesForUnsupportedWildcards"}, @{Name="Test[Module"; ErrorId="ErrorFilteringNamesForUnsupportedWildcards"} @@ -212,7 +212,7 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { $stableVersionPkgs = $res | Where-Object {$_.IsPrerelease -ne $true} $stableVersionPkgs.Count | Should -Be 2 } - +#> It "uninstall all prerelease versions (which satisfy the range) when -Version range and -Prerelease parameter is specified" { Uninstall-PSResource -Name $testModuleName -Version "*" -SkipDependencyCheck Install-PSResource -Name $testModuleName -Version "2.5.0-beta" -Repository $PSGalleryName -TrustRepository @@ -262,7 +262,9 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { write-host "$($pkg.Name)" write-host "$($pkg.Version)" - Uninstall-PSResource -Name "RequiredModule1" -ErrorVariable ev -ErrorAction SilentlyContinue + Uninstall-PSResource -Name "RequiredModule1" -ErrorVariable ev -ErrorAction SilentlyContinue -Verbose + + write-host $ev $pkg = Get-InstalledPSResource "RequiredModule1" $pkg | Should -Not -Be $null @@ -279,7 +281,11 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { $pkg = Get-InstalledPSResource "test_module" $pkg | Should -Not -Be $null $pkg.Count | Should -BeGreaterOrEqual 3 - Uninstall-PSResource -Name "test_module" -Version "*" -ErrorVariable ev -ErrorAction SilentlyContinue + + write-host "************* LOOOK HERE************************************" + write-host "$($pkg.Count)" + + Uninstall-PSResource -Name "test_module" -Version "*" -Verbose $pkg = Get-InstalledPSResource "test_module" $pkg | Should -Not -Be $null From 0501b871b13860bd0c0e8c8ee9b7f13f2a1467a2 Mon Sep 17 00:00:00 2001 From: Anam Navied Date: Tue, 25 Jul 2023 13:58:41 -0400 Subject: [PATCH 7/7] fix typo --- test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 index 31aed9f97..146020660 100644 --- a/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 +++ b/test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1 @@ -282,7 +282,7 @@ Describe 'Test Uninstall-PSResource for Modules' -tags 'CI' { $pkg | Should -Not -Be $null $pkg.Count | Should -BeGreaterOrEqual 3 - write-host "************* LOOOK HERE************************************" + write-host "************* LOOK HERE************************************" write-host "$($pkg.Count)" Uninstall-PSResource -Name "test_module" -Version "*" -Verbose