public/Confirm-Modules.ps1

<#
.SYNOPSIS
Check if installed modules are up to date
.DESCRIPTION
Check installed modules against PowerShell Gallery to confirm they
are current version. If not, optionally, update them to current.
.PARAMETER Update
Update modules which are not the latest version.
.PARAMETER Exclude
Names of modules to ignore. Note that local-only modules (not installed from, or available
within the PowerShell Gallery) are ignored as well.
.PARAMETER ExcludePattern
One or more wildcard patterns to filter out module names to exclude them from validation.
Example: Microsoft.Graph.* or Az.*, Microsoft.Graph.*
.EXAMPLE
Confirm-Modules

Checks modules to report current version status: Current, OutOfDate or LocalOnly.
.EXAMPLE
Confirm-Modules -Update

Checks module versions and updates those which report status OutOfDate.
.EXAMPLE
Confirm-Modules -Exclude MyModule,OtherModule -Update

Checks all modules except for MyModule and OtherModule and updates the rest if they
report status OutOfDate.
.EXAMPLE
Confirm-Modules -Exclude MyModule,OtherModule -ExcludePattern Microsoft.Graph.* -Update

Checks all modules except for MyModule and OtherModule and any with names that start
with "Microsoft.Graph." and updates the rest if they report status OutOfDate.
.EXAMPLE
Confirm-Modules -Exclude Az -ExcludePattern Az.*,Microsoft.Graph.* -Update

Checks all modules except for Az and any with names that start
with "Az." or "Microsoft.Graph." and updates the rest if they report status OutOfDate.
.LINK
https://github.com/Skatterbrainz/helium/blob/master/docs/Confirm-Modules.md
#>

function Confirm-Modules {
    [CmdletBinding()]
    param (
        [parameter()][switch]$Update,
        [parameter()][string[]]$Exclude,
        [parameter()][string[]]$ExcludePattern
    )
    try {
        $count1  = 0
        $count2  = 0
        $count3  = 0
        $padnum  = 12
        $updated = 0
        $modules = [System.Collections.ArrayList]::new()
        Write-Host "Checking for all installed modules"
        Get-Module -ListAvailable | Sort-Object Name -Unique | Foreach-Object {
            $moduleVersion = (Get-Module $_.Name -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1 -ExpandProperty Version)
            $modulePath = (Get-Module $_.Name -ListAvailable | Where-Object {$_.Version -eq $moduleVersion} | Select-Object -ExpandProperty Path)
            if ($modulePath -match $env:HOME) {
                $mscope = 'User'
            } else {
                $mscope = 'Machine'
            }
            $null = $modules.Add(
                [pscustomobject]@{Name = $_.Name; Version = $moduleVersion; Path = $modulePath; Scope = $mscope}
            )
        }
        $total = $modules.Count
        Write-Host "$($total) modules were found. Applying filters..." -NoNewline
        if (![string]::IsNullOrEmpty($Exclude)) {
            $modules = $modules | Where-Object {$_.Name -notin $Exclude}
        }
        foreach ($excluded in $ExcludePattern) {
            $modules = $modules | Where-Object {$_.Name -notlike "$excluded"}
        }
        foreach ($module in $modules) {
            Write-Verbose "Checking: $($module.Name)"
            $gmodule = $null
            $lversion = $module.Version.ToString()
            $gmodule = Find-Module -Name $module.Name -ErrorAction SilentlyContinue
            if ($gmodule) {
                if ($gmodule.Version -ne $module.Version) {
                    $status = 'OutOfDate'
                    $gversion = $gmodule.Version.ToString()
                    $count1++
                } else {
                    $status = 'Current'
                    $gversion = $gmodule.Version.ToString()
                    $count2++
                }
            } else {
                $status = 'LocalOnly'
                $gversion = $null
                $count3++
            }
            [pscustomobject]@{
                Name             = $module.Name
                Scope            = $module.Scope
                InstalledVersion = $lversion
                AvailableVersion = $gversion
                Status           = $status
            }
            if ($Update.IsPresent) {
                if ($status -eq 'OutOfDate') {
                    if ($module.Scope -eq 'User') {
                        Write-Host "Updating module: $($module.Name)" -ForegroundColor Yellow
                        Update-Module -Name $module.Name
                        $updated++
                    } else {
                        if (Test-IsAdmin -eq $True) {
                            Write-Host "Updating module: $($module.Name)" -ForegroundColor Yellow
                            Update-Module -Name $module.Name
                            $updated++
                        } else {
                            Write-Warning "Not running administrator context - Update deferred"
                        }
                    }
                }
            }
        }
        Write-Host "$(($total).ToString().PadRight($padnum,'.')) modules are installed" -ForegroundColor Yellow
        Write-Host "$(($modules.Count).ToString().PadRight($padnum,'.')) modules are not excluded" -ForegroundColor Cyan
        Write-Host "$(($count2).ToString().PadRight($padnum,'.')) modules are current" -ForegroundColor Green
        Write-Host "$(($count1).ToString().PadRight($padnum,'.')) modules are out of date" -ForegroundColor Red
        Write-Host "$(($updated).ToString().PadRight($padnum,'.')) modules were updated" -ForegroundColor Green
        Write-Host "$(($count3).ToString().PadRight($padnum,'.')) modules are local-only (ignored)" -ForegroundColor DarkGreen
        if (![string]::IsNullOrEmpty($Exclude) -or ![string]::IsNullOrWhiteSpace($ExcludePattern)) {
            Write-Host "$(($total - $($modules.Count)).ToString().PadRight($padnum,'.')) modules were excluded" -ForegroundColor Magenta
        }
    } catch {
        Write-Error $_.Exception.Message
    }
}