Updating your PowerShell modules to the latest version plus cleaning up older versions

You installed a PowerShell module on your machine and used it for a long time, and suddenly… It no longer connects, shows warnings that some commands are deprecated, and now what?! In this blog post, I will show you how to update all your modules to the latest version and remove unused older versions.

Requirements

The script should be able to update all modules to the latest version, but it should also update them to the latest prerelease version if specified. It should also show the current version and the new version when updated. And to keep things nice and tidy, it should also remove any previous versions.

Update PowerShellget module

The script uses functions from a newer Powershellget module (Version 2.2.5). The built-in/default version of Powershellget 1.0.0.1 doesn’t have these. To update to the more recent version, you can follow these steps:

Open a PowerShell prompt (v5 or v7) as Administrator and run these lines below one at a time (It could prompt Y/N for installing the NuGet provider):

Install-Module PowerShellGet -AllowClobber -Force
Remove-Module Powershellget
Remove-Module PackageManagement
Import-Module Powershellget -MinimumVersion 2.2.5

Note: Close this PowerShell session you used to update PowerShellget before running the script below.

Running the script

When running the script with no parameter, it will update all modules to the latest production version and remove older versions, if any.  It retrieves the information of 63 updates at a time until all modules are checked for more recent versions at the start of the script. (Limit of PSGallery searching using Find-Module)

If available, the -AllowPreRelease parameter will update all modules to the latest Prerelease version and the latest Production version if not. The -Whatif parameter will only check and output to screen if it would have updated or uninstalled a module.

The -Name parameter can be used to update only specific modules, either by name or enclosed by wildcard (*). For example, running Update-Modules -Name *dsc* will update all modules with DSC at any position in the name.

Note: The script requires running it as an Administrator.
Warning: Updating all your modules to the PreRelease version could break things for you!

The output of the script when starting the Update-Modules function:

The output of the script when removing older versions:

Example output stating the updated modules at the end of the script:

It will show an error in red if the module can’t be updated. It will also report if modules can’t be updated because they are in use:

or

Note: If you have a lot of modules… This could take a while 🙂

The script

Below is the script. Save it to a location (c:\scripts, for example) and add it to your PowerShell profile by:

notepad $profile
add ". c:\scripts\Update-modules.ps1"
quit/save and start new PowerShell session and run Update-Modules

If you don’t add it to your PowerShell profile, you can run it by:

. .\update-modules.ps1
update-modules

Thanks, ScrambledBrain, for updating the script through a pull request on GitHub twice. 🙂

