public/Invoke-OctaSoftwareUpdater.ps1

function Invoke-OctaSoftwareUpdater {
    <#
        .SYNOPSIS
        Bulk-updates installed software via winget (always attempted - ships with Windows 11)
        and Chocolatey/Scoop (only when their executable is actually present). Never installs
        either optional package manager itself.

        Default (no -Apply) is a dry preview of which managers are present, same pattern as
        every category's dry-run - the real update only runs with -Apply, deferred to a
        disposable VM in this project's own testing practice since it's a genuine, real update.
    #>

    [CmdletBinding()]
    param(
        [switch]$Apply,
        [switch]$Yes
    )

    $managers = @(
        [pscustomobject]@{ Manager = 'winget'; Available = $true }
        [pscustomobject]@{ Manager = 'Chocolatey'; Available = [bool](Get-Command choco -ErrorAction SilentlyContinue) }
        [pscustomobject]@{ Manager = 'Scoop'; Available = [bool](Get-Command scoop -ErrorAction SilentlyContinue) }
    )

    if (-not $Apply) {
        foreach ($m in $managers) {
            $status = if ($m.Available) { 'will update' } else { 'not installed - skipped' }
            Write-Host ("{0,-12} {1}" -f $m.Manager, $status)
        }
        return $managers
    }

    if (-not $Yes) {
        $response = Read-Host "Update all software via the package manager(s) above? (y/N)"
        if ($response -notin @('y', 'Y', 's', 'S')) {
            return [pscustomobject]@{ Status = 'Cancelled' }
        }
    }

    $results = @()

    & winget upgrade --all --accept-source-agreements --accept-package-agreements
    $results += [pscustomobject]@{ Manager = 'winget'; ExitCode = $LASTEXITCODE }

    if (($managers | Where-Object Manager -eq 'Chocolatey').Available) {
        & choco upgrade all -y
        $results += [pscustomobject]@{ Manager = 'Chocolatey'; ExitCode = $LASTEXITCODE }
    }

    if (($managers | Where-Object Manager -eq 'Scoop').Available) {
        & scoop update '*'
        $results += [pscustomobject]@{ Manager = 'Scoop'; ExitCode = $LASTEXITCODE }
    }

    return [pscustomobject]@{ Status = 'Success'; Results = $results }
}