Public/WindowsUpdate.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Windows Update functions for DriverManagement module #> function Install-WindowsUpdates { <# .SYNOPSIS Installs Windows cumulative updates .DESCRIPTION Uses PSWindowsUpdate to install Windows updates, excluding drivers by default .PARAMETER IncludeDrivers Include drivers from Windows Update (not recommended with OEM drivers) .PARAMETER Categories Update categories to include .PARAMETER NoReboot Suppress automatic reboot .EXAMPLE Install-WindowsUpdates -NoReboot .OUTPUTS DriverUpdateResult object #> [CmdletBinding(SupportsShouldProcess)] [OutputType('DriverUpdateResult')] param( [Parameter()] [switch]$IncludeDrivers, [Parameter()] [string[]]$Categories = @('Security Updates', 'Critical Updates', 'Updates'), [Parameter()] [switch]$NoReboot ) Assert-Elevation -Operation "Installing Windows Updates" $result = [DriverUpdateResult]::new() $result.CorrelationId = $script:CorrelationId # Ensure PSWindowsUpdate is available if (-not (Get-Module -ListAvailable -Name PSWindowsUpdate)) { try { Write-DriverLog -Message "Installing PSWindowsUpdate module" -Severity Info Install-Module -Name PSWindowsUpdate -Force -Scope AllUsers -ErrorAction Stop } catch { $result.Success = $false $result.Message = "Failed to install PSWindowsUpdate: $($_.Exception.Message)" $result.ExitCode = 1 return $result } } # Remove module if already loaded to avoid alias conflicts if (Get-Module -Name PSWindowsUpdate) { Remove-Module -Name PSWindowsUpdate -Force -ErrorAction SilentlyContinue } # Remove PSWindowsUpdate aliases if they exist (they persist after module removal) $pswuAliases = @('Get-WUList', 'Get-WUInstall', 'Install-WindowsUpdate', 'Download-WindowsUpdate', 'Hide-WindowsUpdate', 'UnHide-WindowsUpdate', 'Show-WindowsUpdate', 'Uninstall-WindowsUpdate', 'Clear-WUJob') foreach ($alias in $pswuAliases) { Remove-Item -Path "Alias:$alias" -Force -ErrorAction SilentlyContinue } # Import module, suppressing alias warnings (they're harmless if module was already loaded) # Use error redirection to suppress alias creation warnings during import $originalErrorAction = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' # Redirect all output and filter out alias errors $importOutput = Import-Module PSWindowsUpdate -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue 2>&1 # Check if import actually failed (not just alias warnings) $realErrors = $importOutput | Where-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { # Only keep errors that aren't alias-related return $_.Exception.Message -notmatch 'alias.*already exists' } return $false } $ErrorActionPreference = $originalErrorAction # If there were real errors (not just alias warnings), handle them if ($realErrors) { $result.Success = $false $result.Message = "Failed to import PSWindowsUpdate: $($realErrors[0].Exception.Message)" $result.ExitCode = 1 return $result } # Verify module loaded successfully if (-not (Get-Module -Name PSWindowsUpdate)) { $result.Success = $false $result.Message = "Failed to import PSWindowsUpdate module" $result.ExitCode = 1 return $result } # Ensure all PSWindowsUpdate operations are non-interactive (no prompts). # PSWindowsUpdate can still prompt for confirmations or Microsoft Update registration in some environments. $oldConfirmPreference = $ConfirmPreference $oldProgressPreference = $ProgressPreference $ConfirmPreference = 'None' $ProgressPreference = 'SilentlyContinue' $oldPSDefaultParameterValues = $null try { $oldPSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } catch { $oldPSDefaultParameterValues = @{} } $global:PSDefaultParameterValues['*:Confirm'] = $false $global:PSDefaultParameterValues['*:WhatIf'] = $false try { # Register Microsoft Update service manager silently (prevents interactive prompts in some cases) if (Get-Command Add-WUServiceManager -ErrorAction SilentlyContinue) { Add-WUServiceManager -MicrosoftUpdate -Confirm:$false -ErrorAction SilentlyContinue | Out-Null } } catch { # Non-fatal; PSWindowsUpdate may still function without explicit registration Write-DriverLog -Message "Microsoft Update registration step encountered a warning: $($_.Exception.Message)" -Severity Warning } Write-DriverLog -Message "Scanning for Windows Updates" -Severity Info $getParams = @{ MicrosoftUpdate = $true Category = $Categories Verbose = $true } if (-not $IncludeDrivers) { $getParams.NotCategory = 'Drivers' } $updates = Get-WindowsUpdate @getParams -Confirm:$false -ErrorAction SilentlyContinue if (-not $updates) { $result.Success = $true $result.Message = "No Windows Updates available" $result.ExitCode = 0 Write-DriverLog -Message $result.Message -Severity Info return $result } Write-DriverLog -Message "Found $($updates.Count) Windows Updates" -Severity Info ` -Context @{ Updates = ($updates | Select-Object KB, Title) } if ($PSCmdlet.ShouldProcess("$($updates.Count) Windows Updates", "Install")) { $installParams = @{ MicrosoftUpdate = $true AcceptAll = $true IgnoreReboot = $true Verbose = $true Confirm = $false } if (-not $IncludeDrivers) { $installParams.NotCategory = 'Drivers' } $installResults = Install-WindowsUpdate @installParams -ErrorAction SilentlyContinue $rebootStatus = Get-WURebootStatus -ErrorAction SilentlyContinue $result.Success = $true $result.Message = "Installed $($installResults.Count) Windows Updates" $result.UpdatesApplied = $installResults.Count $result.RebootRequired = $rebootStatus.RebootRequired $result.ExitCode = if ($result.RebootRequired) { 3010 } else { 0 } $result.Details = @{ Updates = ($installResults | Select-Object KB, Title, Result) } } Write-DriverLog -Message $result.Message -Severity Info -Context $result.ToHashtable() return $result } finally { # Restore preferences even if PSWindowsUpdate throws $ConfirmPreference = $oldConfirmPreference $ProgressPreference = $oldProgressPreference if ($oldPSDefaultParameterValues -ne $null) { $global:PSDefaultParameterValues = $oldPSDefaultParameterValues } } function Get-DriverComplianceStatus { <# .SYNOPSIS Gets the current driver compliance status .DESCRIPTION Reads the compliance status file .EXAMPLE Get-DriverComplianceStatus .OUTPUTS DriverComplianceStatus object #> [CmdletBinding()] [OutputType('DriverComplianceStatus')] param() $config = $script:ModuleConfig $compliancePath = $config.CompliancePath if (-not (Test-Path $compliancePath)) { $status = [DriverComplianceStatus]::new() $status.Status = [ComplianceStatus]::Unknown $status.Message = "No compliance check performed yet" return $status } try { $json = Get-Content $compliancePath -Raw return [DriverComplianceStatus]::FromJson($json) } catch { Write-DriverLog -Message "Failed to read compliance status: $($_.Exception.Message)" -Severity Warning $status = [DriverComplianceStatus]::new() $status.Status = [ComplianceStatus]::Error $status.Message = $_.Exception.Message return $status } } function Update-DriverComplianceStatus { <# .SYNOPSIS Updates the driver compliance status file .DESCRIPTION Writes current compliance state for detection scripts .PARAMETER Status Compliance status .PARAMETER UpdatesApplied Number of updates applied .PARAMETER UpdatesPending Number of updates pending .PARAMETER Message Status message .EXAMPLE Update-DriverComplianceStatus -Status Compliant -UpdatesApplied 5 #> [CmdletBinding()] param( [Parameter(Mandatory)] [ComplianceStatus]$Status, [Parameter()] [int]$UpdatesApplied = 0, [Parameter()] [int]$UpdatesPending = 0, [Parameter()] [string]$Message = '' ) $config = $script:ModuleConfig # Ensure directory exists $complianceDir = Split-Path $config.CompliancePath -Parent if (-not (Test-Path $complianceDir)) { New-Item -Path $complianceDir -ItemType Directory -Force | Out-Null } $oemInfo = Get-OEMInfo $compliance = [DriverComplianceStatus]::new() $compliance.Version = $config.ModuleVersion $compliance.Status = $Status $compliance.OEM = $oemInfo.OEM $compliance.Model = $oemInfo.Model $compliance.UpdatesApplied = $UpdatesApplied $compliance.UpdatesPending = $UpdatesPending $compliance.Message = $Message $compliance.CorrelationId = $script:CorrelationId $compliance.ToHashtable() | ConvertTo-Json -Depth 3 | Set-Content -Path $config.CompliancePath -Encoding UTF8 Write-DriverLog -Message "Updated compliance status: $Status" -Severity Info -Context $compliance.ToHashtable() } |