Skip to content
Closed
Show file tree
Hide file tree
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
78 changes: 60 additions & 18 deletions src/code/UninstallPSResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Collections;

namespace Microsoft.PowerShell.PSResourceGet.Cmdlets
{
Expand Down Expand Up @@ -179,7 +180,7 @@ private bool UninstallPkgHelper(out List<ErrorRecord> errRecords)
{
var successfullyUninstalled = false;
GetHelper getHelper = new GetHelper(this);
List<string> dirsToDelete = getHelper.FilterPkgPathsByName(Name, _pathsToSearch);
List<string> dirsToDelete = getHelper.FilterPkgPathsByName(Name, _pathsToSearch);
int totalDirs = dirsToDelete.Count;
errRecords = new List<ErrorRecord>();

Expand Down Expand Up @@ -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.");
Expand Down Expand Up @@ -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
Expand All @@ -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<string> uniquePackageNames = new();
List<PSModuleInfo> 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]);
}
}
}
Expand All @@ -420,17 +421,58 @@ private bool CheckIfDependency(string pkgName, out ErrorRecord errorRecord)
null);
}

if (uniquePackageNames.Count > 0)
bool dependencyExists = false;
List<string> parentsOfDependency = new List<string>();
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<PSModuleInfo> pkgVersions = _pwsh.AddCommand("Microsoft.PowerShell.Core\\Get-Module").AddParameters(
new Hashtable() {
{ "Name", pkgName },
{ "ListAvailable", true }
}).Invoke<PSModuleInfo>();

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;
}
Expand Down
43 changes: 38 additions & 5 deletions test/UninstallPSResourceTests/UninstallPSResource.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -32,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"}

Expand Down Expand Up @@ -211,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
Expand Down Expand Up @@ -250,14 +251,46 @@ 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 "test_module"
write-host "$($pkg.Name)"
write-host "$($pkg.Version)"

$pkg = Get-InstalledPSResource "RequiredModule1"
$pkg | Should -Not -Be $null
write-host "$($pkg.Name)"
write-host "$($pkg.Version)"

Uninstall-PSResource -Name "RequiredModule1" -ErrorVariable ev -ErrorAction SilentlyContinue -Verbose

Uninstall-PSResource -Name "RequiredModule1" -ErrorVariable ev -ErrorAction SilentlyContinue
write-host $ev

$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

write-host "************* LOOK HERE************************************"
write-host "$($pkg.Count)"

Uninstall-PSResource -Name "test_module" -Version "*" -Verbose

$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" {
Expand Down