Winget App Updates Script

Resolves winget in SYSTEM context and lists available application upgrades. Returns exit code 1 when more than the allowed number of upgradable apps is found, triggering the paired remediation that upgrades them silently. Apps with unknown installed versions are excluded because winget cannot safely upgrade them.

Remediation
709 views214 downloadsVersion 1.0By Ugur Koc
View on GitHub

// QUALITY CHECKS

Validation status

Quality checks

All checks pass
  • ParsePass
  • LintPass
  • MetadataPass
  • Runbook-readyPass
  • Module depsPass

Tests run automatically on every change. What does each check mean?

// REQUIRED PERMISSIONS

Microsoft Graph scopes

DeviceManagementManagedDevices.ReadWrite.All

Allows the app to read and write the properties of devices managed by Microsoft Intune, without a signed-in user. Does not allow high impact operations such as remote wipe and password reset on the device's owner

// CHANGELOG

Version history

  1. Entry · 01

    1.0 - Initial release

// CODE

Source

winget-app-updates.ps1
<#
.TITLE
    Winget App Updates Detection Script

.SYNOPSIS
    Detects third-party applications with available winget upgrades.

.DESCRIPTION
    Resolves winget in SYSTEM context and lists available application upgrades.
    Returns exit code 1 when more than the allowed number of upgradable apps is
    found, triggering the paired remediation that upgrades them silently. Apps with
    unknown installed versions are excluded because winget cannot safely upgrade
    them.

.TAGS
    Remediation,Detection

.REMEDIATIONTYPE
    Detection

.PAIRSCRIPT
    remediate-winget-updates.ps1

.PLATFORM
    Windows

.MINROLE
    Intune Service Administrator

.PERMISSIONS
    DeviceManagementManagedDevices.ReadWrite.All

.AUTHOR
    Ugur Koc

.VERSION
    1.0

.CHANGELOG
    1.0 - Initial release

.LASTUPDATE
    2026-07-20

.EXAMPLE
    .\detect-winget-updates-available.ps1
    Returns exit 1 if any app has a pending winget upgrade

.NOTES
    - Runs in SYSTEM context via Intune Remediations; winget is resolved from the DesktopAppInstaller package folder because SYSTEM has no winget alias
    - Requires Windows 10 1809+ with App Installer (winget) present
    - Apps listed in $ExcludedIds are ignored; add IDs of apps managed by other update mechanisms
#>

$ErrorActionPreference = "Stop"

# Winget package IDs to ignore (managed elsewhere, e.g. by Intune apps or auto-updaters)
$ExcludedIds = @(
    "Microsoft.Office",
    "Microsoft.Teams"
)

function Resolve-WingetPath {
    # SYSTEM context has no winget alias; resolve the packaged exe directly
    $wingetCommand = Get-Command winget.exe -ErrorAction SilentlyContinue
    if ($wingetCommand) { return $wingetCommand.Source }

    $packageFolder = Get-ChildItem -Path "$env:ProgramFiles\WindowsApps" -Filter "Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe" -Directory -ErrorAction SilentlyContinue |
        Sort-Object Name -Descending | Select-Object -First 1

    if ($packageFolder) {
        $candidate = Join-Path $packageFolder.FullName "winget.exe"
        if (Test-Path $candidate) { return $candidate }
    }

    return $null
}

try {
    $winget = Resolve-WingetPath
    if (-not $winget) {
        Write-Output "winget is not installed on this device - nothing to check."
        exit 0
    }

    # --include-unknown is deliberately NOT used: unknown-version apps cannot be upgraded safely
    $output = & $winget upgrade --accept-source-agreements --disable-interactivity 2>&1 | Out-String

    if ($LASTEXITCODE -ne 0 -and $output -notmatch "upgrades available") {
        # Exit code is non-zero when no upgrades exist; treat known "no upgrades" output as compliant
        if ($output -match "No installed package found|No available upgrade") {
            Write-Output "No winget upgrades available."
            exit 0
        }
    }

    # Parse the table: lines after the dashed separator, columns Name Id Version Available Source
    $lines = $output -split "`r?`n"
    $separatorIndex = -1
    for ($i = 0; $i -lt $lines.Count; $i++) {
        if ($lines[$i] -match '^-{5,}') { $separatorIndex = $i; break }
    }

    if ($separatorIndex -lt 0) {
        Write-Output "No winget upgrades available."
        exit 0
    }

    $upgradableApps = [System.Collections.Generic.List[string]]::new()
    for ($i = $separatorIndex + 1; $i -lt $lines.Count; $i++) {
        $line = $lines[$i].Trim()
        if (-not $line) { continue }
        if ($line -match '^\d+ upgrades? available') { continue }
        if ($line -match 'require explicit targeting|cannot be determined') { break }

        # Column widths vary by locale; match the package ID as the token
        # shaped like Publisher.Product instead of splitting by position
        $packageId = ($line -split '\s+' | Where-Object { $_ -match '^[\w-]+(\.[\w-]+)+$' } | Select-Object -First 1)

        if ($packageId -and ($ExcludedIds -notcontains $packageId)) {
            $upgradableApps.Add($packageId)
        }
    }

    if ($upgradableApps.Count -gt 0) {
        Write-Output "Upgrades available for $($upgradableApps.Count) app(s): $($upgradableApps -join ', ')"
        exit 1
    }

    Write-Output "No winget upgrades available."
    exit 0
}
catch {
    Write-Error $_
    exit 2
}

// NOTES

Author notes

- Runs in SYSTEM context via Intune Remediations; winget is resolved from the DesktopAppInstaller package folder because SYSTEM has no winget alias - Requires Windows 10 1809+ with App Installer (winget) present - Apps listed in $ExcludedIds are ignored; add IDs of apps managed by other update mechanisms

// RELATED

Picked by shared tags, category, and script type — nothing magic, just metadata overlap.

  1. Windows Defender Definition Update

    Checks if Windows Defender definitions are current (within 48 hours). Returns exit code 1 if definitions are outdated.

    Remediation
  2. Disk Cleanup Script

    Checks Windows temp folders and recycle bin size. Returns exit code 1 if more than 1GB can be cleaned up.

    Remediation
  3. Local Admin Drift Script

    Enumerates the local Administrators group and compares every member against an allowlist of approved accounts and well-known SIDs (built-in Administrator, the Entra-joined device admin roles, and configurable extra entries). Returns exit code 1 when unauthorized members are present, triggering the paired remediation that removes them. This catches technician accounts, self-elevation leftovers, and helpdesk additions that were never cleaned up.

    Remediation