Install OpenSSH Server from the Win32-OpenSSH zip

Carry the zip for the host architecture in the seed ISO and add
06-Install-OpenSSH-Zip-Admin.bat, which installs from it instead of
Windows Update. Update vm-images.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GuofangandClaude Sonnet 5.5 committed 2026-10-19 14:28:13 +08:00
1 parent 07355a41f1
commit 552a292894
6 files changed
+93 -5

No files matched your search

+6 -3
View File
@@ -174,9 +174,12 @@ VirtIO ISO with `pnputil` on arm64), without which the guest has no network on
arm64. The installer includes the VirtIO drivers, SPICE vdagent, and QEMU Guest arm64. The installer includes the VirtIO drivers, SPICE vdagent, and QEMU Guest
Agent. The launcher exposes the vdagent channel for host/guest clipboard Agent. The launcher exposes the vdagent channel for host/guest clipboard
integration and the QEMU Guest Agent at `.vm/windows11/qemu-guest-agent.sock`. integration and the QEMU Guest Agent at `.vm/windows11/qemu-guest-agent.sock`.
Change the password after first login. The OpenSSH Server is downloaded through Change the password after first login. The OpenSSH Server is not installed
Windows Update, which can be slow, so it is not installed automatically: run automatically. Run `05-Install-OpenSSH-Admin.bat` from the `Installers` folder on
`05-Install-OpenSSH-Admin.bat` from the `Installers` folder on the desktop as the desktop to install it through Windows Update, which can be slow, or
`06-Install-OpenSSH-Zip-Admin.bat` to install it from the Win32-OpenSSH zip for
the host architecture, which the seed ISO carries (downloaded by
`vm-images/download-images.sh openssh-win`), as
administrator, then connect with `ssh guofang@localhost -p 2222`; it also installs administrator, then connect with `ssh guofang@localhost -p 2222`; it also installs
the host's configured SSH public key for passwordless administrator login. the host's configured SSH public key for passwordless administrator login.
The Windows VM hardware (8G memory, 8 CPUs, system disk, devices) The Windows VM hardware (8G memory, 8 CPUs, system disk, devices)
+7 -1
View File
@@ -304,11 +304,17 @@ if installing_from_iso:
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"))]
# The seed carries the OpenSSH zip for this architecture; build-seed.sh
# downloads it when missing.
seed_sources.append(Path(capture(
[str(image_downloader), "--print-path", "openssh-win"])))
def newer_than_seed(source): def newer_than_seed(source):
# Mirrors bash `-nt`: true if source exists and is newer than seed_iso. # Mirrors bash `-nt`: true if source exists and is newer than seed_iso.
return source.exists() and source.stat().st_mtime > seed_iso.stat().st_mtime return source.exists() and source.stat().st_mtime > seed_iso.stat().st_mtime
if not seed_iso.is_file() or any(newer_than_seed(s) for s in seed_sources): if not seed_iso.is_file() or not seed_sources[-1].is_file() \
or any(newer_than_seed(s) for s in seed_sources):
run([str(script_dir / "seediso" / "windows11" / "build-seed.sh"), str(seed_iso), host_arch]) run([str(script_dir / "seediso" / "windows11" / "build-seed.sh"), str(seed_iso), host_arch])
if pid_file.is_file(): if pid_file.is_file():
@@ -0,0 +1,65 @@
@echo off
setlocal
title Install OpenSSH Server from zip
fltmc >nul 2>&1
if errorlevel 1 (
echo Please right-click this file and choose "Run as administrator".
pause
exit /b 1
)
set "ZIP="
for %%Z in ("%~dp0OpenSSH-*.zip") do set "ZIP=%%~fZ"
if not defined ZIP (
echo No OpenSSH-*.zip was found next to this file.
pause
exit /b 1
)
echo Installing the OpenSSH Server from %ZIP%...
if not exist "%ProgramFiles%\OpenSSH\install-sshd.ps1" (
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^
"$t = Join-Path $env:TEMP 'openssh'; Remove-Item $t -Recurse -Force -ErrorAction SilentlyContinue; Expand-Archive -LiteralPath $env:ZIP -DestinationPath $t; Remove-Item (Join-Path $env:ProgramFiles 'OpenSSH') -Recurse -Force -ErrorAction SilentlyContinue; Move-Item (Get-ChildItem $t -Directory | Select-Object -First 1).FullName (Join-Path $env:ProgramFiles 'OpenSSH'); Remove-Item $t -Recurse -Force"
if errorlevel 1 (
echo Extracting the zip failed.
pause
exit /b 1
)
)
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%ProgramFiles%\OpenSSH\install-sshd.ps1"
if errorlevel 1 (
echo install-sshd.ps1 failed.
pause
exit /b 1
)
echo Starting the sshd service...
sc.exe config sshd start= auto
net.exe start sshd >nul 2>&1
sc.exe query sshd | find "RUNNING" >nul
if errorlevel 1 (
echo The sshd service is not running.
pause
exit /b 1
)
netsh.exe advfirewall firewall show rule name="OpenSSH-Server-In-TCP" >nul 2>&1
if errorlevel 1 (
netsh.exe advfirewall firewall add rule name="OpenSSH-Server-In-TCP" ^
dir=in action=allow protocol=TCP localport=22 profile=private,public
)
echo Installing the administrator authorized key...
set "KEYFILE=%ProgramData%\ssh\administrators_authorized_keys"
> "%KEYFILE%" echo ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCwsF2ZDSHDaXM9BdDq3Vxkp6T+DJEakKaVZQJUEHexS34mbrxYAxmGBUPCrddfE01dS31kGS4XHVBa+wGUYodY+nPwnrfD1Q4phKVSzXr65K1Y/sCcjSiANBPWpv2Xd7IMOfLlrdLtmf5QcKWroIqO2LaIDGECwzUUHrfprgeaF9N0FGcmrgbmm23JUk+f6guRQ4br88L4rWIWEBvL9bTdwErNkdfsYE2F/U8aIFpMcVy2eJIOgy8ZjZvkGdm4r730YkTDL1VmAjiHH3/m2R1EOvwLzFhyWKoa4ULk/IQW+8iNnG42QEcaWRM+hMwlVdnNhPJP6WzS+fAEbibD3zM0q2X/4ex+cXRNOCd2/0KhnD2+3p1BM5QeFTzDappkk8W2meQkDE3hvTjtIQCzygFO6LNWtEEa7BTvvlMqF3CZWh88J9XiP750SjESe3Ge5o9A0T2hcJa+KZ1LxVEmNt4nD6E/OD4EymoaLIJZ1fH5ksSFjHzJ+mJouSVjc/H/VSE= guofang@GUOFANG-OFFICE
icacls.exe "%KEYFILE%" /inheritance:r /grant "*S-1-5-32-544:F" /grant "*S-1-5-18:F" >nul
if errorlevel 1 (
echo icacls exited with code %ERRORLEVEL%.
pause
exit /b 1
)
echo Done. SSH is available on port 22 of this machine.
pause
exit /b 0
+6
View File
@@ -151,6 +151,12 @@ function Copy-InstallerScripts {
$target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Installers' $target = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Installers'
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
# 05-Install-OpenSSH-Admin.bat installs from this zip when it is present.
$zips = @(Get-ChildItem -Path $PSScriptRoot -Filter 'OpenSSH-*.zip' -File)
if ($zips.Count -gt 0) {
Copy-Item -Path $zips.FullName -Destination $target -Force
}
} }
$exitCode = 0 $exitCode = 0
+8
View File
@@ -10,6 +10,13 @@ case "$arch" in
*) printf 'Error: unsupported architecture: %s\n' "$arch" >&2; exit 1 ;; *) printf 'Error: unsupported architecture: %s\n' "$arch" >&2; exit 1 ;;
esac esac
# The OpenSSH zip for the target architecture comes from the shared image
# helper, which downloads it when missing.
image_helper="${VM_IMAGE_HELPER:-$script_dir/../../vm-images/download-images.sh}"
openssh_zip="$(VM_IMAGE_ARCH="$answer_arch" "$image_helper" --print-path openssh-win)"
[[ -s "$openssh_zip" ]] || VM_IMAGE_ARCH="$answer_arch" "$image_helper" openssh-win
[[ -s "$openssh_zip" ]] || { printf 'Error: %s was not downloaded.\n' "$openssh_zip" >&2; exit 1; }
# The answer file's components name the processor architecture, so render a # The answer file's components name the processor architecture, so render a
# copy for the target architecture. # copy for the target architecture.
work_dir="$(mktemp -d)" work_dir="$(mktemp -d)"
@@ -20,6 +27,7 @@ sed "s/processorArchitecture=\"amd64\"/processorArchitecture=\"${answer_arch}\"/
seed_files=( seed_files=(
"$work_dir/Autounattend.xml" "$work_dir/Autounattend.xml"
"$script_dir/Configure-Windows.ps1" "$script_dir/Configure-Windows.ps1"
"$openssh_zip"
) )
# Numbered installer scripts (10-Install-Git-Admin.bat, 20-..., in install order). # Numbered installer scripts (10-Install-Git-Admin.bat, 20-..., in install order).
shopt -s nullglob shopt -s nullglob