Update-AllPSModules.psm1

function update-AllPSModules
{
    <#
    .SYNOPSIS
    This script will update all locally installed PowerShell modules to the newest ones if can find online.
 
    .DESCRIPTION
    The script will search the usual PowerShell module profile paths for all modules and update them to the newest online versions.
 
    Updating modules depends on 'PackageManagement' and 'PowerShellGet' updated to the newest versions before upgrading any modules.
 
    The script searches for full releases of all modules by default, but there are switches to search for pre-release modules.
 
    The script presents you with a list of all modules it finds and shows you if a newer version is detected and when that new version was published.
 
    Differences with PowerShell's inbuilt "Update-Module" command:
    • "Update-Module" won't update 'PackageManagement' and 'PowerShellGet'.
    • It shows no data while operating, so you are left with an empty screen unless you use the "-verbose" switch, which displays too much information.
    • You can use the "-AllowPrerelease", but only with a named module. This script can install pre-release or nightly versions of all modules if they exist.
 
    .PARAMETER AllowPreviews
    If you want to include versions that have 'preview', 'RC' etc., in the version number, use this switch. 'Nightly', and 'alpha' are considered too early so are ignored.
 
    .EXAMPLE
    Update-AllPSModules
    This will update all locally installed modules .
    .EXAMPLE
    Update-AllPSModules -AllowPreviews
    This will update all locally installed modules and include versions with 'preview' or 'RC' in the version number.
 
    .NOTES
    Filename: Update-AllPSModules.ps1
    Contributors: Kieran Walsh
    Created: 2021-01-09
    Last Updated: 2022-04-23
    Version: 1.47.0
    ProjectUri: https://github.com/kieranwalsh/Update-AllPSModules
#>


    [CmdletBinding()]
    Param(
        [Parameter()]
        [switch]$AllowPreviews
    )

    if($PSVersionTable.psversion -lt [version]'5.0.0')
    {
        Write-Warning -Message "This script only works with PowerShell 5.0 or newer. You are running $($PSVersionTable.PSVersion)"
        break
    }
    if($ExecutionContext.SessionState.LanguageMode -eq 'ConstrainedLanguage')
    {
        Write-Warning 'Constrained Language mode is enabled, so the script cannot continue.'
        break
    }

    $CurrentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
    If(-not(New-Object -TypeName 'Security.Principal.WindowsPrincipal' -ArgumentList $CurrentUser).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator))
    {
        Write-Warning -Message 'The script is not being run as administrator so cannot continue.'
        Break
    }
    $StartTime = Get-Date

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    $NewSessionRequired = $false
    try
    {
        $RegisteredRepositories = Get-PSRepository -ErrorAction 'Stop' -WarningAction 'Stop'
    }
    catch
    {
        Write-Warning "Unable to query 'PSGallery' online. The script cannot continue - check your proxy/firewall settings."
        break
    }

    if($RegisteredRepositories -notmatch 'PSGallery')
    {
        try
        {
            Set-PSRepository -Name 'PSGallery' -InstallationPolicy 'Trusted' -ErrorAction 'Stop'
        }
        catch
        {
            'Unable to Set the PSRepository'
            break
        }
    }

    Write-Host -Object "Checking which version of the 'PackageManagement' module is installed locally" -NoNewline
    $PackageManagement = (Get-Module -ListAvailable -Name 'PackageManagement' | Sort-Object Version -Descending)[0]
    Write-Host -Object " - found '$(($PackageManagement.version).tostring())'."
    if([version]$PackageManagement.Version -lt [version]'1.4.7')
    {
        "An updated version is required. Attempting to install 'Nuget'"
        try
        {
            $NugetInstall = Install-PackageProvider -Name 'Nuget' -Force -ErrorAction 'Stop'
            Write-Host -Object "Successfully installed 'Nuget' version '$($NugetInstall.Version)'"
        }
        catch
        {
            if($error[0].exception.message -match 'No match was found for the specified search criteria for the provider ')
            {
                Write-Warning -Message 'Unable to find packages online. Check proxy settings.'
            }
            Else
            {
                Write-Host -Object 'Unknown error.'
            }
            break
        }
        Write-Host -Object "Searching for a newer version of 'PackageManagement'."
        try
        {
            $OnlinePackageManagement = Find-Module -Name 'PackageManagement' -Repository 'PSGallery' -ErrorAction 'Stop'
        }
        catch
        {
            Write-Host -Object "Unable to find 'PackageManagement' online. Does this machine have an internet connection?"
            break
        }
        try
        {
            $OnlinePackageManagement | Install-Module -Force -SkipPublisherCheck -ErrorAction 'Stop'
            Write-Host -Object "Successfully installed 'PackageManagement' version '$($OnlinePackageManagement.Version)'"
        }
        catch
        {
            Write-Host -Object "Failed to install 'PackageManagement'."
            break
        }
        Write-Host -Object "You need to close PowerShell and re-open it to use the new 'PackageManagement' module."
        $NewSessionRequired = $true
    }

    Write-Host -Object "Checking which version of the 'PowerShellGet' module is installed locally" -NoNewline
    $PowershellGet = (Get-Module -ListAvailable -Name 'PowerShellGet' | Sort-Object Version -Descending)[0]
    Write-Host -Object " - found '$(($PowershellGet.version).tostring())'."

    if([version]$PowershellGet.Version -lt [version]'1.6.0')
    {
        $OnlinePSGet = Find-Module -Name 'PowershellGet' -Repository 'PSGallery'
        Write-Host -Object "Version '$($OnlinePSGet.version)' found online, will attempt to update."
        try
        {
            $OnlinePSGet | Install-Module -Force -SkipPublisherCheck -ErrorAction 'Stop'
            Write-Host -Object "Successfully installed 'PowerShellGet' version '$($OnlinePSGet.Version)'. Close PowerShell and re-open it to use the new module."
            $NewSessionRequired = $true
        }
        catch
        {
            Write-Host -Object "Unable to install to 'C:\Program Files\WindowsPowerShell\Modules' so will try the Current User module path instead."
            try
            {
                $OnlinePSGet | Install-Module -Force -SkipPublisherCheck -Scope CurrentUser -ErrorAction 'Stop'
                Write-Host -Object "Successfully installed 'PowerShellGet' version '$($OnlinePSGet.Version)'. Close PowerShell and re-open it to use the new module."
                $NewSessionRequired = $true
            }
            catch
            {
                Write-Warning 'Failed to install the latest version of PowerShellGet. This will mean you may not be able to install preview, or beta versions of the modules.'
                break
            }
        }
    }

    if($NewSessionRequired)
    {
        break
    }
    $Failed = @()
    Write-Host -Object 'Searching for all locally installed modules' -NoNewline

    $InstalledModules = Get-InstalledModule |
    Where-Object -FilterScript {
    ($_.name -notmatch 'PackageManagement|PowerShellGet|Az\.|AzureRM\.|Azure\.')
    } |
    Sort-Object -Property 'Name'

    if($InstalledModules)
    {
        $SuccessfulUpdates = 0
        Write-Host -Object " - $(($InstalledModules | Measure-Object).count) modules found."
        Write-Host -Object 'Checking for newer versions online and trying to update them.'
        $MaxNameWidth = (($InstalledModules).'Name' |
            Sort-Object -Property 'Length' -Descending |
            Select-Object -First 1).length + 3

        $MaxVersionWidth = (($InstalledModules).'Version' |
            Sort-Object -Property 'Length' -Descending |
            Select-Object -First 1).length + 3

        foreach($InstalledModule in $InstalledModules)
        {
            Write-Host -Object ("{0,-$MaxNameWidth}" -f $InstalledModule.Name ) -NoNewline
            if(-not($AllowPreviews))
            {
                try
                {
                    $Module = Get-InstalledModule -Name $InstalledModule.Name -AllVersions |
                    Sort-Object -Property {
                        [version](($_.Version -split '-')[0])
                    } -Descending |
                    Select-Object -First 1
                    $LatestAvailableOnline = Find-Module -Name $InstalledModule.Name -ErrorAction 'Stop'
                }
                catch
                {
                    $Failed += $InstalledModule
                    continue
                }
            }
            Else
            {
                try
                {
                    $Modules = Get-InstalledModule -Name $InstalledModule.Name -AllVersions
                    if($Modules.version -match '-')
                    {
                        $UpdateType = ($Modules.version -split '-')[1]
                        $CalculatedVersions = $Modules | ForEach-Object {
                            $NewVersionNumber = $_.Version -replace '-[a-z]+[A-Z]+', '.'
                            if($NewVersionNumber[-1] -eq '.')
                            {
                                $NewVersionNumber = $NewVersionNumber -replace '.$', '.0'
                            }
                            [PSCustomObject]@{
                                'Name'              = $_.Name
                                'Version'           = $_.Version
                                'CalculatedVersion' = [version]$NewVersionNumber
                                'Update Type'       = $UpdateType
                            }
                        }
                        $Module = $CalculatedVersions | Sort-Object -Property 'CalculatedVersion' -Descending | Select-Object -First 1
                    }
                    else
                    {
                        $Module = $Modules | Sort-Object -Property {[version]$_.'Version'} -Descending | Select-Object -First 1
                    }
                    $LatestAvailableOnline = (Find-Module -Name $InstalledModule.Name -AllowPrerelease -AllVersions -ErrorAction 'Stop' | Where-Object {$_.Version -notmatch 'nightly|alpha'})[0]
                }
                catch
                {
                    $Failed += $InstalledModule
                    continue
                }
            }
            Write-Host -Object ("{0,-$MaxVersionWidth}" -f $Module.Version) -NoNewline
            if(([version](($Module.Version -replace '[a-z]*', '').Replace('-', '.') -replace '\.$', '')) -ge ([version](($LatestAvailableOnline.Version -replace '[a-z]*', '').Replace('-', '.') -replace '\.$', '')))
            {
                Write-Host -Object $([char]0x2714) -ForegroundColor 'Green'
            }
            else
            {
                $AllUsersFailed = $false
                $Gap = (20 - (($LatestAvailableOnline.version).length))
                Write-Host -Object ("Online version found: '{0}' - attempting to update. {1,$Gap}" -f "$($LatestAvailableOnline.version)' - Published '$(Get-Date($LatestAvailableOnline.PublishedDate) -Format 'yyyy-MM-dd')", ' ') -ForegroundColor 'Yellow' -NoNewline
                if(-not($AllowPreviews))
                {
                    try
                    {
                        Update-Module -AcceptLicense -Force -Name $Module.Name -Scope 'AllUsers' -ErrorAction 'Stop'
                        Write-Host -Object $([char]0x2714) -ForegroundColor 'Green'
                        $SuccessfulUpdates++
                    }
                    catch
                    {
                        $AllUsersFailed = $true
                    }
                }
                Else
                {
                    try
                    {
                        Update-Module -AcceptLicense -AllowPrerelease -RequiredVersion $LatestAvailableOnline.version -Force -Name $Module.Name -Scope 'AllUsers' -ErrorAction 'Stop'
                        Write-Host -Object $([char]0x2714) -ForegroundColor 'Green'
                        $SuccessfulUpdates++
                    }
                    catch
                    {
                        $AllUsersFailed = $true
                    }
                }
                if($AllUsersFailed)
                {
                    try
                    {
                        Update-Module -AcceptLicense -AllowPrerelease -RequiredVersion $LatestAvailableOnline.version -Force -Name $Module.Name -Scope 'CurrentUser' -ErrorAction 'Stop'
                        Write-Host -Object $([char]0x2714) -ForegroundColor 'Green' -NoNewline
                        Write-Host -Object " ('Current User' scope only)" -ForegroundColor 'Yellow'
                        $SuccessfulUpdates++
                    }
                    catch
                    {
                        Write-Host -Object ([char]0x2718) -ForegroundColor 'Red' -NoNewline
                        Write-Host -Object (' Update not possible, will uninstall and try a new install. ') -ForegroundColor 'Yellow' -NoNewline
                        try
                        {
                            Uninstall-Module -Name $Module.Name -AllowPrerelease -AllVersions -Force -ErrorAction 'Stop'
                            try
                            {
                                Install-Module -Name $Module.Name -RequiredVersion $LatestAvailableOnline.version -AcceptLicense -AllowClobber -AllowPrerelease -Force -Scope 'AllUsers' -SkipPublisherCheck -ErrorAction 'Stop'
                                Write-Host -Object $([char]0x2714) -ForegroundColor 'Green'
                                $SuccessfulUpdates++
                            }
                            catch
                            {
                                try
                                {
                                    Install-Module -Name $Module.Name -RequiredVersion $LatestAvailableOnline.version -AcceptLicense -AllowClobber -AllowPrerelease -Force -Scope 'CurrentUser' -SkipPublisherCheck -ErrorAction 'Stop'
                                    Write-Host -Object $([char]0x2714) -ForegroundColor 'Green' -NoNewline
                                    Write-Host -Object " ('Current User' scope only)" -ForegroundColor 'Yellow'
                                    $SuccessfulUpdates++
                                }
                                catch
                                {
                                    Write-Host -Object ([char]0x2718) -ForegroundColor 'Red'
                                }
                            }
                        }
                        catch
                        {
                            Write-Host -Object ([char]0x2718) -ForegroundColor 'Red'
                        }
                    }
                }
            }
        }
    }
    Else
    {
        '. None found.'
    }

    $OnlinePSGet = Find-Module -Name 'PowershellGet' -AllowPrerelease -Repository 'PSGallery' -ErrorAction 'Stop'
    if(([version](($PowershellGet.Version -split ('-'))[0])) -lt ([version](($OnlinePSGet.Version -split ('-'))[0])))
    {
        Write-Host -Object "A newer version of 'PowerShellGet' is available online, will attempt to update."
        try
        {
            $OnlinePSGet | Install-Module -Force -SkipPublisherCheck -ErrorAction 'Stop'
            Write-Host -Object "Successfully installed 'PowerShellGet' version '$($OnlinePSGet.Version)'. Close PowerShell and re-open it to use the new module."
            $NewSessionRequired = $true
        }
        catch
        {
            Write-Host -Object "Unable to install to 'C:\Program Files\WindowsPowerShell\Modules' so will try the Current User module path instead."
            try
            {
                $OnlinePSGet | Install-Module -Force -SkipPublisherCheck -Scope CurrentUser -ErrorAction 'Stop'
                Write-Host -Object "Successfully installed 'PowerShellGet' version '$($OnlinePSGet.Version)'. Close PowerShell and re-open it to use the new module."
                $NewSessionRequired = $true
            }
            catch
            {
                Write-Warning 'Failed to install the latest version of PowerShellGet. This will mean you may not be able to install preview, or beta versions of the modules.'
                break
            }
        }
    }

    if($Failed)
    {
        'Unable to find these modules:'
        $Failed
    }
    $EndTime = Get-Date
    $TimeTaken = ''
    $TakenSpan = New-TimeSpan -Start $StartTime -End $EndTime
    if($TakenSpan.Hours)
    {
        $TimeTaken += "$($TakenSpan.Hours) hours, $($TakenSpan.Minutes) minutes, "
    }
    Elseif($TakenSpan.Minutes)
    {
        $TimeTaken += "$($TakenSpan.Minutes) minutes, "
    }
    $TimeTaken += "$($TakenSpan.Seconds) seconds"
    if($SuccessfulUpdates)
    {
        Write-Host -Object "Successfully updated $($SuccessfulUpdates) module(s) in $($TimeTaken)."
    }
    Else
    {
        Write-Host -Object "The script took $TimeTaken to complete and no updates were required."
    }
}