Public/Get-WingetUpdates.ps1

function Get-WingetUpdates {
    <#
    .SYNOPSIS
        Check for and install available winget package updates.

    .DESCRIPTION
        Displays a list of all installed winget packages that have updates available,
        with an interactive selection to choose which ones to update.
        Uses the Microsoft.WinGet.Client COM API for reliable package enumeration.

    .PARAMETER Force
        Skip the cache and force a fresh check for updates.

    .EXAMPLE
        Get-WingetUpdates
        Shows available updates and allows you to select which to install.

    .EXAMPLE
        Get-WingetUpdates -Force
        Forces a fresh check for updates.
    #>


    [CmdletBinding()]
    param(
        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [switch]$IWantToLiterallyUpdateAllFuckingResults,

        [Parameter()]
        [switch]$ExportHtml
    )

    # Ensure PwshSpectreConsole is available
    if (-not (Get-Module -Name PwshSpectreConsole)) {
        if (Get-Module -ListAvailable -Name PwshSpectreConsole) {
            Import-Module PwshSpectreConsole -ErrorAction SilentlyContinue
        }
    }

    # Ensure Microsoft.WinGet.Client is available
    if (-not (Get-Module -Name Microsoft.WinGet.Client)) {
        try {
            Import-Module Microsoft.WinGet.Client -ErrorAction Stop
        }
        catch {
            Write-Error "Microsoft.WinGet.Client module is required. Install it with: Install-Module Microsoft.WinGet.Client -Force"
            return
        }
    }

    Write-Host "Checking for winget package updates..." -ForegroundColor Cyan

    # Check cache first
    $cacheFile = Join-Path (Get-WingetBatchConfigDir) "update_cache.json"
    $useCache = $false

    if (-not $Force -and (Test-Path $cacheFile)) {
        try {
            $cache = Get-Content $cacheFile -Raw | ConvertFrom-Json
            $cacheAge = ((Get-Date) - [DateTime]::Parse($cache.LastChecked)).TotalMinutes

            if ($cacheAge -lt 30 -and $cache.Updates.Count -gt 0) {
                $useCache = $true
                $updatesAvailable = [System.Collections.Generic.List[Object]]::new()
                foreach ($u in $cache.Updates) {
                    $updatesAvailable.Add($u)
                }
                Write-Host "Using cached results (checked $([Math]::Round($cacheAge, 0)) minutes ago)" -ForegroundColor DarkGray
            }
        }
        catch {
            # Cache is corrupt, ignore
        }
    }

    if (-not $useCache) {
        # Use COM API to get packages with updates available
        Write-Host "Querying installed packages via COM API..." -ForegroundColor DarkGray
        $installed = Get-WinGetPackage -ErrorAction SilentlyContinue
        $updatesAvailable = [System.Collections.Generic.List[Object]]::new()

        foreach ($pkg in $installed) {
            if ($pkg.IsUpdateAvailable) {
                $updatesAvailable.Add(@{
                    Id = $pkg.Id
                    Name = $pkg.Name
                    InstalledVersion = $pkg.Version
                    Source = $pkg.Source
                    DisplayLine = "$($pkg.Name) ($($pkg.Id)) $($pkg.Version)"
                })
            }
        }

        # Save to cache
        try {
            $cacheData = @{
                LastChecked = (Get-Date).ToString('o')
                Updates = @($updatesAvailable)
            } | ConvertTo-Json -Depth 5
            [System.IO.File]::WriteAllText($cacheFile, $cacheData, [System.Text.Encoding]::UTF8)
        }
        catch {
            Write-Verbose "Failed to save update cache: $_"
        }
    }

    if ($updatesAvailable.Count -eq 0) {
        Write-Host "[OK] All packages are up to date!" -ForegroundColor Green
        return
    }

    Write-Host ""
    Write-Host " - " -ForegroundColor Green -NoNewline
    Write-Host "$($updatesAvailable.Count)" -ForegroundColor White -NoNewline
    Write-Host " update(s) available" -ForegroundColor Green
    Write-Host ""

    # HTML Export
    if ($ExportHtml) {
        Write-Host "`n[HTML] Exporting HTML report..." -ForegroundColor Cyan
        $timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
        $defaultPath = "C:\temp\WingetBatch_Updates_$timestamp.html".Replace(' ', '_')
        $exportPath = Read-Host "Enter path for HTML report [Default: $defaultPath]"
        if (-not $exportPath) { $exportPath = $defaultPath }
        if (-not $exportPath.EndsWith(".html")) { $exportPath += ".html" }

        try {
            Export-WingetHtmlReport -Data $updatesAvailable -ReportTitle "Updates" -FilePath $exportPath
            if (Test-Path $exportPath) {
                Write-Host "[OK] Report successfully saved to $exportPath" -ForegroundColor Green
                Invoke-Item $exportPath
            }
        } catch {
            Write-Host "[FAIL] Failed to generate HTML report: $_" -ForegroundColor Red
        }
    }

    # Interactive selection
    if ($IWantToLiterallyUpdateAllFuckingResults) {
        $selectedPackages = $updatesAvailable | ForEach-Object { $_.Id }
    }
    elseif (Get-Module -Name PwshSpectreConsole) {
        try {
            $displayToId = @{}
            $displayLines = $updatesAvailable | ForEach-Object {
                $display = $_.DisplayLine
                $displayToId[$display] = $_.Id
                $display
            }

            $selectedLines = Read-SpectreMultiSelection -Title "[cyan]Select packages to update (Space to toggle, Enter to confirm)[/]" `
                -Choices $displayLines `
                -PageSize 20 `
                -Color "Green"

            if ($selectedLines.Count -eq 0) {
                Write-Host "No packages selected." -ForegroundColor Yellow
                return
            }

            $selectedPackages = $selectedLines | ForEach-Object { $displayToId[$_] }
        }
        catch {
            Write-Warning "Interactive selection error: $_"
            Write-Host "Packages with updates available:" -ForegroundColor Cyan
            $updatesAvailable | ForEach-Object {
                Write-Host " - $($_.Id)" -ForegroundColor White
            }
            Write-Host ""
            Write-Host "Use 'Update-WinGetPackage -Id <PackageName>' to update manually." -ForegroundColor Yellow
            return
        }
    }
    else {
        # Fallback without interactive selection
        Write-Host "Packages with updates available:" -ForegroundColor Cyan
        $updatesAvailable | ForEach-Object {
            Write-Host " - $($_.Id)" -ForegroundColor White
        }
        Write-Host ""
        Write-Host "To update a package: " -ForegroundColor Cyan -NoNewline
        Write-Host "Update-WinGetPackage -Id <PackageName>" -ForegroundColor Yellow
        Write-Host "To update all: " -ForegroundColor Cyan -NoNewline
        Write-Host "Get-WingetUpdates -IWantToLiterallyUpdateAllFuckingResults" -ForegroundColor Yellow
        return
    }

    Write-Host ""
    Write-Host "Updating " -ForegroundColor Cyan -NoNewline
    Write-Host "$($selectedPackages.Count)" -ForegroundColor White -NoNewline
    Write-Host " package(s)..." -ForegroundColor Cyan
    Write-Host ""

    $successCount = 0
    $failCount = 0

    foreach ($packageId in $selectedPackages) {
        Write-Host ">>> Updating: " -ForegroundColor Magenta -NoNewline
        Write-Host $packageId -ForegroundColor White

        try {
            $result = Update-WinGetPackage -Id $packageId -Mode Silent -ErrorAction Stop
            Write-Host "[OK] Successfully updated " -ForegroundColor Green -NoNewline
            Write-Host $packageId -ForegroundColor White
            $successCount++
        }
        catch {
            Write-Host "[FAIL] Failed to update " -ForegroundColor Red -NoNewline
            Write-Host $packageId -ForegroundColor White -NoNewline
            Write-Host " ($_)" -ForegroundColor Red
            $failCount++
        }
        Write-Host ""
    }

    Write-Host ("=" * 60) -ForegroundColor Green
    Write-Host "Update Complete" -ForegroundColor Green
    Write-Host ("=" * 60) -ForegroundColor Green
    Write-Host " - " -ForegroundColor Green -NoNewline
    Write-Host $successCount -ForegroundColor White -NoNewline
    Write-Host " | Failed: " -ForegroundColor Red -NoNewline
    Write-Host $failCount -ForegroundColor White

    # Clear cache after updates
    if (Test-Path $cacheFile) {
        Remove-Item $cacheFile -Force
    }
}