2323 Luiz Hamilton Silva - @brazilianscriptguy
2424
2525.VERSION
26- Last Updated: 2026-02-12
26+ Last Updated: 2026-02-19
2727 Version: 3.01 (Hardened Edition)
2828#>
2929
@@ -141,6 +141,10 @@ $script:Config = @{
141141 CsvDir = " C:\Logs-TEMP\WSUS-GUI\CSV"
142142 SettingsFile = " C:\Logs-TEMP\WSUS-GUI\settings.json"
143143
144+ # Optional: allowlist for 'Decline legacy platforms' (regex/title patterns). Empty = disabled.
145+ LegacyPlatformTitlePatterns = @ ()
146+ LegacyPlatformMaxToDecline = 500
147+
144148 FqdnHostname = $null
145149 LogPath = $null
146150}
@@ -1560,8 +1564,72 @@ function Invoke-DeclineLegacyPlatforms {
15601564 [bool ]$UseSSL = $false
15611565 )
15621566
1563- Log- Message " Decline legacy platforms: option is currently not implemented in this build." " WARNING"
1564- Log- Message " Reason: classification/product matching is environment-specific and risky without an explicit allowlist." " WARNING"
1567+ # Safety: this is environment-specific. We only act when an explicit allowlist exists.
1568+ $patterns = @ ()
1569+ try {
1570+ if ($Config.ContainsKey (' LegacyPlatformTitlePatterns' ) -and $Config.LegacyPlatformTitlePatterns ) {
1571+ $patterns = @ ($Config.LegacyPlatformTitlePatterns ) | Where-Object { -not [string ]::IsNullOrWhiteSpace($_ ) }
1572+ }
1573+ } catch { }
1574+
1575+ if (@ ($patterns ).Count -eq 0 ) {
1576+ Log- Message " Decline legacy platforms: skipped (no allowlist configured in `$ Config.LegacyPlatformTitlePatterns)." " WARNING"
1577+ Log- Message " Tip: add regex/title patterns like 'Windows XP|Server 2003|Itanium' to enable this step safely." " INFO"
1578+ return
1579+ }
1580+
1581+ $maxToDecline = 500
1582+ try {
1583+ if ($Config.ContainsKey (' LegacyPlatformMaxToDecline' ) -and $Config.LegacyPlatformMaxToDecline ) {
1584+ $maxToDecline = [int ]$Config.LegacyPlatformMaxToDecline
1585+ }
1586+ } catch { $maxToDecline = 500 }
1587+
1588+ $wsus = Get-WSUSConnectionCached - Server $ServerName - Port $Port - UseSSL:$UseSSL
1589+
1590+ Log- Message (" Decline legacy platforms: starting. Patterns={0} Max={1}" -f ($patterns -join " ; " ), $maxToDecline ) " INFO"
1591+
1592+ $declined = 0
1593+ $matched = 0
1594+
1595+ try {
1596+ $scope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
1597+ $scope.ApprovedStates = [Microsoft.UpdateServices.Administration.ApprovedStates ]::NotApproved
1598+ $scope.IncludeDeclined = $false
1599+
1600+ $updates = $wsus.GetUpdates ($scope )
1601+
1602+ foreach ($u in $updates ) {
1603+ if ($script :CancelRequested ) { break }
1604+ if ($declined -ge $maxToDecline ) { break }
1605+
1606+ $hay = " $ ( $u.Title ) "
1607+ $isMatch = $false
1608+ foreach ($p in $patterns ) {
1609+ if ($hay -match $p ) { $isMatch = $true ; break }
1610+ }
1611+
1612+ if (-not $isMatch ) { continue }
1613+ $matched ++
1614+
1615+ try {
1616+ if (-not $u.IsDeclined ) {
1617+ $u.Decline ()
1618+ $declined ++
1619+ }
1620+ } catch {
1621+ Log- Message (" Decline legacy platforms: failed to decline update '{0}': {1}" -f $u.Title , $_.Exception.Message ) " WARNING"
1622+ }
1623+ }
1624+
1625+ Log- Message (" Decline legacy platforms: matched={0} declined={1} (Cancel={2})" -f $matched , $declined , $script :CancelRequested ) " INFO"
1626+ if ($declined -ge $maxToDecline ) {
1627+ Log- Message (" Decline legacy platforms: reached cap ({0}). Increase `$ Config.LegacyPlatformMaxToDecline if intentional." -f $maxToDecline ) " WARNING"
1628+ }
1629+ } catch {
1630+ Log- Message (" Decline legacy platforms: failed: {0}" -f $_.Exception.Message ) " ERROR"
1631+ throw
1632+ }
15651633}
15661634
15671635function Import-WsusAdminApi {
@@ -2640,12 +2708,7 @@ function Invoke-RunFromUi {
26402708
26412709 # --- Determine tasks from UI ---
26422710 $tasks = @ ()
2643-
2644-
2645- # --- Determine tasks from UI ---
2646- $tasks = @ ()
2647-
2648- # Use helpers to avoid StrictMode crashes when controls/vars differ between builds.
2711+ # Use helpers to avoid StrictMode crashes when controls/vars differ between builds.
26492712 $wizUnused = (Get-CheckboxValue - Name ' chkUnusedUpdates' - Default $false ) -or (Get-CheckboxValue - Name ' chkCleanupObsoleteUpdates' - Default $false )
26502713 $wizObsoletePC = (Get-CheckboxValue - Name ' chkObsoleteComputers' - Default $false ) -or (Get-CheckboxValue - Name ' chkCleanupObsoleteComputers' - Default $false )
26512714 $wizUnneeded = (Get-CheckboxValue - Name ' chkUnneededFiles' - Default $false ) -or (Get-CheckboxValue - Name ' chkCleanupUnneededContentFiles' - Default $false )
@@ -2719,50 +2782,18 @@ function Invoke-RunFromUi {
27192782 $UseSSL = $false
27202783 try { $UseSSL = [bool ]$chkUseSsl.Checked } catch { $UseSSL = $false }
27212784
2722- # --- Services/pool once per run ---
2723- Ensure- WsusServicesAndPool - Server $Server
2724-
2725- # --- Pre-warm WSUS connection cache once per run ---
2726- $null = Get-WSUSConnectionCached - Server $Server - Port $Port - UseSSL:$UseSSL
2727-
2728- # --- Split tasks ---
2785+ # --- Split tasks / apply Scope filter ---
27292786 $sqlTaskNames = @ (" CheckDB" , " CheckFragmentation" , " Reindex" , " ShrinkDB" , " BackupDB" )
2730- $sqlTasks = $tasks | Where-Object { $_ -in $sqlTaskNames }
2731- $wsusTasks = $tasks | Where-Object { $_ -notin $sqlTaskNames }
2732-
2733- # --- WSUS tasks ---
2734- foreach ($t in $wsusTasks ) {
2735- if ($script :CancelRequested ) { break }
2736-
2737- switch ($t ) {
2738- " DeclineUnapproved" {
2739- Decline- WSUSUnapproved - ServerName $Server - Port $Port - UseSSL:$UseSSL `
2740- - OlderThanDays (Get-NumericUpDownValue - Name ' nudDeclineUnapprovedDays' - Default 30 )
2741- }
2787+ $sqlTasksAll = @ ($tasks | Where-Object { $_ -in $sqlTaskNames })
2788+ $wsusTasksAll = @ ($tasks | Where-Object { $_ -notin $sqlTaskNames })
27422789
2743- " WsusCleanupWizard" {
2744- Run- WsusCleanupWizard `
2745- - IncludeUnusedUpdates:$wizUnused `
2746- - IncludeObsoleteComputers:$wizObsoletePC `
2747- - IncludeUnneededFiles:$wizUnneeded `
2748- - IncludeExpiredUpdates:$wizExpired `
2749- - IncludeSupersededUpdates:$wizSuperseded `
2750- - AttemptCompress:$wizCompress `
2751- - ServerName $Server - Port $Port - UseSSL:$UseSSL
2752- }
2753- " CompressRevisions" {
2754- # Separate task kept for backwards-compatibility; wizard can also trigger compress.
2755- Invoke-WsusCompressRevisionsHardened - ServerName $Server - Port $Port - UseSSL:$UseSSL
2756- }
2757- " DeclineLegacyPlatforms" {
2758- Invoke-DeclineLegacyPlatforms - ServerName $Server - Port $Port - UseSSL:$UseSSL
2759- }
2760-
2761-
2762- }
2790+ switch ($Scope ) {
2791+ ' SqlOnly' { $sqlTasks = $sqlTasksAll ; $wsusTasks = @ () }
2792+ ' WsusOnly' { $sqlTasks = @ (); $wsusTasks = $wsusTasksAll }
2793+ default { $sqlTasks = $sqlTasksAll ; $wsusTasks = $wsusTasksAll }
27632794 }
27642795
2765- # --- SQL tasks (WID/ SUSDB) ---
2796+ # --- 1) SQL tasks FIRST (reduces timeouts during WSUS cleanup on large SUSDB) ---
27662797 if (@ ($sqlTasks ).Count -gt 0 -and -not $script :CancelRequested ) {
27672798
27682799 $doCheckDB = ($sqlTasks -contains " CheckDB" )
@@ -2771,12 +2802,67 @@ function Invoke-RunFromUi {
27712802 $doShrink = ($sqlTasks -contains " ShrinkDB" )
27722803 $doBackup = ($sqlTasks -contains " BackupDB" )
27732804
2805+ Log- Message (" SQL maintenance starting (first): CheckDB={0} Frag={1} Reindex={2} Shrink={3} Backup={4}" -f `
2806+ $doCheckDB , $doFrag , $doReindex , $doShrink , $doBackup
2807+ ) " INFO"
2808+
27742809 Run- WIDMaintenance `
27752810 - DoCheckDB:$doCheckDB `
27762811 - DoCheckFragmentation:$doFrag `
27772812 - DoReindex:$doReindex `
27782813 - DoShrink:$doShrink `
27792814 - DoBackup:$doBackup
2815+
2816+ Log- Message " SQL maintenance finished." " INFO"
2817+ }
2818+
2819+ # --- 2) WSUS tasks SECOND (connect once; reuse cache) ---
2820+ if (@ ($wsusTasks ).Count -gt 0 -and -not $script :CancelRequested ) {
2821+
2822+ # Services/pool once per run (WSUS-only tasks)
2823+ Ensure- WsusServicesAndPool - Server $Server
2824+
2825+ # Pre-warm WSUS connection cache once per run
2826+ $null = Get-WSUSConnectionCached - Server $Server - Port $Port - UseSSL:$UseSSL
2827+
2828+ # Enforce a deterministic WSUS pipeline:
2829+ # 1) Cleanup wizard
2830+ # 2) Compress (if explicitly selected)
2831+ # 3) Legacy platform declines (if selected)
2832+ # 4) Decline unapproved LAST
2833+ $wsusOrder = @ (" WsusCleanupWizard" , " CompressRevisions" , " DeclineLegacyPlatforms" , " DeclineUnapproved" )
2834+
2835+ foreach ($t in ($wsusOrder | Where-Object { $wsusTasks -contains $_ })) {
2836+ if ($script :CancelRequested ) { break }
2837+
2838+ switch ($t ) {
2839+
2840+ " WsusCleanupWizard" {
2841+ Run- WsusCleanupWizard `
2842+ - IncludeUnusedUpdates:$wizUnused `
2843+ - IncludeObsoleteComputers:$wizObsoletePC `
2844+ - IncludeUnneededFiles:$wizUnneeded `
2845+ - IncludeExpiredUpdates:$wizExpired `
2846+ - IncludeSupersededUpdates:$wizSuperseded `
2847+ - AttemptCompress:$wizCompress `
2848+ - ServerName $Server - Port $Port - UseSSL:$UseSSL
2849+ }
2850+
2851+ " CompressRevisions" {
2852+ # Separate task kept for backwards-compatibility; wizard can also trigger compress.
2853+ Invoke-WsusCompressRevisionsHardened - ServerName $Server - Port $Port - UseSSL:$UseSSL
2854+ }
2855+
2856+ " DeclineLegacyPlatforms" {
2857+ Invoke-DeclineLegacyPlatforms - ServerName $Server - Port $Port - UseSSL:$UseSSL
2858+ }
2859+
2860+ " DeclineUnapproved" {
2861+ Decline- WSUSUnapproved - ServerName $Server - Port $Port - UseSSL:$UseSSL `
2862+ - OlderThanDays (Get-NumericUpDownValue - Name ' nudDeclineUnapprovedDays' - Default 30 )
2863+ }
2864+ }
2865+ }
27802866 }
27812867
27822868 Log- Message " Run finished." " INFO"
0 commit comments