Public/Support/Helper/Update-InstalledModule.ps1
# Module: TeamsFunctions # Function: ModuleManagement # Author: Barbara Forbes, David Eberhardt # Updated: 17-SEP 2022 # Status: Live function Update-InstalledModule { <# .SYNOPSIS Updates and reloads modules from a Repository. .DESCRIPTION Updates all local modules that originated from a Repository (PowerShellGallery by default) .PARAMETER Name List of modules to update .PARAMETER Exclude List of modules to exclude from updating. .PARAMETER AllowPrerelease Updates to latest Version including PreReleases (if found) for every Module discovered .PARAMETER SkipMajorVersion Skip major version updates to account for breaking changes. .PARAMETER Scope String. CurrentUser or AllUsers. Default is CurrentUser. .PARAMETER Repository String. If not provided, targets the PowerShell gallery (PSGallery) EXPERIMENTAL. Untested behaviour may occur for custom repositories (Credential Parameter is not parsed, etc.) Please use "Get-InstalledModule | Where Repository -eq 'MyRepo' | Update-Module" as an alternative .PARAMETER Credential PsCredential. If provided, attaches the Credential on calls to Find-Module and Update-Module respectively. EXPERIMENTAL. Functionality is untested (Additional Parameters of *Module-CmdLets are not provided/parsed) .EXAMPLE Update-InstalledModule Updates all Modules to latest version found in Repository PowerShellGallery and installs them in the User Scope .EXAMPLE Update-InstalledModule [-Name] PSReadLine,PowerShellGet -SkipMajorVersion -Scope AllUsers Updates Modules PSReadLine and PowerShellGet to latest version found in Repository PowerShellGallery. Using Switch SkipMajorVersion will only update to the latest minor version currently installed of the module Scope AllUsers requires Administrative Rights. Script will terminate if not run as Administrator .EXAMPLE Update-InstalledModule [-Name] PSReadLine -AllowPrerelease Updates all Modules to latest version found in Repository PowerShellGallery including PreReleases Please note that, if the Name Parameter is not provided this will update ALL Modules to PreReleases found for each! .EXAMPLE Update-InstalledModule -Exclude Az Updates all Modules to latest version found in Repository PowerShellGallery, except Az .EXAMPLE Update-InstalledModule -Repository MyRepo Updates all Modules to latest version found in Repository MyRepo, except Az .EXAMPLE Update-InstalledModule -Repository MyRepo -Credential $MyPsCredential Authenticating against the Repository MyRepo with the provided Credential $MyCredential; Updates all Modules to latest version found in Repository MyRepo, except Az .INPUTS System.String .OUTPUTS System.Void .NOTES Inspired by Barbara Forbes (@ba4bes, https://4bes.nl) 'Update-EveryModule', just separated out into two scripts. This is splitting Update-EveryModule into Update-InstalledModule: Updating modules with options Limit-InstalledModule: Removing old versions except the latest The parameters Repository and Credential are added to allow more flexibility with other repositories. They are currently EXPERIMENTAL and untested. Handle with Care! To avoid having to confirm a Trusted Source, the InstallationPolicy for the Repository can be set to Trusted with: Set-PSRepository -Name "PsGallery" -InstallationPolicy Trusted Set-PSRepository -Name "MyRepo" -InstallationPolicy Trusted .COMPONENT SupportingFunction .FUNCTIONALITY Updates all Modules installed for a given Repository providing some options to control .LINK https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/Update-InstalledModule.md .LINK https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/Limit-InstalledModule.md .LINK https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/about_ModuleManagement.md .LINK https://github.com/DEberhardt/TeamsFunctions/tree/main/docs/ #> [cmdletbinding(SupportsShouldProcess, ConfirmImpact = 'Low')] [Alias('uim')] [OutputType([System.Void])] param ( [Parameter(HelpMessage = 'Modules will be updated')] [System.Collections.Generic.List[object]]$Name = @(), [Parameter(HelpMessage = 'Excluded Modules will not be updated')] [System.Collections.Generic.List[object]]$Exclude = @(), [Parameter()] [switch]$AllowPrerelease, [parameter()] [switch]$SkipMajorVersion, [Parameter()] [ValidateSet('CurrentUser', 'AllUsers')] [String]$Scope = 'CurrentUser', [Parameter()] [String]$Repository = 'PSGallery', [Parameter()] [System.Management.Automation.PSCredential]$Credential = [System.Management.Automation.PSCredential]::Empty ) begin { Show-FunctionStatus -Level Live Write-Verbose -Message "[BEGIN ] $($MyInvocation.MyCommand)" # Setting Preference Variables according to Upstream settings if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.SessionState.PSVariable.GetValue('VerbosePreference') } if (-not $PSBoundParameters.ContainsKey('Debug')) { $DebugPreference = $PSCmdlet.SessionState.PSVariable.GetValue('DebugPreference') } else { $DebugPreference = 'Continue' } if ( $PSBoundParameters.ContainsKey('InformationAction')) { $InformationPreference = $PSCmdlet.SessionState.PSVariable.GetValue('InformationAction') } else { $InformationPreference = 'Continue' } # Testing for Administrator if ( $Scope -eq 'AllUsers' ) { function Test-Administrator { $user = [Security.Principal.WindowsIdentity]::GetCurrent(); (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } # Check for Admin privileges If (! (Test-Administrator)) { throw "No Administrative privileges. To install in the 'AllUsers' Scope, please run this script as Administrator" } } # Setting Exclusions Write-Verbose "Excluding Modules: $($Exclude -join ', ')" # Preparing Query $GetInstalledModuleParams = @{ ErrorAction = 'Stop' } if ( $PSBoundParameters.ContainsKey('Name') ) { Write-Verbose -Message "Querying Modules with Name(s) $($Name -join ', ')" $GetInstalledModuleParams.Name = @($Name) } else { Write-Verbose -Message 'Querying all Modules' } # Querying installed Modules $CurrentModules = Get-InstalledModule @GetInstalledModuleParams | Where-Object Repository -EQ $Repository if ( $PSBoundParameters.ContainsKey('Exclude') ) { $CurrentModules = $CurrentModules | Where-Object Name -NotMatch "^$($Exclude -join '|')" } #region Confirm scope $Count = $(if ($CurrentModules.GetType().BaseType.Name -eq 'Array') { $CurrentModules.Count } else { 1 }) Write-Verbose -Message "Modules found to process: $Count" if ( $Count -gt 10 ) { $go = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'Continue' $goWithConfirm = New-Object System.Management.Automation.Host.ChoiceDescription '&Confirm', 'Continue with Confirm' $abort = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'Abort' $options = [System.Management.Automation.Host.ChoiceDescription[]]($go, $goWithConfirm, $abort) $title = 'Multiple Modules discovered!' $message = "Detected $Count Modules to process, excluding: $(if ( $Exclude ) { $Exclude -join ', ' } else { 'none' } ) | Proceed?" $result = $host.ui.PromptForChoice($title, $message, $options, 0) switch ($result) { 0 { } 1 { $ConfirmRequired = $true } 2 { break } } } #endregion # Preparing Splatting parameters $FindModuleParams = @{ ErrorAction = 'Stop' Repository = $Repository } $UpdateModuleParams = @{ ErrorAction = 'Stop' AcceptLicense = $true Force = $true Scope = $Scope } if ( $PSBoundParameters.ContainsKey('AllowPreRelease') ) { $FindModuleParams.AllowPreRelease = $UpdateModuleParams.AllowPreRelease = $true } if ( $PSBoundParameters.ContainsKey('Credential') ) { $FindModuleParams.Credential = $UpdateModuleParams.Credential = $Credential } } process { Write-Verbose -Message "[PROCESS] $($MyInvocation.MyCommand)" $CurrentModules | ForEach-Object { [System.Version]$ModuleVersion = $_.Version.Split('-')[0] #region Finding latest Module Version try { Write-Verbose "$($_.Name) - Checking Repository '$Repository' for latest version" $OnlineModule = Find-Module -Name $_.Name @FindModuleParams # Finding most recent Minor Version if SkipMajorVersion is used. if ( $SkipMajorVersion.IsPresent -and $OnlineModule.Version.Split('.')[0] -gt $_.Version.Split('.')[0] ) { Write-Warning "$($_.Name) - Found new major version! Online: $($OnlineModule.Version), Local: $($_.Version)" $MaximumVersion = New-Object -TypeName System.Version -ArgumentList ($($_.Version.Split('.')[0]), 999, 999) Write-Verbose "$($_.Name) - Checking Repository '$Repository' for latest minor version ($MaximumVersion)" -Verbose $OnlineModule = Find-Module -Name $_.Name @FindModuleParams -MaximumVersion $MaximumVersion } $OnlineModuleVersion = [System.Version]$OnlineModule.Version.Split('-')[0] } catch { Write-Error "Module $($_.Name) not found in gallery $_" $OnlineModule = $null } #endregion #Region Update $LocalModule = Get-Module -Name $_.Name -ListAvailable -Verbose:$false | Sort-Object Version -Descending | Select-Object -First 1 if ( $LocalModule ) { $LocalModuleVersion = if ( $LocalModule.Count -gt 1 ) { $LocalModule[0].Version } else { $LocalModule.Version } } else { $LocalModuleVersion = $null } Write-Verbose -Message "$($_.Name) - Online: $($OnlineModule.Version), Local: $($_.Version) $( if ($LocalModuleVersion) { "| $($LocalModuleVersion)"})" if ( $LocalModuleVersion -eq $OnlineModuleVersion ) { Write-Verbose -Message "$($_.Name) - Module is up to date: ($LocalModuleVersion)" } elseif ( $OnlineModuleVersion -gt $ModuleVersion -and $OnlineModuleVersion -gt $LocalModuleVersion ) { # Removing Module if Loaded $Loaded = Get-Module -Name $_.Name if ( $Loaded ) { Write-Verbose -Message "$($_.Name) - Module loaded; Removing Module" Remove-Module -Name $_.Name -Force -Verbose:$false -ErrorAction SilentlyContinue } # Updating Module try { Write-Information "INFO: $($_.Name) - Updating Module from $($LocalModuleVersion) to $($OnlineModuleVersion)" if ( $PSBoundParameters.ContainsKey('WhatIf') ) { $UpdateModuleParams.WhatIf = $True } if ( $ConfirmRequired -or $PSBoundParameters.ContainsKey('Confirm') ) { $UpdateModuleParams.Confirm = $True } $UpdateModuleParams.RequiredVersion = $OnlineModuleVersion Update-Module -Name $_.Name @UpdateModuleParams } catch { Write-Error "$($_.Name) failed: $_ " continue } # Importing module if it was loaded before if ( $Loaded ) { Write-Verbose -Message "$($_.Name) - Importing Module" try { $null = Import-Module -Name $_.Name -Verbose:$false -ErrorAction Stop } catch { Import-Module -Name $_.Name -Verbose:$false -ErrorAction SilentlyContinue } } } elseif ($null -ne $OnlineModule) { Write-Verbose -Message "$($_.Name) - Module is up to date: $($_.Version)" } #endregion } } #process end { Write-Verbose -Message "[END ] $($MyInvocation.MyCommand)" } #end } # Update-InstalledModule |