Private/Get-WUPendingUpdates.ps1
|
function Get-WUPendingUpdates { <# .SYNOPSIS Retrieves policy-scoped pending Windows Updates with a bounded WUA search. .DESCRIPTION Runs the synchronous Windows Update Agent search in an isolated child process so an unhealthy WUA client cannot block the calling diagnostic indefinitely. On Intune, Windows Update for Business, and Windows Autopatch devices, the result contains only updates visible under the effective update policy. It does not bypass deployment approvals, rollout timing, safeguard holds, or scan-source configuration. #> [CmdletBinding()] param( [string]$LogPath, [ValidateRange(5, 1800)] [int]$SearchTimeoutSeconds = 120 ) Write-WULog -Message 'Checking for policy-scoped pending Windows Updates' -LogPath $LogPath Write-WULog -Message "WUA search timeout: $SearchTimeoutSeconds seconds" -LogPath $LogPath $searchEnvelope = Invoke-WUSearch ` -Criteria 'IsInstalled=0' ` -TimeoutSeconds $SearchTimeoutSeconds ` -HistoryLimit 200 ` -LogPath $LogPath $updateHistory = @{} foreach ($entry in @($searchEnvelope.History)) { if ($entry.KBNumber) { if (-not $updateHistory.ContainsKey($entry.KBNumber)) { $updateHistory[$entry.KBNumber] = @() } $updateHistory[$entry.KBNumber] += $entry } } $pendingUpdates = foreach ($update in @($searchEnvelope.Updates)) { $effectiveSize = if ($update.ActualDownloadSize -gt 0) { [int64]$update.ActualDownloadSize } else { [int64]$update.MaxDownloadSize } $sizeKB = if ($effectiveSize -gt 0) { [math]::Round($effectiveSize / 1KB, 1) } else { 0 } $importance = if ($update.MsrcSeverity) { $update.MsrcSeverity } elseif ($update.AutoSelectOnWebSites) { 'Important' } else { 'Optional' } $kbNumber = $null if ($update.KBArticleIDs -and @($update.KBArticleIDs).Count -gt 0) { $kbNumber = 'KB{0}' -f @($update.KBArticleIDs)[0] } elseif ($update.Title -match 'KB(\d+)') { $kbNumber = "KB$($matches[1])" } $previousAttempts = 0 $lastAttemptDate = $null $lastAttemptResult = $null if ($kbNumber -and $updateHistory.ContainsKey($kbNumber)) { $attempts = @($updateHistory[$kbNumber]) $previousAttempts = $attempts.Count $lastAttempt = $attempts | Sort-Object Date -Descending | Select-Object -First 1 if ($lastAttempt) { $lastAttemptDate = $lastAttempt.Date $lastAttemptResult = switch ([int]$lastAttempt.ResultCode) { 0 { 'Not Started' } 1 { 'In Progress' } 2 { 'Succeeded' } 3 { 'Succeeded with Errors' } 4 { 'Failed (0x{0:X8})' -f ([uint32]$lastAttempt.HResult) } 5 { 'Aborted' } default { "Unknown ($($lastAttempt.ResultCode))" } } } } $downloadStatus = if ($update.IsDownloaded) { 'Downloaded (100%)' } elseif ($update.DownloadContentsCount -gt 0) { 'Partially Downloaded' } else { 'Not Downloaded (0%)' } $installationState = if ($update.IsDownloaded) { 'Pending Install' } elseif ($update.DownloadContentsCount -gt 0) { 'Downloading' } else { 'Pending Download' } if ($previousAttempts -gt 0 -and $lastAttemptResult -match 'Failed|Aborted') { $installationState = "Retry Pending ($previousAttempts previous attempts)" } [PSCustomObject]@{ Title = $update.Title KBNumber = $kbNumber KBArticleIDs = @($update.KBArticleIDs) SecurityBulletinIDs = @($update.SecurityBulletinIDs) Description = $update.Description Categories = @($update.Categories) -join ', ' Type = $update.Type Importance = $importance SizeKB = $sizeKB SizeMB = [math]::Round($sizeKB / 1024, 1) MaxDownloadSizeMB = [math]::Round(([int64]$update.MaxDownloadSize) / 1MB, 1) IsEstimatedSize = $update.ActualDownloadSize -le 0 DownloadContentsCount = $update.DownloadContentsCount DownloadStatus = $downloadStatus IsDownloaded = [bool]$update.IsDownloaded InstallationState = $installationState PreviousAttempts = $previousAttempts LastAttemptDate = $lastAttemptDate LastAttemptResult = $lastAttemptResult RebootRequired = [bool]$update.RebootRequired IsHidden = [bool]$update.IsHidden IsMandatory = [bool]$update.IsMandatory ReleaseDate = $update.ReleaseDate SupportUrl = $update.SupportUrl UpdateID = $update.UpdateID RevisionNumber = $update.RevisionNumber ScanScope = 'Windows Update Agent policy-scoped catalog' } } $pendingUpdates = @($pendingUpdates) Write-WULog -Message "Policy-scoped WUA search completed; pending updates: $($pendingUpdates.Count)" -LogPath $LogPath if ($pendingUpdates.Count -gt 0) { $totalSizeMB = ($pendingUpdates | Measure-Object -Property SizeMB -Sum).Sum $retryCount = @( $pendingUpdates | Where-Object { $_.PreviousAttempts -gt 0 -and $_.LastAttemptResult -match 'Failed|Aborted' } ).Count Write-WULog -Message " Estimated total size: $([math]::Round($totalSizeMB, 1)) MB" -LogPath $LogPath Write-WULog -Message " Previously failed and pending retry: $retryCount" -LogPath $LogPath } return $pendingUpdates } |