function Update-Modules {
	param (
		[switch]$AllowPrerelease,
		[string]$Name = '*',
		[switch]$WhatIf
	)
	
	# Test admin privileges without using -Requires RunAsAdministrator,
	# which causes a nasty error message, if trying to load the function within a PS profile but without admin privileges
	if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator")) {
		Write-Warning ("Function {0} needs admin privileges. Break now." -f $MyInvocation.MyCommand)
		return
	}

	# Get all installed modules
	Write-Host ("Retrieving all installed modules ...") -ForegroundColor Green
	$CurrentModules = Get-InstalledModule -Name $Name -ErrorAction SilentlyContinue | Select-Object Name, Version | Sort-Object Name

	if (-not $CurrentModules) {
		Write-Host ("No modules found.") -ForegroundColor Gray
		return
	}
	else {
		$ModulesCount = $CurrentModules.Name.Count
		$DigitsLength = $ModulesCount.ToString().Length
		Write-Host ("{0} modules found." -f $ModulesCount) -ForegroundColor Gray
	}

	# Show status of AllowPrerelease Switch
	''
	if ($AllowPrerelease) {
		Write-Host ("Updating installed modules to the latest PreRelease version ...") -ForegroundColor Green
	}
	else {
		Write-Host ("Updating installed modules to the latest Production version ...") -ForegroundColor Green
	}

	#Retrieve current versions of modules (63 at a time because of PSGallery limit) if $InstalledModules is greater than 0
	if ($CurrentModules.Count -gt 0) {
		$startnumber = 0
		$endnumber = 62
		$onlineversions = $null
		while ($CurrentModules.Count -gt $onlineversions.Count) {
			Write-Host ("Checking online versions for installed modules [{0}..{1}/{2}]" -f $startnumber, $endnumber, $CurrentModules.Count) -ForegroundColor Green
			$currentversions = Find-Module -Name $CurrentModules.name[$startnumber..$endnumber]
			$startnumber = $startnumber + 63
			$endnumber = $endnumber + 63
			$onlineversions = $onlineversions + $currentversions
		}
	}
	else {
		Write-Warning ("No modules were found to check for updates, please check your NameFilter. Exiting...")
		return
	}

	# Loop through the installed modules and update them if a newer version is available
	$i = 0
	foreach ($Module in $CurrentModules) {
		$i++
		$Counter = ("[{0,$DigitsLength}/{1,$DigitsLength}]" -f $i, $ModulesCount)
		$CounterLength = $Counter.Length
		Write-Host ('{0} Checking for updated version of module {1} ...' -f $Counter, $Module.Name) -ForegroundColor Green
		try {
			$latest = $onlineversions | Where-Object name -eq $module.Name -ErrorAction Stop
			if ([version]$Module.Version -lt [version]$latest.version) {
				Update-Module -Name $Module.Name -AllowPrerelease:$AllowPrerelease -AcceptLicense -Scope:AllUsers -Force:$True -ErrorAction Stop -WhatIf:$WhatIf.IsPresent
			}
		}
		catch {
			Write-Host ("{0$CounterLength} Error updating module {1}!" -f ' ', $Module.Name) -ForegroundColor Red
		}

		# Retrieve newest version number and remove old(er) version(s) if any
		$AllVersions = Get-InstalledModule -Name $Module.Name -AllVersions | Sort-Object PublishedDate -Descending
		$MostRecentVersion = $AllVersions[0].Version
		if ($AllVersions.Count -gt 1 ) {
			Foreach ($Version in $AllVersions) {
				if ($Version.Version -ne $MostRecentVersion) {
					try {
						Write-Host ("{0,$CounterLength} Uninstalling previous version {1} of module {2} ..." -f ' ', $Version.Version, $Module.Name) -ForegroundColor Gray
						Uninstall-Module -Name $Module.Name -RequiredVersion $Version.Version -Force:$True -ErrorAction Stop -AllowPrerelease -WhatIf:$WhatIf.IsPresent
					}
					catch {
						Write-Warning ("{0,$CounterLength} Error uninstalling previous version {1} of module {2}!" -f ' ', $Version.Version, $Module.Name)
					}
				}
			}
		}
	}

	# Get the new module versions for comparing them to to previous one if updated
	$NewModules = Get-InstalledModule -Name $Name | Select-Object Name, Version | Sort-Object Name
	if ($NewModules) {
		''
		Write-Host ("List of updated modules:") -ForegroundColor Green
		$NoUpdatesFound = $true
		foreach ($Module in $NewModules) {
			$CurrentVersion = $CurrentModules | Where-Object Name -EQ $Module.Name
			if ($CurrentVersion.Version -notlike $Module.Version) {
				$NoUpdatesFound = $false
				Write-Host ("- Updated module {0} from version {1} to {2}" -f $Module.Name, $CurrentVersion.Version, $Module.Version) -ForegroundColor Green
			}
		}

		if ($NoUpdatesFound) {
			Write-Host ("No modules were updated.") -ForegroundColor Gray
		}
	}
}

Download the script(s) from GitHub here.

