forked from os-autoinst/os-autoinst-distri-opensuse
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Pablo Herranz Ramírez
committed
Jan 29, 2025
1 parent
881b023
commit dd21f56
Showing
2 changed files
with
186 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,84 +1,202 @@ | ||
# Define a function to log messages with timestamps | ||
function LogMessage { | ||
param ( | ||
[string]$Message, | ||
[string]$Color = "White" | ||
) | ||
$Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") | ||
Write-Host "$Timestamp - $Message" -ForegroundColor $Color | ||
} | ||
|
||
# Start logging | ||
LogMessage "Starting Windows Update process..." | ||
# Automated Windows Update Tester | ||
# Features: Non-interactive, JSON reporting, Idempotent operations, CI/CD integration | ||
|
||
# Create Update Session | ||
$Session = New-Object -ComObject Microsoft.Update.Session | ||
$Searcher = $Session.CreateUpdateSearcher() | ||
param( | ||
[switch]$NonInteractive, | ||
[switch]$AutoReboot, | ||
[string]$StatePath = "$env:TEMP\UpdateTestState.json" | ||
) | ||
|
||
# Search for Updates | ||
LogMessage "Searching for updates..." | ||
$SearchResult = $Searcher.Search("IsInstalled=0") | ||
### Configuration | ||
$MAX_RETRIES = 3 | ||
$RETRY_DELAY = 30 # seconds | ||
|
||
# Check if updates are available | ||
if ($SearchResult.Updates.Count -eq 0) { | ||
LogMessage "No updates found." -Color "Yellow" | ||
return 0 | ||
### Unified Logging System | ||
$GLOBAL_LOG = @() | ||
function LogEvent { | ||
param($Message, $Level="INFO", $Data=$null) | ||
$entry = @{ | ||
Timestamp = [datetime]::UtcNow.ToString("o") | ||
Level = $Level | ||
Message = $Message | ||
Data = $Data | ||
} | ||
$GLOBAL_LOG += $entry | ||
Write-Host "[$($entry.Level)] $($entry.Message)" | ||
} | ||
|
||
# Display updates found | ||
LogMessage "$($SearchResult.Updates.Count) update(s) found." | ||
$UpdatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl | ||
foreach ($Update in $SearchResult.Updates) { | ||
LogMessage "Update found: $($Update.Title)" | ||
$UpdatesToInstall.Add($Update) | Out-Null | ||
### State Manager | ||
class UpdateState { | ||
[string]$RunId = [guid]::NewGuid().ToString() | ||
[datetime]$StartTime = [datetime]::UtcNow | ||
[array]$DiscoveredUpdates = @() | ||
[array]$InstalledUpdates = @() | ||
[array]$PendingReboots = @() | ||
[int]$RetryCount = 0 | ||
[bool]$RebootPerformed = $false | ||
} | ||
|
||
# Download updates individually | ||
LogMessage "Downloading updates..." | ||
foreach ($Update in $UpdatesToInstall) { | ||
$Downloader = $Session.CreateUpdateDownloader() | ||
$SingleUpdateColl = New-Object -ComObject Microsoft.Update.UpdateColl | ||
$SingleUpdateColl.Add($Update) | Out-Null | ||
$Downloader.Updates = $SingleUpdateColl | ||
|
||
LogMessage "Downloading: $($Update.Title)..." | ||
function Get-State { | ||
param($Path) | ||
try { | ||
$DownloadResult = $Downloader.Download() | ||
if ($DownloadResult.ResultCode -eq 2) { | ||
LogMessage "Downloaded: $($Update.Title) successfully." -Color "Green" | ||
} else { | ||
LogMessage "Download failed for: $($Update.Title)" -Color "Yellow" | ||
return 1 | ||
if (Test-Path $Path) { | ||
$content = Get-Content $Path -Raw | ||
return $content | ConvertFrom-Json | ||
} | ||
} catch { | ||
LogMessage "Error downloading $($Update.Title): $_" -Color "Red" | ||
return 1 | ||
$port.WriteLine(LogEvent "State load failed: $_" "ERROR") | ||
} | ||
return [UpdateState]::new() | ||
} | ||
|
||
# Install updates individually | ||
LogMessage "Installing updates..." | ||
foreach ($Update in $UpdatesToInstall) { | ||
$Installer = $Session.CreateUpdateInstaller() | ||
$SingleUpdateColl = New-Object -ComObject Microsoft.Update.UpdateColl | ||
$SingleUpdateColl.Add($Update) | Out-Null | ||
$Installer.Updates = $SingleUpdateColl | ||
function Save-State { | ||
param($State, $Path) | ||
$State.EndTime = [datetime]::UtcNow | ||
$State | ConvertTo-Json -Depth 5 | Out-File $Path | ||
} | ||
|
||
### Core Update Operations | ||
function Get-WindowsUpdates { | ||
$session = New-Object -ComObject Microsoft.Update.Session | ||
$searcher = $session.CreateUpdateSearcher() | ||
$result = $searcher.Search("IsInstalled=0 and IsHidden=0") | ||
|
||
LogMessage "Installing: $($Update.Title)..." | ||
try { | ||
$InstallResult = $Installer.Install() | ||
if ($InstallResult.ResultCode -eq 2) { | ||
LogMessage "Successfully installed: $($Update.Title)" -Color "Green" | ||
} else { | ||
LogMessage "Installation failed for: $($Update.Title). Result code: $($InstallResult.ResultCode)" -Color "Yellow" | ||
return 1 | ||
$updates = foreach ($u in $result.Updates) { | ||
@{ | ||
Id = $u.Identity.UpdateID | ||
Title = $u.Title | ||
KB = ($u.KBArticleIDs -join ', ') | ||
RebootRequired = $u.InstallationBehavior.RebootBehavior -gt 0 | ||
Categories = $u.Categories.Name | ||
} | ||
} catch { | ||
LogMessage "Error occurred during installation of $($Update.Title): $_" -Color "Red" | ||
return 1 | ||
} | ||
|
||
return $updates | ||
} | ||
|
||
function Install-UpdateBatch { | ||
param($Updates, $Session) | ||
|
||
$installer = $Session.CreateUpdateInstaller() | ||
$installer.Updates = $Updates | ||
$installer.AllowRestarts = $false | ||
|
||
$result = $installer.Install() | ||
return @{ | ||
ResultCode = $result.ResultCode | ||
RebootRequired = $result.RebootRequired | ||
FailedUpdates = if ($result.ResultCode -ne 2) { $Updates } else { @() } | ||
} | ||
} | ||
|
||
### Verification Systems | ||
function Test-UpdateSuccess { | ||
param($Update) | ||
$session = New-Object -ComObject Microsoft.Update.Session | ||
$history = $session.CreateUpdateSearcher().QueryHistory(0, 100) | | ||
Where-Object { $_.Title -eq $Update.Title -and $_.Date -gt (Get-Date).AddDays(-1) } | ||
|
||
return [PSCustomObject]@{ | ||
Installed = $history.ResultCode -contains 2 | ||
PendingReboot = Test-RebootRequired | ||
VerificationTime = [datetime]::UtcNow | ||
} | ||
} | ||
|
||
# Completion message | ||
LogMessage "Windows Update process completed." | ||
return 0 | ||
### Main Workflow | ||
try { | ||
# Load state | ||
$state = Get-State -Path $StatePath | ||
|
||
# Phase 1: Discovery | ||
if (-not $state.DiscoveredUpdates) { | ||
$port.WriteLine(LogEvent "Starting update discovery" "INFO") | ||
$state.DiscoveredUpdates = Get-WindowsUpdates | ||
Save-State $state $StatePath | ||
} | ||
|
||
# Phase 2: Installation | ||
while ($state.RetryCount -lt $MAX_RETRIES) { | ||
try { | ||
$session = New-Object -ComObject Microsoft.Update.Session | ||
$updatesToInstall = $state.DiscoveredUpdates | | ||
Where-Object { $_.Id -notin $state.InstalledUpdates.Id } | ||
|
||
if (-not $updatesToInstall) { | ||
$port.WriteLine(LogEvent "All updates installed" "INFO") | ||
break | ||
} | ||
|
||
$port.WriteLine(LogEvent "Installing batch of updates" "INFO" @{Count=$updatesToInstall.Count}) | ||
$result = Install-UpdateBatch -Updates $updatesToInstall -Session $session | ||
|
||
if ($result.ResultCode -eq 2) { | ||
$state.InstalledUpdates += $updatesToInstall | ||
$state.PendingReboots += $updatesToInstall | Where-Object RebootRequired | ||
Save-State $state $StatePath | ||
break | ||
} else { | ||
$state.RetryCount++ | ||
$port.WriteLine(LogEvent "Installation failed, retry $($state.RetryCount)/$MAX_RETRIES" "WARN") | ||
Start-Sleep -Seconds $RETRY_DELAY | ||
} | ||
} catch { | ||
$port.WriteLine(LogEvent "Installation error: $_" "ERROR") | ||
$port.WriteLine('2') | ||
return | ||
} | ||
} | ||
|
||
# Phase 3: Reboot Management | ||
if ($state.PendingReboots -and $AutoReboot) { | ||
$port.WriteLine(LogEvent "Automated reboot initiated" "INFO") | ||
Save-State $state $StatePath | ||
if (-not $NonInteractive) { | ||
Restart-Computer -Force | ||
} | ||
$port.WriteLine('0') | ||
return | ||
} | ||
|
||
# Phase 4: Post-Reboot Verification | ||
if ($state.RebootPerformed) { | ||
$port.WriteLine(LogEvent "Post-reboot verification started" "INFO") | ||
$verificationResults = foreach ($update in $state.InstalledUpdates) { | ||
$status = Test-UpdateSuccess $update | ||
[PSCustomObject]@{ | ||
Update = $update.Title | ||
KB = $update.KB | ||
Verified = $status.Installed | ||
PendingReboot = $status.PendingReboot | ||
} | ||
} | ||
|
||
$state | Add-Member -NotePropertyName Verification -NotePropertyValue $verificationResults | ||
Save-State $state $StatePath | ||
} | ||
|
||
# Generate CI Report | ||
$report = @{ | ||
RunId = $state.RunId | ||
TotalUpdates = $state.DiscoveredUpdates.Count | ||
InstalledUpdates = $state.InstalledUpdates.Count | ||
FailedUpdates = $state.DiscoveredUpdates.Count - $state.InstalledUpdates.Count | ||
PendingReboot = [bool]$state.PendingReboots | ||
VerificationStatus = if ($state.Verification) { | ||
@{ | ||
Verified = ($state.Verification | Where-Object Verified).Count | ||
Failed = ($state.Verification | Where-Object { -not $_.Verified }).Count | ||
} | ||
} else { $null } | ||
} | ||
|
||
$report | ConvertTo-Json -Depth 3 | Out-File "$env:TEMP\UpdateTestReport.json" | ||
$port.WriteLine(LogEvent "Test cycle completed" "INFO" $report) | ||
$port.WriteLine('0') | ||
return | ||
|
||
} catch { | ||
$port.WriteLine(LogEvent "Critical error: $_" "ERROR") | ||
$port.WriteLine('1') | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters