Add an update-status tool and copy numbered scripts to Desktop\Tools

80-Get-UpdateStatus-Admin.ps1 prints whether the Windows 11 guest is
upgrading or downloading updates (policy, pending markers, services,
processes, BITS jobs, update events, network use); the .bat launcher
elevates and keeps the window open.

The seed ISO and Configure-Windows.ps1 now take every NN-*.bat and
NN-*.ps1, and the first-logon copy goes to Desktop\Tools instead of
Desktop\Installers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GuofangandClaude Sonnet 5.5 committed 2026-10-26 09:27:52 +08:00
1 parent fccc7986d9
commit 72af276309
5 files changed
+127 -5

No files matched your search

+2 -1
View File
@@ -287,7 +287,8 @@ def build_seed_iso(image_downloader, force=False):
"""Build the seed ISO, unless it is newer than all of its sources and not forced.""" """Build the seed ISO, unless it is newer than all of its sources and not forced."""
seed_dir = script_dir / "seediso" / "windows11" seed_dir = script_dir / "seediso" / "windows11"
seed_sources = [seed_dir / "Autounattend.xml", seed_dir / "Configure-Windows.ps1", seed_sources = [seed_dir / "Autounattend.xml", seed_dir / "Configure-Windows.ps1",
*sorted(seed_dir.glob("[0-9][0-9]-*.bat"))] *sorted(seed_dir.glob("[0-9][0-9]-*.bat")),
*sorted(seed_dir.glob("[0-9][0-9]-*.ps1"))]
# build-seed.sh embeds the administrator SSH public key. # build-seed.sh embeds the administrator SSH public key.
ssh_key = os.environ.get("SSH_PUBLIC_KEY_FILE") or next( ssh_key = os.environ.get("SSH_PUBLIC_KEY_FILE") or next(
@@ -0,0 +1,14 @@
@echo off
setlocal
title Windows update status
fltmc >nul 2>&1
if errorlevel 1 (
echo Requesting administrator rights...
powershell.exe -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
exit /b
)
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp080-Get-UpdateStatus-Admin.ps1"
echo.
pause
@@ -0,0 +1,104 @@
# Prints whether the guest is upgrading or downloading updates.
# Read-only. Run as Administrator for complete results (BITS jobs of all users,
# command lines of system processes); a standard user gets partial output.
param(
[int]$EventCount = 8
)
$ErrorActionPreference = 'Continue'
$ProgressPreference = 'SilentlyContinue'
function Write-Section([string]$Title) {
Write-Host ''
Write-Host "== $Title =="
}
function Out-Text($Value) {
$text = ($Value | Out-String).TrimEnd()
if ($text) { Write-Host $text } else { Write-Host '(none)' }
}
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$isAdmin = [Security.Principal.WindowsPrincipal]::new($identity).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator)
Write-Section 'Host'
Write-Host ('Time: {0}' -f (Get-Date))
Write-Host ('Admin: {0}' -f $isAdmin)
if (-not $isAdmin) {
Write-Host 'Warning: not elevated, BITS jobs and process command lines may be incomplete.'
}
Write-Section 'Operating system'
Out-Text (Get-CimInstance Win32_OperatingSystem |
Select-Object Caption, Version, BuildNumber, LastBootUpTime | Format-List)
Write-Section 'Update policy'
$wuPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
$props = 'NoAutoUpdate', 'NoAutoRebootWithLoggedOnUsers', 'ExcludeWUDriversInQualityUpdate',
'TargetReleaseVersion', 'TargetReleaseVersionInfo'
foreach ($item in @(@{ Path = "$wuPath\AU" }, @{ Path = $wuPath })) {
$values = Get-ItemProperty -Path $item.Path -ErrorAction SilentlyContinue
foreach ($name in $props) {
if ($values -and $values.PSObject.Properties[$name]) {
Write-Host ('{0,-34} {1}' -f $name, $values.$name)
}
}
}
Write-Section 'Upgrade / pending-reboot markers'
$markers = [ordered]@{
'C:\$WINDOWS.~BT (upgrade staging)' = Test-Path -LiteralPath 'C:\$WINDOWS.~BT'
'C:\Windows\WinSxS\pending.xml' = Test-Path -LiteralPath 'C:\Windows\WinSxS\pending.xml'
'WindowsUpdate RebootRequired' = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
'Component Based Servicing RebootPending' = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
}
foreach ($key in $markers.Keys) {
Write-Host ('{0,-42} {1}' -f $key, $markers[$key])
}
Write-Section 'Update services'
Out-Text (Get-Service wuauserv, UsoSvc, TrustedInstaller, bits, WaaSMedicSvc, DoSvc `
-ErrorAction SilentlyContinue |
Select-Object Name, Status, StartType | Format-Table -AutoSize)
Write-Section 'Update scheduled tasks'
Out-Text (Get-ScheduledTask `
-TaskPath '\Microsoft\Windows\UpdateOrchestrator\*', '\Microsoft\Windows\WindowsUpdate\*' `
-ErrorAction SilentlyContinue |
Select-Object TaskName, State | Format-Table -AutoSize)
Write-Section 'Update-related processes'
$pattern = 'setup|TiWorker|TrustedInstaller|UsoClient|MoSetup|SetupHost|SetupPrep|MusNotification|wusa|dism|EdgeUpdate|Installer'
Out-Text (Get-CimInstance Win32_Process |
Where-Object { $_.Name -match $pattern -or $_.CommandLine -match 'wuauserv|UsoSvc|DoSvc' } |
Select-Object ProcessId, Name, CreationDate, CommandLine | Format-List)
Write-Section 'BITS download jobs'
Out-Text (Get-BitsTransfer -AllUsers -ErrorAction SilentlyContinue |
Select-Object DisplayName, JobState, BytesTransferred, BytesTotal | Format-Table -AutoSize)
Write-Section "Windows Update events (last $EventCount)"
Out-Text (Get-WinEvent -LogName 'Microsoft-Windows-WindowsUpdateClient/Operational' `
-MaxEvents $EventCount -ErrorAction SilentlyContinue |
Select-Object TimeCreated, Id, Message | Format-List)
Write-Section 'Established HTTP/HTTPS connections by process'
Out-Text (Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue |
Where-Object { $_.RemotePort -in 80, 443 } |
Group-Object OwningProcess |
ForEach-Object {
$process = Get-Process -Id $_.Name -ErrorAction SilentlyContinue
[pscustomobject]@{ Pid = $_.Name; Process = $process.Name; Connections = $_.Count }
} | Format-Table -AutoSize)
Write-Section 'Network receive rate (2 samples, 2 s apart)'
$counter = Get-Counter '\Network Interface(*)\Bytes Received/sec' `
-SampleInterval 2 -MaxSamples 2 -ErrorAction SilentlyContinue
if ($counter) {
Out-Text ($counter[-1].CounterSamples |
Select-Object InstanceName, @{ n = 'KBps'; e = { [int]($_.CookedValue / 1024) } } |
Format-Table -AutoSize)
} else {
Write-Host '(performance counters unavailable)'
}
+4 -2
View File
@@ -144,12 +144,14 @@ function Disable-AutomaticUpdate {
} }
function Copy-InstallerScripts { function Copy-InstallerScripts {
$scripts = @(Get-ChildItem -Path $PSScriptRoot -Filter '*-Install-*.bat' -File) # Numbered scripts (NN-*.bat / NN-*.ps1: installers and tools) go to Desktop\Tools.
$scripts = @(Get-ChildItem -Path $PSScriptRoot -File |
Where-Object { $_.Name -match '^\d\d-.*\.(bat|ps1)$' })
if ($scripts.Count -eq 0) { if ($scripts.Count -eq 0) {
throw "No installer batch files were found next to $PSCommandPath." throw "No installer batch files were found next to $PSCommandPath."
} }
$target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Installers' $target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Tools'
New-Item -ItemType Directory -Path $target -Force | Out-Null New-Item -ItemType Directory -Path $target -Force | Out-Null
Copy-Item -Path $scripts.FullName -Destination $target -Force Copy-Item -Path $scripts.FullName -Destination $target -Force
} }
+3 -2
View File
@@ -43,9 +43,10 @@ if [[ -n "$ssh_key" ]]; then
cp -- "$ssh_key" "$work_dir/authorized_keys" cp -- "$ssh_key" "$work_dir/authorized_keys"
seed_files+=("$work_dir/authorized_keys") seed_files+=("$work_dir/authorized_keys")
fi fi
# Numbered installer scripts (10-Install-Git-Admin.bat, 20-..., in install order). # Numbered scripts: installers (10-Install-Git-Admin.bat, 20-..., in install order)
# and tools (80-Get-UpdateStatus-Admin.bat/.ps1); all land in Desktop\Tools.
shopt -s nullglob shopt -s nullglob
seed_files+=("$script_dir"/[0-9][0-9]-*.bat) seed_files+=("$script_dir"/[0-9][0-9]-*.bat "$script_dir"/[0-9][0-9]-*.ps1)
shopt -u nullglob shopt -u nullglob
mkdir -p "$(dirname -- "$output")" mkdir -p "$(dirname -- "$output")"