24 thoughts on “Updating your PowerShell modules to the latest version plus cleaning up older versions

  1. Your methodology is different than others that I have seen where it uses Get-InstalledModule. It is more elegant than others I have seen. However, it will not update modules that are native to PowerShell that have not previously been updated. For example, PSDesiredStateConfiguration.

    The PSDesiredStateConfiguration brings up other issues between PowerShell 5x and 7x. In PowerShell 5, the Find-Module lists a version not compatible with this version of PowerShell. Attempting to install this version does not produce any errors and has an end result in no change.

    • It also doesn’t update modules when they are not installed using install-module. It’s not perfect, I could expand it to try and catch these exceptions too in the future

  2. Thank you for this script!. However, I’m getting the following error for each of my 23 modules and none have been updated:

    Updating installed modules to the latest Production version …
    [ 1/23] Checking for updated version of module AzureAD …

    Error formatting a string: Index (zero based) must be greater than or equal to zero and less than the size of the
    argument list..
    At C:\temp\Update-PSModules.ps1:67 char:13
    + Write-Host (“{0$CounterLength} Error updating module {1}! …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: ({07} Error updating module {1}!:String) [], RuntimeException
    + FullyQualifiedErrorId : FormatError

    • This is because you have an older Powershellget module that misses some functions. I have added a chapter to this blog post (Update PowerShellget module) and hope that helps. I could reproduce this error on a fresh Windows Sandbox session. (Sometimes, I forget that I constantly update my modules, and some things just don’t work on older versions, my bad!)

      • As an addition to this I had to manually delete the old version from three places:
        %USERPROFILE%\Documents\WindowsPowerShell\Modules\PowerShellGet\
        C:\Program Files\WindowsPowerShell\Modules\PowerShellGet
        C:\Program Files (x86)\WindowsPowerShell\Modules\PowerShellGet

        Once I’d done that the script no longer threw the error message mentioned above.

  3. Hi,
    having troubles to run the script.
    I´m Execution Policy is Remote Signed, latest PowerShellGet installed like descripted but if I start update-modules.ps1 it just jumps to a new row and nothing happened.
    Any ideas?
    Regards,

  4. Hi,
    if I run the .\update-modules nothing happened. PS just jumps to a new row.
    No error or something.
    I had installed PowerShellGet as descripted.
    Only thing I did not do was the $Porfile part.
    Regards,

    • If you run .\update-modules.ps1, then it makes the function available in your session. You can run update-modules afterwards. If you do the $profile part, then it’s always available as update-modules in every session.

      • Thank you for reply.
        If I run update-modules after .\update-modules
        I get the following:
        update-modules : The term ‘update-modules’ is not recognized as the name of a cmdlet, function, script file, or
        operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try
        again.
        At line:1 char:1
        + update-modules
        + ~~~~~~~~~~~~~~
        + CategoryInfo : ObjectNotFound: (update-modules:String) [], CommandNotFoundException
        + FullyQualifiedErrorId : CommandNotFoundException

        Suggestion [3,General]: The command update-modules was not found, but does exist in the current location. Windows PowerShell does not load commands from the current location by default. If you trust this command, instead type: “.\update-modules”. See “get-help about_Command_Precedence” for more details.

        Thanks, Philip

      • I think I see what’s going on there, my bad for not mentioning it correctly! You have to run “. .\update-modules.ps1″ to import it into your session. (So that’s a period, space, period.\update-modules.ps1” in the ps1 is in the current directory. Afterwards you can run update-modules

  5. ok WOW!
    Thanks you. did not know this with “period-space-period”
    now its running!
    Next for me is to figure out how to add my path to my currently empty $Profile 🙂

    Thanks!

  6. Pingback: PowerShell is fun :)PowerShell Profile

  7. Pingback: The latest technology news week 32 2023 - ivobeerens.nl

  8. Pingback: PowerShell is fun :)Check for PowerShell module updates

  9. Pingback: The latest technology news Week 40-2023 - ivobeerens.nl

  10. Pingback: The latest technology news Week 40-2023

  11. This script solved my AutomatedLab issues and it simply removed module files from the prior version of AutomatedLab. I googled “powershell update module remove old version” and your site was the first offer.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.