Skip to content

Commit

Permalink
Changes in the update script
Browse files Browse the repository at this point in the history
  • Loading branch information
Pablo Herranz Ramírez committed Jan 29, 2025
1 parent 881b023 commit dd21f56
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 68 deletions.
250 changes: 184 additions & 66 deletions data/wsl/UpdateInstall.ps1
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
}
4 changes: 2 additions & 2 deletions tests/wsl/install/update_windows.pm
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ sub run {

my $vbs_url = data_url("wsl/UpdateInstall.ps1");
$self->open_powershell_as_admin;
$self->run_in_powershell(cmd => "Invoke-WebRequest -Uri \"$vbs_url\" -OutFile \"C:\\UpdateInstall.ps1\"");
$self->run_in_powershell(cmd => "Invoke-WebRequest -Uri \"$vbs_url\" -OutFile \"$env:TEMP\\UpdateInstall.ps1\"");
$self->run_in_powershell(cmd => "Set-ExecutionPolicy Bypass -Scope CurrentUser -Force");
$self->run_in_powershell(
cmd => 'cd \\; $port.WriteLine($(.\\UpdateInstall.ps1))',
cmd => '$env:TEMP\\UpdateInstall.ps1 -NonInteractive',
code => sub {
die("Update script finished unespectedly or timed out...")
unless wait_serial('0', timeout => 3600);
Expand Down

0 comments on commit dd21f56

Please sign in to comment.