Public/Get-BuildUpgradeStatus.ps1

function Get-BuildUpgradeStatus {
    <#
    .SYNOPSIS
        Monitors the status of an in-progress Windows build upgrade.
 
    .DESCRIPTION
        Get-BuildUpgradeStatus checks for active Windows upgrade processes and reports
        current progress, stage, and recent activity. Useful for monitoring unattended
        upgrades initiated by Invoke-BuildUpgrade.
 
    .PARAMETER Detailed
        Display detailed recent log entries from the setup process.
 
    .PARAMETER Follow
        Continuously monitor the upgrade progress (refreshes every 10 seconds).
        Press Ctrl+C to exit.
 
    .EXAMPLE
        Get-BuildUpgradeStatus
         
        Shows current upgrade status and progress percentage.
 
    .EXAMPLE
        Get-BuildUpgradeStatus -Detailed
         
        Shows current status with recent log entries.
 
    .EXAMPLE
        Get-BuildUpgradeStatus -Follow
         
        Continuously monitors upgrade progress until completion.
 
    .OUTPUTS
        PSCustomObject with upgrade status information:
        - InProgress: Boolean indicating if upgrade is running
        - ProcessId: Setup process ID (if running)
        - Progress: Current progress percentage
        - Stage: Current upgrade stage
        - RuntimeMinutes: How long upgrade has been running
        - LastActivity: Most recent log entry
        - LogPath: Path to active setup log
 
    .NOTES
        Name: Get-BuildUpgradeStatus
        Author: WindowsUpdateTools Module
        Version: 1.0.0
         
        Requires an active upgrade initiated by Invoke-BuildUpgrade or manual setup.exe execution.
    #>


    [CmdletBinding()]
    param(
        [switch]$Detailed,
        [switch]$Follow
    )

    begin {
        $script:LoopCount = 0
    }

    process {
        do {
            # Clear screen if following
            if ($Follow -and $script:LoopCount -gt 0) {
                Clear-Host
            }
            $script:LoopCount++

            # Check for running setup processes
            $setupProcesses = Get-Process | Where-Object { $_.ProcessName -eq "setup" }
            
            $status = [PSCustomObject]@{
                InProgress = $false
                ProcessId = $null
                Progress = $null
                ProgressAge = $null
                Stage = "Not Running"
                RuntimeMinutes = $null
                LastActivity = $null
                LogPath = $null
                StagingDirectory = $null
                RecentErrors = @()
            }

            if ($setupProcesses) {
                $mainProcess = $setupProcesses | Sort-Object StartTime | Select-Object -First 1
                $status.InProgress = $true
                $status.ProcessId = $mainProcess.Id
                $status.RuntimeMinutes = [math]::Round(((Get-Date) - $mainProcess.StartTime).TotalMinutes, 1)
            }

            # Check for staging directory and logs
            if (Test-Path "C:\`$WINDOWS.~BT") {
                $status.StagingDirectory = "C:\`$WINDOWS.~BT"
                
                # Try to read setupact.log for progress
                $setupActLog = "C:\`$WINDOWS.~BT\Sources\Panther\setupact.log"
                if (Test-Path $setupActLog) {
                    $status.LogPath = $setupActLog
                    $status.InProgress = $true
                    
                    # Search entire file for most recent "Overall progress" (most accurate)
                    # This is slower but necessary because progress can be far back in large logs
                    $progressLines = Get-Content $setupActLog -ErrorAction SilentlyContinue | 
                        Select-String "Overall progress:\s*\[(\d+)%\]" | 
                        Select-Object -Last 1
                    
                    # If no Overall progress, fall back to any progress in recent logs
                    if (-not $progressLines) {
                        $recentLogs = Get-Content $setupActLog -Tail 10000 -ErrorAction SilentlyContinue
                        $progressLines = $recentLogs | Select-String "progress:\s*\[(\d+)%\]" | Select-Object -Last 1
                    }
                    
                    # Get recent logs for stage detection (after finding progress)
                    $recentLogs = Get-Content $setupActLog -Tail 5000 -ErrorAction SilentlyContinue
                    if ($progressLines) {
                        if ($progressLines.Line -match '\[(\d+)%\]') {
                            $status.Progress = [int]$matches[1]
                            
                            # Extract timestamp of progress update
                            if ($progressLines.Line -match '^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})') {
                                $progressTime = [DateTime]::ParseExact($matches[1], 'yyyy-MM-dd HH:mm:ss', $null)
                                $status.ProgressAge = [math]::Round(((Get-Date) - $progressTime).TotalMinutes, 1)
                            }
                        }
                    }
                    
                    # Determine stage based on log content and progress
                    $lastFewLines = $recentLogs | Select-Object -Last 50 | Out-String
                    if ($lastFewLines -match "DISM|driver") {
                        $status.Stage = "Installing Drivers"
                    } elseif ($lastFewLines -match "compatibility|compat|appraiser") {
                        $status.Stage = "Compatibility Check"
                    } elseif ($lastFewLines -match "download|acquire") {
                        $status.Stage = "Downloading Updates"
                    } elseif ($lastFewLines -match "install|apply") {
                        $status.Stage = "Installing"
                    } elseif ($lastFewLines -match "finalize|complete") {
                        $status.Stage = "Finalizing"
                    } elseif ($status.Progress -and $status.Progress -lt 30) {
                        $status.Stage = "Initializing"
                    } elseif ($status.Progress -and $status.Progress -lt 60) {
                        $status.Stage = "Preparing Installation"
                    } elseif ($status.Progress -and $status.Progress -lt 90) {
                        $status.Stage = "Processing"
                    } else {
                        $status.Stage = "In Progress"
                    }
                    
                    # Get last activity
                    $lastLogEntry = $recentLogs | Select-Object -Last 1
                    if ($lastLogEntry) {
                        $status.LastActivity = $lastLogEntry
                    }
                    
                    # Check for errors
                    $errorLog = "C:\`$WINDOWS.~BT\Sources\Panther\setuperr.log"
                    if (Test-Path $errorLog) {
                        $errors = Get-Content $errorLog -Tail 10 -ErrorAction SilentlyContinue
                        if ($errors) {
                            $status.RecentErrors = $errors
                        }
                    }
                }
            }

            # Display status
            Write-Host "`n=== Windows Build Upgrade Status ===" -ForegroundColor Cyan
            Write-Host "Timestamp: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`n" -ForegroundColor Gray
            
            if ($status.InProgress) {
                Write-Host "Status: " -NoNewline
                Write-Host "IN PROGRESS" -ForegroundColor Green
                
                if ($status.ProcessId) {
                    Write-Host "Process ID: $($status.ProcessId)" -ForegroundColor White
                }
                
                if ($status.RuntimeMinutes) {
                    Write-Host "Runtime: $($status.RuntimeMinutes) minutes" -ForegroundColor White
                }
                
                Write-Host "Stage: " -NoNewline
                Write-Host $status.Stage -ForegroundColor Yellow
                
                if ($null -ne $status.Progress) {
                    $progressBar = "[" + ("=" * [math]::Floor($status.Progress / 5)) + (" " * (20 - [math]::Floor($status.Progress / 5))) + "]"
                    Write-Host "Progress: " -NoNewline
                    Write-Host "$progressBar $($status.Progress)%" -ForegroundColor Cyan
                    
                    if ($status.ProgressAge) {
                        Write-Host " (updated $($status.ProgressAge) min ago)" -ForegroundColor DarkGray
                    }
                }
                
                if ($status.LastActivity) {
                    Write-Host "`nLast Activity:" -ForegroundColor Gray
                    Write-Host " $($status.LastActivity)" -ForegroundColor White
                }
                
                if ($Detailed -and $status.LogPath) {
                    Write-Host "`nRecent Log Entries:" -ForegroundColor Gray
                    Get-Content $status.LogPath -Tail 15 -ErrorAction SilentlyContinue | ForEach-Object {
                        Write-Host " $_" -ForegroundColor DarkGray
                    }
                }
                
                if ($status.RecentErrors.Count -gt 0) {
                    Write-Host "`nRecent Errors:" -ForegroundColor Red
                    $status.RecentErrors | ForEach-Object {
                        Write-Host " $_" -ForegroundColor Yellow
                    }
                }
                
                Write-Host "`nNote: System will reboot automatically when ready." -ForegroundColor Gray
            } else {
                Write-Host "Status: " -NoNewline
                Write-Host "NO ACTIVE UPGRADE" -ForegroundColor Yellow
                Write-Host "`nNo Windows upgrade process detected." -ForegroundColor Gray
                Write-Host "Check if upgrade completed or check logs at:" -ForegroundColor Gray
                Write-Host " C:\Windows\Panther\setupact.log" -ForegroundColor White
                Write-Host " C:\temp\WU-BuildUpgrade-*.log" -ForegroundColor White
            }

            if ($Follow -and $status.InProgress) {
                Write-Host "`nRefreshing in 10 seconds... (Press Ctrl+C to exit)" -ForegroundColor DarkGray
                Start-Sleep -Seconds 10
            }

        } while ($Follow -and $status.InProgress)

        # Return status object
        return $status
    }
}