Remove-OutdatedModule.ps1
function Remove-OutdatedModule { <# .SYNOPSIS Removes outdated module versions and keeps the most recent one .DESCRIPTION Over time you may be in a situation where multiple, oudated versins of the same module are installed on the machine, for example: Get-Module az.* -ListAvailable Directory: C:\Users\carlo\Documents\PowerShell\Modules ModuleType Version PreRelease Name PSEdition ExportedCommands ---------- ------- ---------- ---- --------- ---------------- Script 1.8.1 Az.Accounts Core,Desk {Disable-AzDataCollection, Disable-AzContextAutosave, Enable-AzDataCollection, Enable-AzContextAutosa… Script 1.8.0 Az.Accounts Core,Desk {Disable-AzDataCollection, Disable-AzContextAutosave, Enable-AzDataCollection, Enable-AzContextAutosa… Script 1.7.5 Az.Accounts Core,Desk {Disable-AzDataCollection, Disable-AzContextAutosave, Enable-AzDataCollection, Enable-AzContextAutosa… Script 1.1.1 Az.Advisor Core,Desk {Get-AzAdvisorRecommendation, Enable-AzAdvisorRecommendation, Disable-AzAdvisorRecommendation, Get-Az… Script 1.1.1 Az.Aks Core,Desk {Get-AzAks, New-AzAks, Remove-AzAks, Import-AzAksCredential…} Script 1.0.3 Az.Aks Core,Desk {Get-AzAks, New-AzAks, Remove-AzAks, Import-AzAksCredential…} Script 0.1.2 Az.AlertsManagement Core,Desk {Get-AzAlert, Get-AzAlertObjectHistory, Update-AzAlertState, Measure-AzAlertStatistic…} Script 0.1.1 Az.AlertsManagement Core,Desk {Get-AzAlert, Get-AzAlertObjectHistory, Update-AzAlertState, Measure-AzAlertStatistic…} Script 1.1.3 Az.AnalysisServices Core,Desk {Resume-AzAnalysisServicesServer, Suspend-AzAnalysisServicesServer, Get-AzAnalysisServicesServer, Rem… Script 1.1.2 Az.AnalysisServices Core,Desk {Resume-AzAnalysisServicesServer, Suspend-AzAnalysisServicesServer, Get-AzAnalysisServicesServer, Rem… Script 2.0.1 Az.ApiManagement Core,Desk {Add-AzApiManagementApiToProduct, Add-AzApiManagementProductToGroup, Add-AzApiManagementRegion, Add-A… Script 2.0.0 Az.ApiManagement Core,Desk {Add-AzApiManagementApiToProduct, Add-AzApiManagementProductToGroup, Add-AzApiManagementRegion, Add-A… Script 1.4.1 Az.ApiManagement Core,Desk {Add-AzApiManagementApiToProduct, Add-AzApiManagementProductToGroup, Add-AzApiManagementRegion, Add-A… Script 0.1.4 Az.AppConfiguration Core,Desk {Get-AzAppConfigurationStore, Get-AzAppConfigurationStoreKey, Get-AzAppConfigurationStoreKeyValue, Ne… Script 1.1.0 Az.ApplicationInsights Core,Desk {Get-AzApplicationInsights, New-AzApplicationInsights, Remove-AzApplicationInsights, Update-AzApplica… Script 1.0.3 Az.ApplicationInsights Core,Desk {Get-AzApplicationInsights, New-AzApplicationInsights, Remove-AzApplicationInsights, Set-AzApplicatio… Binary 1.0.1 Az.ApplicationMonitor Desk Script 0.1.7 Az.Attestation Core,Desk {New-AzAttestation, Get-AzAttestation, Remove-AzAttestation, Get-AzAttestationPolicy…} Script 1.3.6 Az.Automation Core,Desk {Get-AzAutomationHybridWorkerGroup, Remove-AzAutomationHybridWorkerGroup, Get-AzAutomationJobOutputRe… Script 3.0.0 Az.Batch Core,Desk {Remove-AzBatchAccount, Get-AzBatchAccount, Get-AzBatchAccountKey, New-AzBatchAccount…} Script 2.0.2 Az.Batch Core,Desk {Remove-AzBatchAccount, Get-AzBatchAccount, Get-AzBatchAccountKey, New-AzBatchAccount…} Script 1.0.3 Az.Billing Core,Desk {Get-AzBillingInvoice, Get-AzBillingPeriod, Get-AzEnrollmentAccount, Get-AzConsumptionBudget…} Script 1.0.2 Az.Billing Core,Desk {Get-AzBillingInvoice, Get-AzBillingPeriod, Get-AzEnrollmentAccount, Get-AzConsumptionBudget…} Script 0.2.13 Az.Blueprint Core,Desk {Get-AzBlueprint, Get-AzBlueprintAssignment, New-AzBlueprintAssignment, Remove-AzBlueprintAssignment…} Script 0.2.12 Az.Blueprint Core,Desk {Get-AzBlueprint, Get-AzBlueprintAssignment, New-AzBlueprintAssignment, Remove-AzBlueprintAssignment…} [...] Removing all those outdated modules can be tedious and time consuming, this function automates the task. By default the function uses 'Get-Module -ListAvailable' to retrieve the list of modules available in $env:PSModulePath, but you can specify a list of folders to scan instead of searching all available locations. .PARAMETER Folder Full path to the folder(s) you want to scan for old modules to clean-up .PARAMETER Name Module name(s) to scan for clean-up. .PARAMETER Force Suppresses the confirmation prompt when removing old module .EXAMPLE Remove-OutdatedModule -WhatIf What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.Accounts\1.7.5". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.Accounts\1.8.0". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.Aks\1.0.3". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.AlertsManagement\0.1.1". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.AnalysisServices\1.1.2". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.ApiManagement\1.4.1". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.ApiManagement\2.0.0". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.ApplicationInsights\1.0.3". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.Batch\2.0.2". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.Billing\1.0.2". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.Compute\3.7.0". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.DataFactory\1.7.0". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.DataFactory\1.8.0". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\PowerShell\Modules\Az.DataShare\0.1.2". .EXAMPLE Remove-OutdatedModule 'C:\Users\myuser\Documents\Powershell\Modules\Az.Blueprint','C:\Users\myuser\Documents\Powershell\Modules\Az.CognitiveServices' -WhatIf What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\Powershell\Modules\Az.Blueprint\0.2.12". What if: Performing the operation "Remove module" on target "C:\Users\myuser\Documents\Powershell\Modules\Az.CognitiveServices\1.3.0". .EXAMPLE Remove-OutdatedModule 'C:\Users\myuser\Documents\Powershell\Modules\Az.Blueprint','C:\Users\myuser\Documents\Powershell\Modules\Az.CognitiveServices' -Verbose -Force VERBOSE: Az.Blueprint, 0.2.12 VERBOSE: Az.CognitiveServices, 1.3.0 #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [parameter(Position = 0, ParameterSetName = 'folder')] [ValidateScript( { Test-Path $_ })] [string[]]$Folder, [parameter(Position = 0, ParameterSetName = 'modulename')] [Alias('ModuleName')] [string[]]$Name = "*", [parameter()] [switch]$Force ) if (! [string]::IsNullOrWhiteSpace($Folder)) { $groups = Get-ChildItem -Path $Folder -Recurse -Filter '*.psd1' | ForEach-Object { Test-ModuleManifest -Path $_.FullName -ErrorAction 'SilentlyContinue' -Verbose:$false } | Group-Object 'Name' | Where-Object 'Count' -GT 1 } else { $groups = Get-Module -ListAvailable -Name $Name -Verbose:$false | Where-Object Path -Like "$env:USERPROFILE*" | Group-Object 'Name' | Where-Object 'Count' -GT 1 } foreach ($group in $groups) { $versions = $null $versions = $group.Group | Sort-Object 'Version' | Select-Object -SkipLast 1 $versionToKeep = $null $versionToKeep = $group.Group | Sort-Object 'Version' | Select-Object -Last 1 foreach ($version in $versions) { # this is needed because a module in a subfolder could have been already deleted after its parent if (Test-Path -Path $version.Path) { Write-Verbose "$($version.Name), $($version.Version)" $moduleFolder = $null $moduleFolder = Split-Path $version.Path -Parent if ($Force -or ($PSCmdlet.ShouldProcess("$moduleFolder", "Remove module"))) { if ($Force -or ($PSCmdlet.ShouldContinue("Remove module?", "$moduleFolder"))) { # todo: workaround for https://github.com/PowerShell/PowerShell/issues/9246 (https://github.com/PowerShell/PowerShell/issues/11721) # todo: this does not address the problem if the path is a subfolder of a junction # bug: the function seems to erroneously remove preview modules, they have "-beta" appended to the version number; that likely breaks the [ModuleVersion] object Get-ChildItem -Path $moduleFolder -File -Recurse -Verbose:$false | ForEach-Object { Remove-Item $_ -Force -ErrorAction 'SilentlyContinue' -Verbose:$false } # note: for modules with subfolders I need to ignore the first error and run again the delete command, unless I want to have a recursive function Get-ChildItem -Path $moduleFolder -Recurse -Force -Verbose:$false | Sort-Object 'FullName' -Descending | ForEach-Object { Remove-Item -Path $_.FullName -Force -ErrorAction 'SilentlyContinue' -Verbose:$false } # finally, I can remove the module version root folder Remove-Item -Path $moduleFolder -Force } } } } } } |