docs: Update README and documentation for lxs multi-tool across Linux and Windows platforms.

This commit is contained in:
2026-09-09 15:32:05 -04:00
parent 3fda22d3d5
commit 1df5cdb0bb
37 changed files with 4811 additions and 75 deletions
+220
View File
@@ -0,0 +1,220 @@
<#
LXS - Update Windows
Description: Install pending Windows updates and upgrade every winget
package. Mirror of linux/tools/update-server.sh.
Repo: https://git.hyko.cx/hykocx/lxs
Usage: update-windows.ps1 [-Yes] [-NoWinget] [-NoWindowsUpdate] [-Help]
#>
# Load LXS common library (colors, UI helpers, spinner, loggers, guards).
# Prefers the sibling ..\lib\common.ps1 (repo checkout or installed layout) and
# only hits the network when this script is run standalone.
$LxsRawBase = if ($env:LXS_RAW_BASE) { $env:LXS_RAW_BASE } else { 'https://git.hyko.cx/hykocx/lxs/raw/branch/main' }
if (-not $env:LXS_RAW_PLATFORM_BASE) { $env:LXS_RAW_PLATFORM_BASE = "$LxsRawBase/windows" }
$LxsLibPath = if ($PSScriptRoot) { Join-Path $PSScriptRoot '..\lib\common.ps1' } else { $null }
if ($LxsLibPath -and (Test-Path $LxsLibPath)) {
. $LxsLibPath
} else {
try {
$LxsLibSource = Invoke-RestMethod -Uri "$env:LXS_RAW_PLATFORM_BASE/lib/common.ps1" -UseBasicParsing -ErrorAction Stop
} catch {
Write-Error 'Failed to fetch lib/common.ps1'
exit 1
}
. ([scriptblock]::Create($LxsLibSource))
}
$env:LXS_LOG_FILE = Join-Path (Get-LxsTempDir) 'lxs_update_windows.log'
$LxsOpts = Read-LxsFlags -Arguments $args -Known @(
'-Yes', '-y', '-NoWinget', '-NoWindowsUpdate', '-Help', '-h'
)
if (Show-LxsUnknownFlags $LxsOpts) { exit 1 }
$AssumeYes = Test-LxsFlag $LxsOpts @('-Yes', '-y')
$NoWinget = Test-LxsFlag $LxsOpts @('-NoWinget')
$NoWindowsUpdate = Test-LxsFlag $LxsOpts @('-NoWindowsUpdate')
if (Test-LxsFlag $LxsOpts @('-Help', '-h')) {
Write-Host @'
Usage: update-windows.ps1 [options]
Options:
-Yes, -y Skip the confirmation prompt
-NoWinget Skip `winget upgrade --all`
-NoWindowsUpdate Skip Windows Update
-Help, -h Show this help
'@
exit 0
}
if (-not (Assert-LxsWindows)) { exit 1 }
if (-not $NoWindowsUpdate) { Assert-LxsAdmin -ScriptPath $PSCommandPath -Arguments $args }
# ═══════════════════════════════════════════════════════════════════════════
# Windows Update via the built-in COM API. Deliberately not PSWindowsUpdate:
# that module has to be pulled from the PowerShell Gallery first, which fails
# on locked-down or offline machines. The COM surface ships with Windows.
# ═══════════════════════════════════════════════════════════════════════════
function Invoke-LxsWindowsUpdate {
Write-Host ''
Show-LxsBoxMid 'WINDOWS UPDATE'
Write-Host ''
try {
$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
} catch {
Write-LxsErr "Windows Update API unavailable: $($_.Exception.Message)"
return $false
}
Write-LxsInfo 'Searching for updates (this can take several minutes)...'
try {
$result = $searcher.Search("IsInstalled=0 and Type='Software' and IsHidden=0")
} catch {
Write-LxsErr "Search failed: $($_.Exception.Message)"
return $false
}
$updates = @($result.Updates)
if ($updates.Count -eq 0) {
Write-LxsOk 'No pending updates'
return $true
}
Write-LxsOk "$($updates.Count) update(s) available:"
Write-Host ''
foreach ($u in $updates) {
$sizeMB = [math]::Round($u.MaxDownloadSize / 1MB, 1)
Write-Host " $($script:Gray)-$($script:NC) $($u.Title) $($script:Gray)(${sizeMB} MB)$($script:NC)"
}
Write-Host ''
if (-not (Confirm-LxsAction -Question 'Download and install these updates?' -DefaultYes -AssumeYes:$AssumeYes)) {
Write-LxsInfo 'Skipped.'
return $true
}
# EULAs must be accepted per-update before an unattended install.
$toInstall = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($u in $updates) {
if (-not $u.EulaAccepted) {
try { $u.AcceptEula() } catch { Write-LxsWarn "Could not accept the EULA for: $($u.Title)" }
}
[void]$toInstall.Add($u)
}
Write-Host ''
Write-LxsInfo 'Downloading...'
try {
$downloader = $session.CreateUpdateDownloader()
$downloader.Updates = $toInstall
$dlResult = $downloader.Download()
if ($dlResult.ResultCode -ne 2) {
Write-LxsWarn "Download finished with result code $($dlResult.ResultCode) (2 = success)."
} else {
Write-LxsOk 'Download complete'
}
} catch {
Write-LxsErr "Download failed: $($_.Exception.Message)"
return $false
}
Write-Host ''
Write-LxsInfo 'Installing...'
try {
$installer = $session.CreateUpdateInstaller()
$installer.Updates = $toInstall
$instResult = $installer.Install()
} catch {
Write-LxsErr "Install failed: $($_.Exception.Message)"
return $false
}
for ($i = 0; $i -lt $toInstall.Count; $i++) {
$code = $instResult.GetUpdateResult($i).ResultCode
$title = $toInstall.Item($i).Title
if ($code -eq 2) { Write-LxsOk $title } else { Write-LxsWarn "$title (result code $code)" }
}
Write-Host ''
if ($instResult.RebootRequired) {
Write-LxsWarn 'A reboot is required to finish installing these updates.'
} else {
Write-LxsOk 'Windows Update finished'
}
return $true
}
# ═══════════════════════════════════════════════════════════════════════════
# winget
# ═══════════════════════════════════════════════════════════════════════════
function Invoke-LxsWingetUpgrade {
Write-Host ''
Show-LxsBoxMid 'WINGET'
Write-Host ''
if (-not (Assert-LxsWinget)) { return $false }
Write-LxsInfo 'Packages with an available upgrade:'
Write-Host ''
& winget upgrade --include-unknown --accept-source-agreements | Out-String | Write-Host
if (-not (Confirm-LxsAction -Question 'Upgrade all of them?' -DefaultYes -AssumeYes:$AssumeYes)) {
Write-LxsInfo 'Skipped.'
return $true
}
Write-Host ''
& winget upgrade --all --include-unknown --silent `
--accept-package-agreements --accept-source-agreements --disable-interactivity
$code = $LASTEXITCODE
Write-Host ''
if ($code -eq 0) {
Write-LxsOk 'winget upgrade completed'
return $true
}
# -1978335189 = no applicable upgrade found; nothing actually went wrong.
if ($code -eq -1978335189) {
Write-LxsOk 'Everything is already up to date'
return $true
}
Write-LxsWarn "winget exited with code $code"
return $false
}
# ═══════════════════════════════════════════════════════════════════════════
# Run
# ═══════════════════════════════════════════════════════════════════════════
Clear-Host
Show-LxsBoxTop -Title 'UPDATE WINDOWS'
Write-Host ''
Write-Host 'The following will run on this machine:'
if (-not $NoWindowsUpdate) { Write-Host ' - Windows Update: search, download and install pending updates' }
if (-not $NoWinget) { Write-Host ' - winget: upgrade every package with a newer version' }
Write-Host ''
Show-LxsSeparator
if (-not (Confirm-LxsAction -Question 'Continue?' -DefaultYes -AssumeYes:$AssumeYes)) {
Write-LxsInfo 'Cancelled.'
exit 0
}
$failed = $false
if (-not $NoWindowsUpdate) { if (-not (Invoke-LxsWindowsUpdate)) { $failed = $true } }
if (-not $NoWinget) { if (-not (Invoke-LxsWingetUpgrade)) { $failed = $true } }
Write-Host ''
Show-LxsSeparator
Write-Host ''
if ($failed) {
Write-LxsWarn 'Update pass finished with warnings.'
exit 1
}
Write-LxsOk 'Update pass finished'
exit 0