On Arm64 Windows the VirtIO guest tools installer is an x86 program and the VirtIO ISO has no Arm64 package for it, so the first-logon script failed there and the guest had no network (no NetKVM driver). Install the Arm64 NetKVM, vioserial, Balloon, viorng and vioinput drivers from their folders on the ISO with pnputil instead. The OpenSSH Server capability is downloaded through Windows Update, which can take very long and blocked the rest of the script. Move it, with the administrator authorized key, into 05-Install-OpenSSH-Admin.bat, which the script copies to the Installers folder on the desktop to run by hand. Update the vm-images submodule to the build script that runs on Debian. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
180 lines
6.3 KiB
PowerShell
180 lines
6.3 KiB
PowerShell
param(
|
|
[switch]$InstallVirtIO,
|
|
[switch]$EnableAutomaticLogon,
|
|
[switch]$DisableAutoUpdate,
|
|
[switch]$CopyInstallers,
|
|
[switch]$NoPause
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
Set-StrictMode -Version Latest
|
|
|
|
function Test-Administrator {
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
|
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
}
|
|
|
|
if (-not (Test-Administrator)) {
|
|
if (-not $PSCommandPath) {
|
|
throw 'Run this script from a PowerShell window opened as Administrator.'
|
|
}
|
|
|
|
$arguments = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', "`"$PSCommandPath`"")
|
|
foreach ($name in $PSBoundParameters.Keys) {
|
|
$arguments += "-$name"
|
|
}
|
|
|
|
$elevated = Start-Process `
|
|
-FilePath 'powershell.exe' `
|
|
-ArgumentList $arguments `
|
|
-Verb RunAs `
|
|
-Wait `
|
|
-PassThru
|
|
exit $elevated.ExitCode
|
|
}
|
|
|
|
# The guest tools installer is an x86 program and the VirtIO ISO has no Arm64
|
|
# package for it, so on Arm64 Windows the Arm64 drivers are installed from their
|
|
# folders on the ISO instead. NetKVM is the network driver; without it the guest
|
|
# has no network.
|
|
function Install-VirtIOArm64Drivers {
|
|
$roots = (Get-PSDrive -PSProvider FileSystem).Root
|
|
$drivers = @(
|
|
@{ Folder = 'NetKVM'; Inf = 'netkvm.inf' },
|
|
@{ Folder = 'vioserial'; Inf = 'vioser.inf' },
|
|
@{ Folder = 'Balloon'; Inf = 'balloon.inf' },
|
|
@{ Folder = 'viorng'; Inf = 'viorng.inf' },
|
|
@{ Folder = 'vioinput'; Inf = 'vioinput.inf' }
|
|
)
|
|
|
|
$installed = 0
|
|
foreach ($driver in $drivers) {
|
|
$inf = $roots |
|
|
ForEach-Object { Join-Path $_ "$($driver.Folder)\w11\ARM64\$($driver.Inf)" } |
|
|
Where-Object { Test-Path -LiteralPath $_ } |
|
|
Select-Object -First 1
|
|
if (-not $inf) {
|
|
if ($driver.Folder -eq 'NetKVM') {
|
|
throw 'The Arm64 NetKVM driver was not found on the attached media.'
|
|
}
|
|
continue
|
|
}
|
|
|
|
& pnputil.exe /add-driver $inf /install | Out-Host
|
|
# 3010 asks for a restart; 259 means that no device uses the driver.
|
|
if ($LASTEXITCODE -notin 0, 259, 3010) {
|
|
throw "pnputil failed to install $inf (exit code $LASTEXITCODE)."
|
|
}
|
|
$installed++
|
|
}
|
|
Write-Host "Installed $installed VirtIO Arm64 driver packages."
|
|
}
|
|
|
|
function Install-VirtIOGuestTools {
|
|
if ([Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') {
|
|
Install-VirtIOArm64Drivers
|
|
return
|
|
}
|
|
|
|
$installer = Get-ChildItem `
|
|
-Path (Get-PSDrive -PSProvider FileSystem).Root `
|
|
-Filter 'virtio-win-guest-tools.exe' `
|
|
-File `
|
|
-ErrorAction SilentlyContinue |
|
|
Select-Object -First 1
|
|
|
|
if (-not $installer) {
|
|
throw 'virtio-win-guest-tools.exe was not found on the attached media.'
|
|
}
|
|
|
|
$process = Start-Process `
|
|
-FilePath $installer.FullName `
|
|
-ArgumentList '/quiet', '/norestart' `
|
|
-Wait `
|
|
-PassThru
|
|
|
|
if ($process.ExitCode -notin 0, 3010) {
|
|
throw "VirtIO guest tools installer exited with code $($process.ExitCode)."
|
|
}
|
|
}
|
|
|
|
function Enable-AutomaticLogon {
|
|
$winlogonPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
|
|
|
|
Set-ItemProperty -Path $winlogonPath -Name 'AutoAdminLogon' -Value '1' -Type String
|
|
Set-ItemProperty -Path $winlogonPath -Name 'DefaultUserName' -Value 'guofang' -Type String
|
|
Set-ItemProperty -Path $winlogonPath -Name 'DefaultPassword' -Value 'wang' -Type String
|
|
Set-ItemProperty -Path $winlogonPath -Name 'DefaultDomainName' -Value $env:COMPUTERNAME -Type String
|
|
Remove-ItemProperty -Path $winlogonPath -Name 'AutoLogonCount' -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
function Disable-AutomaticUpdate {
|
|
$windowsUpdatePath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
|
|
$autoUpdatePath = Join-Path $windowsUpdatePath 'AU'
|
|
$storePath = 'HKLM:\SOFTWARE\Policies\Microsoft\WindowsStore'
|
|
$currentVersion = Get-ItemProperty `
|
|
-Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
|
|
$releaseVersion = $currentVersion.DisplayVersion
|
|
|
|
New-Item -Path $autoUpdatePath -Force | Out-Null
|
|
New-Item -Path $storePath -Force | Out-Null
|
|
|
|
# Never check automatically; manual checks and installs keep working.
|
|
Set-ItemProperty -Path $autoUpdatePath -Name 'NoAutoUpdate' -Value 1 -Type DWord
|
|
Set-ItemProperty -Path $autoUpdatePath -Name 'NoAutoRebootWithLoggedOnUsers' -Value 1 -Type DWord
|
|
Set-ItemProperty -Path $windowsUpdatePath -Name 'ExcludeWUDriversInQualityUpdate' -Value 1 -Type DWord
|
|
|
|
# Stay on the installed feature release instead of upgrading automatically.
|
|
if ($releaseVersion) {
|
|
Set-ItemProperty -Path $windowsUpdatePath -Name 'TargetReleaseVersion' -Value 1 -Type DWord
|
|
Set-ItemProperty -Path $windowsUpdatePath -Name 'TargetReleaseVersionInfo' -Value $releaseVersion -Type String
|
|
}
|
|
|
|
# 2 = never download Microsoft Store app updates automatically.
|
|
Set-ItemProperty -Path $storePath -Name 'AutoDownload' -Value 2 -Type DWord
|
|
|
|
# Windows may re-enable this task, so the policies above are the real switch.
|
|
Disable-ScheduledTask `
|
|
-TaskPath '\Microsoft\Windows\UpdateOrchestrator\' `
|
|
-TaskName 'Schedule Scan' `
|
|
-ErrorAction SilentlyContinue | Out-Null
|
|
}
|
|
|
|
function Copy-InstallerScripts {
|
|
$scripts = @(Get-ChildItem -Path $PSScriptRoot -Filter '*-Install-*.bat' -File)
|
|
if ($scripts.Count -eq 0) {
|
|
throw "No installer batch files were found next to $PSCommandPath."
|
|
}
|
|
|
|
$target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Installers'
|
|
New-Item -ItemType Directory -Path $target -Force | Out-Null
|
|
Copy-Item -Path $scripts.FullName -Destination $target -Force
|
|
}
|
|
|
|
$exitCode = 0
|
|
try {
|
|
if ($InstallVirtIO) {
|
|
Install-VirtIOGuestTools
|
|
}
|
|
if ($EnableAutomaticLogon) {
|
|
Enable-AutomaticLogon
|
|
}
|
|
if ($DisableAutoUpdate) {
|
|
Disable-AutomaticUpdate
|
|
}
|
|
if ($CopyInstallers) {
|
|
Copy-InstallerScripts
|
|
}
|
|
Write-Host 'Done.' -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Host "Failed: $($_.Exception.Message)" -ForegroundColor Red
|
|
$exitCode = 1
|
|
}
|
|
|
|
if (-not $NoPause) {
|
|
Read-Host 'Press Enter to exit' | Out-Null
|
|
}
|
|
exit $exitCode
|