Skip to content

Instantly share code, notes, and snippets.

@TheAngryByrd
Created August 19, 2026 16:00
Show Gist options
  • Select an option

  • Save TheAngryByrd/9dc076452ae11e386676d11defe035ae to your computer and use it in GitHub Desktop.

Select an option

Save TheAngryByrd/9dc076452ae11e386676d11defe035ae to your computer and use it in GitHub Desktop.
Check outdated Windows software, drivers, package managers, and developer tools without installing updates.
#Requires -Version 5.1
[CmdletBinding()]
param(
[switch] $SkipWindowsUpdate,
[switch] $SkipPowerShellResources,
[switch] $SkipDeveloperTools
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$script:CompletedChecks = [System.Collections.Generic.List[string]]::new()
$script:SkippedChecks = [System.Collections.Generic.List[string]]::new()
$script:FailedChecks = [System.Collections.Generic.List[string]]::new()
function Write-CheckTitle {
param([Parameter(Mandatory)][string] $Name)
Write-Host "`n=== $Name ===" -ForegroundColor Cyan
}
function Complete-Check {
param([Parameter(Mandatory)][string] $Name)
[void] $script:CompletedChecks.Add($Name)
}
function Skip-Check {
param(
[Parameter(Mandatory)][string] $Name,
[Parameter(Mandatory)][string] $Reason
)
Write-Host "Skipped: $Reason" -ForegroundColor DarkGray
[void] $script:SkippedChecks.Add($Name)
}
function Fail-Check {
param(
[Parameter(Mandatory)][string] $Name,
[Parameter(Mandatory)][string] $Reason
)
Write-Warning "$Name failed. $Reason"
[void] $script:FailedChecks.Add($Name)
}
function Invoke-ToolCheck {
param(
[Parameter(Mandatory)][string] $Name,
[Parameter(Mandatory)][string] $Command,
[string[]] $ArgumentList = @(),
[int[]] $AllowedExitCodes = @(0)
)
Write-CheckTitle $Name
$tool = Get-Command $Command -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($null -eq $tool) {
Skip-Check -Name $Name -Reason "$Command is not installed."
return
}
try {
$LASTEXITCODE = 0
& $tool @ArgumentList | Out-Host
$exitCode = $LASTEXITCODE
if ($AllowedExitCodes -notcontains $exitCode) {
Fail-Check -Name $Name -Reason "$Command exited with code $exitCode."
return
}
Complete-Check $Name
}
catch {
Fail-Check -Name $Name -Reason $_.Exception.Message
}
}
function Invoke-WindowsUpdateChecks {
$sessionName = 'Windows Update'
try {
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$searcher.Online = $true
}
catch {
foreach ($updateType in @('Software', 'Driver')) {
$name = "${sessionName}: $updateType"
Write-CheckTitle $name
Fail-Check -Name $name -Reason $_.Exception.Message
}
return
}
foreach ($updateType in @('Software', 'Driver')) {
$name = "${sessionName}: $updateType"
Write-CheckTitle $name
try {
$result = $searcher.Search(
"IsInstalled=0 and IsHidden=0 and Type='$updateType'"
)
$updates = @(
foreach ($update in $result.Updates) {
[pscustomobject]@{
Title = $update.Title
KB = $update.KBArticleIDs -join ', '
RebootRequired = $update.RebootRequired
}
}
)
if ($updates.Count -eq 0) {
Write-Host "No $($updateType.ToLowerInvariant()) updates found."
}
else {
$updates | Format-Table -AutoSize
}
Complete-Check $name
}
catch {
Fail-Check -Name $name -Reason $_.Exception.Message
}
}
}
function Invoke-DeviceHealthCheck {
$name = 'Device health'
Write-CheckTitle $name
try {
$deviceErrors = @(
Get-CimInstance -ClassName Win32_PnPEntity |
Where-Object {
$null -ne $_.ConfigManagerErrorCode -and
$_.ConfigManagerErrorCode -ne 0
} |
Select-Object Name, PNPClass, Manufacturer, ConfigManagerErrorCode
)
if ($deviceErrors.Count -eq 0) {
Write-Host 'No device errors found.'
}
else {
$deviceErrors | Format-Table -AutoSize
}
Complete-Check $name
}
catch {
Fail-Check -Name $name -Reason $_.Exception.Message
}
}
function Show-HardwareVendor {
$name = 'Hardware vendor'
Write-CheckTitle $name
try {
$system = Get-CimInstance -ClassName Win32_ComputerSystem
$vendorTool = switch -Regex ($system.Manufacturer) {
'Dell|Alienware' { 'Dell Command Update'; break }
'LENOVO' { 'Lenovo System Update or Commercial Vantage'; break }
'HP|Hewlett-Packard' { 'HP Image Assistant or HP Support Assistant'; break }
'Microsoft' { 'Surface app'; break }
'ASUSTeK' { 'MyASUS'; break }
'Acer' { 'AcerSense or Acer Care Center'; break }
'Micro-Star|MSI' { 'MSI Center'; break }
default { 'the manufacturer update tool' }
}
[pscustomobject]@{
Manufacturer = $system.Manufacturer
Model = $system.Model
VendorUpdateTool = $vendorTool
} | Format-List
Write-Host 'Windows Update reports only drivers offered by the configured update service.'
Complete-Check $name
}
catch {
Fail-Check -Name $name -Reason $_.Exception.Message
}
}
function Invoke-PnpmGlobalCheck {
$name = 'Global pnpm packages'
$pnpm = Get-Command pnpm -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($null -eq $pnpm) {
Write-CheckTitle $name
Skip-Check -Name $name -Reason 'pnpm is not installed.'
return
}
$corepack = Get-Command corepack -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($pnpm.Source -match '[\\/]Volta[\\/]pnpm\.exe$' -and $corepack) {
$originalPath = $env:Path
$originalPnpmHome = $env:PNPM_HOME
try {
if ([string]::IsNullOrWhiteSpace($env:PNPM_HOME)) {
$env:PNPM_HOME = Join-Path $env:LOCALAPPDATA 'pnpm'
}
$pnpmBin = Join-Path $env:PNPM_HOME 'bin'
$env:Path = "$($env:PNPM_HOME);$pnpmBin;$env:Path"
Invoke-ToolCheck `
-Name $name `
-Command 'corepack' `
-ArgumentList @('pnpm', 'outdated', '--global') `
-AllowedExitCodes @(0, 1)
}
finally {
$env:Path = $originalPath
$env:PNPM_HOME = $originalPnpmHome
}
return
}
Invoke-ToolCheck `
-Name $name `
-Command 'pnpm' `
-ArgumentList @('outdated', '--global') `
-AllowedExitCodes @(0, 1)
}
function Invoke-YarnGlobalCheck {
$name = 'Global Yarn Classic packages'
Write-CheckTitle $name
$yarn = Get-Command yarn -ErrorAction SilentlyContinue |
Select-Object -First 1
$npm = Get-Command npm -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($null -eq $yarn) {
Skip-Check -Name $name -Reason 'yarn is not installed.'
return
}
if ($null -eq $npm) {
Skip-Check -Name $name -Reason 'npm is required to check Yarn Classic global packages.'
return
}
try {
$LASTEXITCODE = 0
$directoryOutput = @(& $yarn global dir)
if ($LASTEXITCODE -ne 0) {
Fail-Check -Name $name -Reason "yarn global dir exited with code $LASTEXITCODE."
return
}
$globalDirectory = [string] (
$directoryOutput |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Select-Object -Last 1
)
$packageFile = Join-Path $globalDirectory 'package.json'
if (-not (Test-Path -LiteralPath $packageFile)) {
Write-Host 'No global Yarn Classic packages found.'
Complete-Check $name
return
}
$packageData = Get-Content -Raw -LiteralPath $packageFile |
ConvertFrom-Json
$dependenciesProperty = $packageData.PSObject.Properties['dependencies']
if (
$null -eq $dependenciesProperty -or
@($dependenciesProperty.Value.PSObject.Properties).Count -eq 0
) {
Write-Host 'No global Yarn Classic packages found.'
Complete-Check $name
return
}
$LASTEXITCODE = 0
& $npm --prefix $globalDirectory outdated --depth=0 | Out-Host
if (@(0, 1) -notcontains $LASTEXITCODE) {
Fail-Check -Name $name -Reason "npm exited with code $LASTEXITCODE."
return
}
Complete-Check $name
}
catch {
Fail-Check -Name $name -Reason $_.Exception.Message
}
}
function Invoke-PowerShellResourceCheck {
$name = 'PowerShell resources'
Write-CheckTitle $name
$hasPSResourceGet =
(Get-Command Get-InstalledPSResource -ErrorAction SilentlyContinue) -and
(Get-Command Find-PSResource -ErrorAction SilentlyContinue)
$hasPowerShellGet =
(Get-Command Get-InstalledModule -ErrorAction SilentlyContinue) -and
(Get-Command Find-Module -ErrorAction SilentlyContinue)
if (-not $hasPSResourceGet -and -not $hasPowerShellGet) {
Skip-Check -Name $name -Reason 'No supported PowerShell package manager is installed.'
return
}
try {
$provider = $null
$installedPackages = @()
if ($hasPSResourceGet) {
try {
$installedPackages = @(
Get-InstalledPSResource -ErrorAction Stop
)
}
catch {
if ($_.Exception.Message -notmatch "No match was found for package '\*'") {
throw
}
}
if ($installedPackages.Count -gt 0) {
$provider = 'PSResourceGet'
}
}
if ($installedPackages.Count -eq 0 -and $hasPowerShellGet) {
$installedPackages = @(
Get-InstalledModule -ErrorAction Stop
)
if ($installedPackages.Count -gt 0) {
$provider = 'PowerShellGet'
}
}
if ($installedPackages.Count -eq 0) {
Write-Host 'No package-managed PowerShell resources found.'
Complete-Check $name
return
}
$installedPackages = @(
$installedPackages |
Group-Object Name |
ForEach-Object {
$_.Group |
Sort-Object Version -Descending |
Select-Object -First 1
}
)
Write-Host "Checking $($installedPackages.Count) installed PowerShell resources."
$resourceUpdates = @(
foreach ($installed in $installedPackages) {
try {
$findParameters = @{
Name = $installed.Name
ErrorAction = 'Stop'
}
if (-not [string]::IsNullOrWhiteSpace($installed.Repository)) {
$findParameters.Repository = $installed.Repository
}
if ($provider -eq 'PSResourceGet') {
$latest = Find-PSResource @findParameters |
Sort-Object Version -Descending |
Select-Object -First 1
}
else {
$latest = Find-Module @findParameters |
Sort-Object Version -Descending |
Select-Object -First 1
}
if ($latest -and $installed.Version -lt $latest.Version) {
[pscustomobject]@{
Name = $installed.Name
Installed = $installed.Version
Available = $latest.Version
Repository = $latest.Repository
}
}
}
catch {
Write-Warning "Could not check $($installed.Name). $($_.Exception.Message)"
}
}
)
if ($resourceUpdates.Count -eq 0) {
Write-Host 'No outdated PowerShell resources found.'
}
else {
$resourceUpdates | Format-Table -AutoSize
}
Complete-Check $name
}
catch {
Fail-Check -Name $name -Reason $_.Exception.Message
}
}
if ($SkipWindowsUpdate) {
foreach ($name in @('Windows Update: Software', 'Windows Update: Driver')) {
Write-CheckTitle $name
Skip-Check -Name $name -Reason 'The SkipWindowsUpdate parameter was specified.'
}
}
else {
Invoke-WindowsUpdateChecks
}
Invoke-DeviceHealthCheck
Show-HardwareVendor
$applicationChecks = @(
@{
Name = 'WinGet applications and Microsoft Store packages'
Command = 'winget'
Arguments = @('upgrade', '--include-unknown')
ExitCodes = @(0)
},
@{
Name = 'Chocolatey packages'
Command = 'choco'
Arguments = @('outdated')
ExitCodes = @(0)
},
@{
Name = 'Scoop packages'
Command = 'scoop'
Arguments = @('status')
ExitCodes = @(0)
}
)
foreach ($check in $applicationChecks) {
$checkParameters = @{
Name = $check.Name
Command = $check.Command
ArgumentList = $check.Arguments
AllowedExitCodes = $check.ExitCodes
}
Invoke-ToolCheck @checkParameters
}
$developerChecks = @(
@{
Name = 'Default Python packages'
Command = 'py'
Arguments = @('-m', 'pip', 'list', '--outdated')
ExitCodes = @(0)
},
@{
Name = 'Global npm packages'
Command = 'npm'
Arguments = @('outdated', '--global', '--depth=0')
ExitCodes = @(0, 1)
},
@{
Name = '.NET workloads'
Command = 'dotnet'
Arguments = @('workload', 'list')
ExitCodes = @(0)
},
@{
Name = 'Rust toolchains'
Command = 'rustup'
Arguments = @('check')
ExitCodes = @(0)
},
@{
Name = 'Cargo packages through cargo-update'
Command = 'cargo-install-update'
Arguments = @('--list')
ExitCodes = @(0)
},
@{
Name = 'Ruby gems'
Command = 'gem'
Arguments = @('outdated')
ExitCodes = @(0)
},
@{
Name = 'Global Composer packages'
Command = 'composer'
Arguments = @('global', 'outdated', '--direct')
ExitCodes = @(0)
}
)
if ($SkipDeveloperTools) {
foreach ($name in @('Global pnpm packages', 'Global Yarn Classic packages')) {
Write-CheckTitle $name
Skip-Check -Name $name -Reason 'The SkipDeveloperTools parameter was specified.'
}
foreach ($check in $developerChecks) {
Write-CheckTitle $check.Name
Skip-Check -Name $check.Name -Reason 'The SkipDeveloperTools parameter was specified.'
}
}
else {
Invoke-PnpmGlobalCheck
Invoke-YarnGlobalCheck
foreach ($check in $developerChecks) {
$checkParameters = @{
Name = $check.Name
Command = $check.Command
ArgumentList = $check.Arguments
AllowedExitCodes = $check.ExitCodes
}
Invoke-ToolCheck @checkParameters
}
}
if ($SkipPowerShellResources) {
Write-CheckTitle 'PowerShell resources'
Skip-Check -Name 'PowerShell resources' -Reason 'The SkipPowerShellResources parameter was specified.'
}
else {
Invoke-PowerShellResourceCheck
}
Write-CheckTitle 'Summary'
[pscustomobject]@{
Completed = $script:CompletedChecks.Count
Skipped = $script:SkippedChecks.Count
Failed = $script:FailedChecks.Count
} | Format-List
if ($script:SkippedChecks.Count -gt 0) {
Write-Host "Skipped checks: $($script:SkippedChecks -join ', ')"
}
if ($script:FailedChecks.Count -gt 0) {
Write-Warning "Failed checks: $($script:FailedChecks -join ', ')"
exit 1
}
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment