1
0
mirror of https://github.com/dockur/windows.git synced 2026-07-27 21:42:36 +07:00

Compare commits

...

20 Commits

Author SHA1 Message Date
Kroese 2ac9d71b32 fix: Fall back to default boot load size for NT5 media (#1989) 2026-07-26 07:22:27 +02:00
Kroese e7e637531e fix: Avoid pipefail when reading boot load size (#1988) 2026-07-26 07:07:03 +02:00
Kroese b90ded928b fix: Read boot image offset during legacy fallback (#1987) 2026-07-26 06:40:59 +02:00
Kroese 0d0dd7be87 fix: Read legacy boot load size before image preparation (#1986) 2026-07-26 06:32:26 +02:00
Kroese 5b080ac6c8 build: Update workflow (#1985) 2026-07-26 06:03:43 +02:00
Kroese 6df58b5dc7 fix: Preserve source boot load size for legacy ISOs (#1984) 2026-07-26 05:51:44 +02:00
Kroese 8929f7cdcd feat: Refactor Windows answer file handling (#1983) 2026-07-26 05:34:24 +02:00
Kroese aa6240ff99 fix: Add the compact identifiers for Windows N (#1981) 2026-07-26 03:32:58 +02:00
Kroese 3d3bab023d fix: Limit XP EPYC workaround to x86 (#1980) 2026-07-26 03:28:22 +02:00
Kroese 36d127d8cf fix: Handling of UTF-16 batch files (#1979) 2026-07-26 03:18:41 +02:00
Kroese 2b7d7bf902 fix: Respect REBUILD=N for legacy Windows media (#1978) 2026-07-26 02:55:50 +02:00
Kroese 30d9858c7d fix: Warn instead of failing on domain XML updates (#1977) 2026-07-26 02:41:29 +02:00
Kroese 09941ba0f6 docs: Environment variables (#1976) 2026-07-26 02:33:30 +02:00
Kroese 828bdb1b29 fix: Preserve Server Evaluation identity during image detection (#1975) 2026-07-26 02:01:31 +02:00
Kroese c64cf2313b build: Add Windows installation validation workflows (#1974) 2026-07-26 01:52:13 +02:00
Kroese f45575c13f build: Update dependabot config (#1973) 2026-07-26 01:12:41 +02:00
Kroese 11b300149f fix: Legacy boot image extraction and load size (#1972) 2026-07-26 00:30:51 +02:00
Kroese 605f31af72 fix: Legacy ISO boot load size (#1971) 2026-07-26 00:10:41 +02:00
Kroese 4e35ead612 feat: Added option to prevent rebuild of the image (#1970) 2026-07-26 00:02:17 +02:00
Kroese 0a3bff5a7d docs: Document new REBUILD option (#1969) 2026-07-25 23:54:21 +02:00
9 changed files with 2170 additions and 616 deletions
+7
View File
@@ -1,4 +1,5 @@
version: 2
updates:
- package-ecosystem: docker
directory: /
@@ -6,9 +7,15 @@ updates:
interval: weekly
cooldown:
default-days: 7
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
ignore:
- dependency-name: "*"
update-types:
- version-update:semver-minor
- version-update:semver-patch
+680
View File
@@ -0,0 +1,680 @@
name: Installation
on:
workflow_call:
inputs:
name:
description: Display name for the Windows version
required: true
type: string
version:
description: VERSION value passed to the container
required: true
type: string
runner:
description: GitHub Actions runner
required: false
default: ubuntu-24.04
type: string
image:
description: Container image to test
required: false
default: ghcr.io/dockur/windows:latest
type: string
callback:
description: Guest script type
required: false
default: powershell
type: string
cpu:
description: CPU model exposed to Windows
required: false
default: host
type: string
expected_caption:
description: Text expected in the Windows caption
required: true
type: string
expected_edition:
description: Expected Windows EditionID
required: false
default: ""
type: string
minimum_build:
description: Minimum acceptable Windows build number
required: true
type: number
platform:
description: Expected Windows platform
required: true
type: string
permissions:
contents: read
jobs:
install:
name: ${{ inputs.name }}
runs-on: ${{ inputs.runner }}
timeout-minutes: 180
env:
CONTAINER: windows-test
IMAGE: ${{ inputs.image }}
steps:
- name: Check KVM
shell: bash
run: |
set -Eeuo pipefail
test -c /dev/kvm
- name: Free disk space
shell: bash
run: |
set -Eeuo pipefail
sudo rm -rf \
/usr/local/.ghcup \
/usr/local/lib/android \
/usr/local/lib/node_modules \
/usr/local/share/boost \
/usr/local/share/chromium \
/usr/local/share/powershell \
/usr/share/dotnet \
/usr/share/swift \
/opt/az \
/opt/ghc \
/opt/google \
/opt/hostedtoolcache \
/opt/microsoft \
/opt/pipx
sudo apt-get clean
sudo rm -rf \
/var/cache/apt/* \
/var/lib/apt/lists/*
docker system prune \
--all \
--force \
--volumes > /dev/null || true
available_kb="$(df --output=avail / | tail -1)"
available_gb="$((available_kb / 1024 / 1024))"
echo "Available disk space: ${available_gb} GB"
if (( available_gb < 40 )); then
echo "At least 40 GB of free space is required."
exit 1
fi
- name: Prepare installation test
id: test
shell: bash
env:
CALLBACK: ${{ inputs.callback }}
run: |
set -Eeuo pipefail
case "$CALLBACK" in
powershell | legacy)
;;
*)
echo "Unsupported guest script type: $CALLBACK"
exit 1
;;
esac
token="$(cat /proc/sys/kernel/random/uuid)"
mkdir -p \
"$RUNNER_TEMP/data" \
"$RUNNER_TEMP/oem" \
"$RUNNER_TEMP/storage"
printf '%s\n' "$token" > "$RUNNER_TEMP/data/readme.txt"
case "$CALLBACK" in
powershell)
cat > "$RUNNER_TEMP/oem/ready.ps1" <<'POWERSHELL'
param(
[Parameter(Mandatory = $true)]
[string]$Token
)
$ErrorActionPreference = "Stop"
$windows = Get-CimInstance Win32_OperatingSystem
$registry = Get-ItemProperty `
"HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
$platform = switch (
$env:PROCESSOR_ARCHITECTURE.ToUpperInvariant()
) {
"AMD64" {
"x64"
}
"ARM64" {
"arm64"
}
"X86" {
"x86"
}
default {
$env:PROCESSOR_ARCHITECTURE.ToLowerInvariant()
}
}
while ($true) {
try {
$share = (
Get-Content `
-LiteralPath "\\host.lan\Data\readme.txt" `
-Raw
).Trim()
$result = @{
token = $Token
caption = [string]$windows.Caption
edition = [string]$registry.EditionID
version = [string]$windows.Version
build = [string]$windows.BuildNumber
platform = $platform
share = $share
}
$json = $result | ConvertTo-Json -Compress
$temporary = "\\host.lan\Data\windows.tmp"
$destination = "\\host.lan\Data\windows.json"
$encoding = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText(
$temporary,
$json,
$encoding
)
Move-Item `
-LiteralPath $temporary `
-Destination $destination `
-Force
break
}
catch {
Start-Sleep -Seconds 10
}
}
POWERSHELL
cat > "$RUNNER_TEMP/oem/install.bat" <<EOF
@echo off
powershell.exe -NoProfile -ExecutionPolicy Bypass ^
-File C:\OEM\ready.ps1 ^
-Token "$token"
EOF
;;
legacy)
cat > "$RUNNER_TEMP/oem/ready.vbs" <<'VBSCRIPT'
Option Explicit
Const ForReading = 1
Const ForWriting = 2
Dim arguments
Dim token
Set arguments = WScript.Arguments
If arguments.Count < 1 Then
WScript.Quit 1
End If
token = arguments(0)
Function EscapeJson(value)
Dim result
result = CStr(value)
result = Replace(result, "\", "\\")
result = Replace(result, Chr(34), "\" & Chr(34))
result = Replace(result, vbCr, "\r")
result = Replace(result, vbLf, "\n")
result = Replace(result, vbTab, "\t")
EscapeJson = result
End Function
Function ReadRegistry(shell, path)
Dim value
value = ""
On Error Resume Next
value = shell.RegRead(path)
If Err.Number <> 0 Then
Err.Clear
value = ""
End If
On Error GoTo 0
ReadRegistry = CStr(value)
End Function
Function ReadTextFile(filesystem, path)
Dim file
Dim value
value = ""
Set file = filesystem.OpenTextFile(path, ForReading, False)
value = file.ReadAll
file.Close
value = Replace(value, vbCr, "")
value = Replace(value, vbLf, "")
ReadTextFile = value
End Function
Function GetPlatform(shell)
Dim architecture
architecture = UCase(shell.ExpandEnvironmentStrings("%PROCESSOR_ARCHITECTURE%"))
Select Case architecture
Case "AMD64"
GetPlatform = "x64"
Case "ARM64"
GetPlatform = "arm64"
Case "X86"
GetPlatform = "x86"
Case Else
GetPlatform = LCase(architecture)
End Select
End Function
Dim filesystem
Dim shell
Dim locator
Dim service
Dim systems
Dim system
Dim caption
Dim edition
Dim version
Dim build
Dim platform
Dim share
Dim json
Dim temporary
Dim destination
Dim file
Set filesystem = CreateObject("Scripting.FileSystemObject")
Set shell = CreateObject("WScript.Shell")
Set locator = CreateObject("WbemScripting.SWbemLocator")
Set service = locator.ConnectServer(".", "root\cimv2")
Set systems = service.ExecQuery("SELECT Caption, Version, BuildNumber FROM Win32_OperatingSystem")
caption = ""
version = ""
build = ""
For Each system In systems
caption = CStr(system.Caption)
version = CStr(system.Version)
build = CStr(system.BuildNumber)
Exit For
Next
edition = ReadRegistry(shell, "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\EditionID")
platform = GetPlatform(shell)
temporary = "\\host.lan\Data\windows.tmp"
destination = "\\host.lan\Data\windows.json"
Do
On Error Resume Next
share = ReadTextFile(filesystem, "\\host.lan\Data\readme.txt")
If Err.Number = 0 Then
json = _
"{" & _
"""token"":""" & EscapeJson(token) & """," & _
"""caption"":""" & EscapeJson(caption) & """," & _
"""edition"":""" & EscapeJson(edition) & """," & _
"""version"":""" & EscapeJson(version) & """," & _
"""build"":""" & EscapeJson(build) & """," & _
"""platform"":""" & EscapeJson(platform) & """," & _
"""share"":""" & EscapeJson(share) & """" & _
"}"
Set file = filesystem.OpenTextFile(temporary, ForWriting, True)
file.Write json
file.Close
If filesystem.FileExists(destination) Then
filesystem.DeleteFile destination, True
End If
filesystem.MoveFile temporary, destination
End If
If Err.Number = 0 Then
On Error GoTo 0
Exit Do
End If
Err.Clear
On Error GoTo 0
WScript.Sleep 10000
Loop
VBSCRIPT
cat > "$RUNNER_TEMP/oem/install.bat" <<EOF
@echo off
cscript.exe //B //NoLogo C:\OEM\ready.vbs "$token"
EOF
;;
esac
echo "token=$token" >> "$GITHUB_OUTPUT"
- name: Pull image
shell: bash
run: |
set -Eeuo pipefail
docker pull "$IMAGE"
docker image inspect "$IMAGE" \
--format 'Digest: {{index .RepoDigests 0}}'
- name: Install and validate Windows
shell: bash
env:
CPU: ${{ inputs.cpu }}
EXPECTED_TOKEN: ${{ steps.test.outputs.token }}
EXPECTED_CAPTION: ${{ inputs.expected_caption }}
EXPECTED_EDITION: ${{ inputs.expected_edition }}
EXPECTED_PLATFORM: ${{ inputs.platform }}
MINIMUM_BUILD: ${{ inputs.minimum_build }}
VERSION: ${{ inputs.version }}
DISPLAY_NAME: ${{ inputs.name }}
run: |
set -Eeuo pipefail
docker run --detach \
--name "$CONTAINER" \
--device /dev/kvm \
--device /dev/net/tun \
--cap-add NET_ADMIN \
--stop-timeout 120 \
--env "VERSION=$VERSION" \
--env "RAM_SIZE=half" \
--env "CPU_CORES=half" \
--env "CPU_MODEL=$CPU" \
--env "DISK_SIZE=64G" \
--volume "$RUNNER_TEMP/data:/shared" \
--volume "$RUNNER_TEMP/oem:/oem:ro" \
--volume "$RUNNER_TEMP/storage:/storage" \
"$IMAGE"
echo
echo "Container log:"
echo "------------------------------------------------------------"
docker logs \
--follow \
--timestamps \
"$CONTAINER" &
logs_pid="$!"
stop_logs() {
kill "$logs_pid" 2>/dev/null || true
wait "$logs_pid" 2>/dev/null || true
}
trap stop_logs EXIT
deadline=$((SECONDS + 9000))
reboot_timeout=1800
minimum_reboots=1
boot_loop_limit=5
first_bios_start=-1
while (( SECONDS < deadline )); do
state="$(
docker inspect \
--format '{{.State.Status}}' \
"$CONTAINER" 2>/dev/null || true
)"
if [[ "$state" != "running" ]]; then
echo
echo "------------------------------------------------------------"
echo "Container stopped before Windows became ready."
echo "Container state: ${state:-missing}"
exit 1
fi
container_log="$(docker logs "$CONTAINER" 2>&1 || true)"
if grep -Eqi \
'KVM internal error|KVM: entry failed|hardware error 0x[0-9a-f]+|Triple fault' \
<<< "$container_log"; then
echo
echo "------------------------------------------------------------"
echo "Detected a fatal QEMU or KVM error."
exit 1
fi
if grep -Eqi \
'CDBOOT: Cannot boot from CD.*Code: 5' \
<<< "$container_log"; then
echo
echo "------------------------------------------------------------"
echo "The installation media could not be booted."
exit 1
fi
bios_starts="$(
grep -Fci 'SeaBIOS (version ' <<< "$container_log" || true
)"
hard_disk_boots="$(
grep -Fci 'Booting from Hard Disk' <<< "$container_log" || true
)"
dvd_boots="$(
grep -Fci 'Booting from DVD/CD' <<< "$container_log" || true
)"
not_bootable_disk="$(
grep -Fci \
'Boot failed: not a bootable disk' \
<<< "$container_log" || true
)"
unreadable_boot_disk="$(
grep -Fci \
'Boot failed: could not read the boot disk' \
<<< "$container_log" || true
)"
no_bootable_device="$(
grep -Fci 'No bootable device.' <<< "$container_log" || true
)"
bootmgr_missing="$(
grep -Fci 'BOOTMGR is missing' <<< "$container_log" || true
)"
reboots=$((bios_starts > 0 ? bios_starts - 1 : 0))
if (( first_bios_start < 0 && bios_starts > 0 )); then
first_bios_start=$SECONDS
fi
if (( first_bios_start >= 0 &&
SECONDS - first_bios_start >= reboot_timeout &&
reboots < minimum_reboots )); then
echo
echo "------------------------------------------------------------"
echo "The installation did not reboot within" \
"$((reboot_timeout / 60)) minutes after the first BIOS start."
echo "Observed reboots: $reboots"
echo "Required reboots: $minimum_reboots"
exit 1
fi
if (( bios_starts >= boot_loop_limit )) &&
(( hard_disk_boots >= boot_loop_limit ||
dvd_boots >= boot_loop_limit ||
not_bootable_disk >= boot_loop_limit ||
unreadable_boot_disk >= boot_loop_limit ||
no_bootable_device >= boot_loop_limit ||
bootmgr_missing >= boot_loop_limit )); then
echo
echo "------------------------------------------------------------"
echo "Detected a repeated BIOS boot loop."
echo "SeaBIOS starts: $bios_starts"
echo "Hard disk boots: $hard_disk_boots"
echo "DVD boots: $dvd_boots"
echo "Not-bootable disk failures: $not_bootable_disk"
echo "Unreadable boot-disk failures: $unreadable_boot_disk"
echo "No-bootable-device failures: $no_bootable_device"
echo "BOOTMGR failures: $bootmgr_missing"
exit 1
fi
response=""
if [[ -s "$RUNNER_TEMP/data/windows.json" ]]; then
response="$(cat "$RUNNER_TEMP/data/windows.json")"
fi
if [[ -n "$response" ]]; then
stop_logs
trap - EXIT
echo
echo "------------------------------------------------------------"
echo "Received response:"
if ! jq . <<< "$response"; then
echo "$response"
echo "The guest returned invalid JSON."
exit 1
fi
token="$(jq -r '.token // empty' <<< "$response")"
caption="$(jq -r '.caption // empty' <<< "$response")"
edition="$(jq -r '.edition // empty' <<< "$response")"
version="$(jq -r '.version // empty' <<< "$response")"
build="$(jq -r '.build // empty' <<< "$response")"
platform="$(jq -r '.platform // empty' <<< "$response")"
share="$(jq -r '.share // empty' <<< "$response")"
if [[ "$token" != "$EXPECTED_TOKEN" ]]; then
echo "The response token does not match."
exit 1
fi
if [[ "$share" != "$EXPECTED_TOKEN" ]]; then
echo "Failed to read \\\\host.lan\\Data\\readme.txt."
echo "Expected contents:"
echo " $EXPECTED_TOKEN"
echo "Received:"
echo " ${share:-empty}"
exit 1
fi
normalized_caption="${caption//\(R\)/}"
normalized_expected_caption="${EXPECTED_CAPTION//\(R\)/}"
if [[ "$normalized_caption" != *"$normalized_expected_caption"* ]]; then
echo "Expected caption containing:"
echo " $EXPECTED_CAPTION"
echo "Received:"
echo " $caption"
exit 1
fi
normalized_edition="${edition%Eval}"
normalized_expected_edition="${EXPECTED_EDITION%Eval}"
if [[ -n "$EXPECTED_EDITION" &&
"$normalized_edition" != "$normalized_expected_edition" ]]; then
echo "Expected edition: $EXPECTED_EDITION"
echo "Received edition: $edition"
exit 1
fi
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Unexpected Windows version: $version"
exit 1
fi
if [[ ! "$build" =~ ^[0-9]+$ ]]; then
echo "Invalid Windows build number: $build"
exit 1
fi
if (( build < MINIMUM_BUILD )); then
echo "Expected build $MINIMUM_BUILD or newer."
echo "Received build: $build"
exit 1
fi
if [[ "$platform" != "$EXPECTED_PLATFORM" ]]; then
echo "Expected platform:"
echo " $EXPECTED_PLATFORM"
echo "Received platform:"
echo " $platform"
exit 1
fi
echo
echo "$DISPLAY_NAME installed successfully."
echo "Version: $version"
echo "Build: $build"
echo "Platform: $platform"
echo "Shared file: accessible"
exit 0
fi
sleep 10
done
echo
echo "------------------------------------------------------------"
echo "Timed out waiting for Windows installation."
exit 1
- name: Cleanup
if: always()
shell: bash
run: |
docker rm --force "$CONTAINER" 2>/dev/null || true
+107 -5
View File
@@ -2,6 +2,8 @@ name: Links
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
concurrency:
group: links
@@ -22,29 +24,63 @@ jobs:
with:
fetch-depth: 0
-
name: Validate Links
name: Validate all download mirrors
id: validate
run: |
errors=0
count=0
skipped=0
tested=0
host=""
failed_links=""
failed_links_html=""
declare -A seen
ignored_domains=(
files.dog
bobpony.com
)
wget https://github.com/lwthiker/curl-impersonate/releases/download/v0.6.1/curl-impersonate-v0.6.1.x86_64-linux-gnu.tar.gz
tar -xzf curl-impersonate-v0.6.1.x86_64-linux-gnu.tar.gz
sudo cp curl-impersonate-ff /usr/local/bin/
check() {
local url="$1" http
http=$(curl-impersonate-ff -sSL -o /dev/null -w "%{http_code}" --max-time 10 -I -- "$url" 2>&1) || http="000"
[[ "$http" == 2* ]] && return 0
http=$(curl-impersonate-ff -sSL -o /dev/null -w "%{http_code}" --max-time 10 -r "0-0" -- "$url" 2>&1) || http="000"
[[ "$http" == 2* ]]
}
isIgnoredDomain() {
local url="$1"
local domain ignored
domain="${url#*://}"
domain="${domain%%/*}"
domain="${domain%%:*}"
domain="${domain,,}"
for ignored in "${ignored_domains[@]}"; do
if [[ "$domain" == "$ignored" || "$domain" == *."$ignored" ]]; then
return 0
fi
done
return 1
}
while IFS= read -r line; do
if [[ "$line" =~ ^[[:space:]]*local[[:space:]]+host=\"(https://[^\"]+)\" ]]; then
host="${BASH_REMATCH[1]%/}"
continue
fi
[[ "$line" =~ ^[[:space:]]*url=\"(.+)\" ]] || continue
val="${BASH_REMATCH[1]#/}"
if [[ "$val" == https://* ]]; then
url="$val"
elif [[ -n "$host" ]]; then
@@ -52,16 +88,82 @@ jobs:
else
continue
fi
[[ -v seen[$url] ]] && continue
seen[$url]=1
count=$((count + 1))
if isIgnoredDomain "$url"; then
echo "SKIP: $url (ignored domain)"
skipped=$((skipped + 1))
continue
fi
tested=$((tested + 1))
if check "$url"; then
echo " OK: $url"
else
echo "FAIL: $url"
errors=$((errors + 1))
failed_links+="$url"$'\n'
failed_links_html+="<li><a href=\"$url\">$url</a></li>"$'\n'
fi
done < "src/define.sh"
echo ""
printf '%d/%d links valid\n' "$(( count - errors ))" "$count"
(( errors == 0 ))
printf '%d/%d tested links valid\n' "$((tested - errors))" "$tested"
if (( skipped > 0 )); then
printf '%d link(s) skipped for ignored domains\n' "$skipped"
fi
if (( errors > 0 )); then
printf '%d blocking failure(s)\n' "$errors"
{
echo "failed=true"
echo "links<<EOF"
printf '%s' "$failed_links"
echo "EOF"
echo "html_links<<EOF"
printf '%s' "$failed_links_html"
echo "EOF"
} >> "$GITHUB_OUTPUT"
else
echo "failed=false" >> "$GITHUB_OUTPUT"
fi
-
name: Send mail
if: steps.validate.outputs.failed == 'true'
uses: action-pack/send-mail@v1
with:
to: ${{secrets.MAILTO}}
from: Github Actions <${{secrets.MAILTO}}>
connection_url: ${{secrets.MAIL_CONNECTION}}
subject: Mirror verification failed!
body: |
Mirror verification failed!
The following links failed:
${{ steps.validate.outputs.links }}
See https://github.com/${{ github.repository }}/actions for more information.
html_body: |
<p>Mirror verification failed!</p>
<p>The following links failed:</p>
<ul>
${{ steps.validate.outputs.html_links }}
</ul>
<p>
See
<a href="https://github.com/${{ github.repository }}/actions">GitHub Actions</a>
for more information.
</p>
-
name: Fail workflow
if: always() && steps.validate.outputs.failed == 'true'
run: exit 1
+249
View File
@@ -0,0 +1,249 @@
name: Validation
on:
release:
types:
- published
workflow_dispatch:
permissions:
contents: read
concurrency:
group: validation-${{ github.ref }}
cancel-in-progress: true
jobs:
desktop:
strategy:
fail-fast: false
matrix:
include:
- name: Windows 11 Pro
version: "11"
expected_caption: Windows 11
expected_edition: Professional
minimum_build: 22000
platform: x64
- name: Windows 11 LTSC
version: "11l"
expected_caption: Windows 11
expected_edition: EnterpriseS
minimum_build: 26100
platform: x64
- name: Windows 11 Enterprise
version: "11e"
expected_caption: Windows 11
expected_edition: Enterprise
minimum_build: 22000
platform: x64
- name: Windows 11 IoT Enterprise
version: "11i"
expected_caption: Windows 11
expected_edition: IoTEnterpriseS
minimum_build: 26100
platform: x64
- name: Windows 10 Pro
version: "10"
expected_caption: Windows 10
expected_edition: Professional
minimum_build: 10240
platform: x64
- name: Windows 10 LTSC
version: "10l"
expected_caption: Windows 10
expected_edition: EnterpriseS
minimum_build: 17763
platform: x64
- name: Windows 10 Enterprise
version: "10e"
expected_caption: Windows 10
expected_edition: Enterprise
minimum_build: 10240
platform: x64
- name: Windows 10 IoT Enterprise
version: "10i"
expected_caption: Windows 10
expected_edition: IoTEnterpriseS
minimum_build: 19044
platform: x64
- name: Windows 8.1 Enterprise
version: "8e"
expected_caption: Windows 8.1
expected_edition: Enterprise
minimum_build: 9600
platform: x64
name: ${{ matrix.name }}
uses: ./.github/workflows/install.yml
with:
name: ${{ matrix.name }}
version: ${{ matrix.version }}
expected_caption: ${{ matrix.expected_caption }}
expected_edition: ${{ matrix.expected_edition }}
minimum_build: ${{ matrix.minimum_build }}
platform: ${{ matrix.platform }}
tiny:
strategy:
fail-fast: false
matrix:
include:
- name: Core11
version: "core11"
expected_caption: Windows 11
minimum_build: 22000
platform: x64
- name: Tiny11
version: "tiny11"
expected_caption: Windows 11
minimum_build: 22000
platform: x64
- name: Tiny10
version: "tiny10"
expected_caption: Windows 10
minimum_build: 19041
platform: x64
name: ${{ matrix.name }}
uses: ./.github/workflows/install.yml
with:
name: ${{ matrix.name }}
version: ${{ matrix.version }}
expected_caption: ${{ matrix.expected_caption }}
minimum_build: ${{ matrix.minimum_build }}
platform: ${{ matrix.platform }}
server:
strategy:
fail-fast: false
matrix:
include:
- name: Windows Server 2025
version: "2025"
expected_caption: Windows Server 2025
minimum_build: 26100
platform: x64
- name: Windows Server 2022
version: "2022"
expected_caption: Windows Server 2022
minimum_build: 20348
platform: x64
- name: Windows Server 2019
version: "2019"
expected_caption: Windows Server 2019
minimum_build: 17763
platform: x64
- name: Windows Server 2016
version: "2016"
expected_caption: Windows Server 2016
minimum_build: 14393
platform: x64
- name: Windows Server 2012 R2
version: "2012"
expected_caption: Windows Server 2012 R2
minimum_build: 9600
platform: x64
name: ${{ matrix.name }}
uses: ./.github/workflows/install.yml
with:
name: ${{ matrix.name }}
version: ${{ matrix.version }}
expected_caption: ${{ matrix.expected_caption }}
minimum_build: ${{ matrix.minimum_build }}
platform: ${{ matrix.platform }}
legacy:
strategy:
fail-fast: false
matrix:
include:
- name: Windows 7 Ultimate
version: "7u"
cpu: host
expected_caption: Windows 7
minimum_build: 7601
platform: x64
- name: Windows 7 Ultimate x86
version: "7x86"
cpu: host
expected_caption: Windows 7
minimum_build: 7601
platform: x86
- name: Windows Vista Ultimate
version: "vu"
cpu: host
expected_caption: Windows Vista
minimum_build: 6002
platform: x64
- name: Windows Vista Ultimate x86
version: "vistax86"
cpu: host
expected_caption: Windows Vista
minimum_build: 6002
platform: x86
- name: Windows XP Professional
version: "xp"
cpu: host
expected_caption: Windows XP
minimum_build: 2600
platform: x86
- name: Windows XP Professional x64
version: "xp64"
cpu: host
expected_caption: Windows XP
minimum_build: 3790
platform: x64
- name: Windows 2000 Professional
version: "2k"
cpu: host
expected_caption: Windows 2000
minimum_build: 2195
platform: x86
- name: Windows Server 2008 R2
version: "2008"
cpu: host
expected_caption: Windows Server 2008 R2
minimum_build: 7601
platform: x64
- name: Windows Server 2003 R2
version: "2003"
cpu: host
expected_caption: Windows Server 2003
minimum_build: 3790
platform: x64
name: ${{ matrix.name }}
uses: ./.github/workflows/install.yml
with:
name: ${{ matrix.name }}
version: ${{ matrix.version }}
callback: legacy
cpu: ${{ matrix.cpu }}
expected_caption: ${{ matrix.expected_caption }}
minimum_build: ${{ matrix.minimum_build }}
platform: ${{ matrix.platform }}
+1
View File
@@ -161,6 +161,7 @@ Also see [Dynamic memory allocation](https://github.com/qemus/qemu/blob/master/d
| `VERIFY` | `N` | Verifies downloaded installation media against predefined checksums. |
| `REMOVE` | `Y` | Deletes the downloaded Windows ISO after installation to save space. |
| `MANUAL` | `N` | Enables manual installation instead of unattended installation. |
| `REBUILD` | `Y` | Skips rebuilding the Windows ISO and uses the original image unchanged. |
| `COMMAND` | | Command to be executed during the final step of automatic installation. |
## 🔌 Shutdown
+530 -366
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -11,6 +11,7 @@ set -Eeuo pipefail
: "${EDITION:=""}"
: "${MANUAL:=""}"
: "${REMOVE:=""}"
: "${REBUILD:=""}"
: "${VERSION:=""}"
: "${COMMAND:=""}"
: "${DETECTED:=""}"
@@ -776,7 +777,7 @@ normalizeEditionID() {
case "$edition" in
"pro" | "professional" | "business" )
edition="" ;;
"pro-n" | "professional-n" )
"pro-n" | "pron" | "professional-n" | "professionaln" )
edition="n" ;;
esac
+535 -137
View File
@@ -184,7 +184,7 @@ getVersions() {
groups_ref=()
indexes_ref=()
platform=$(getPlatform "$xml")
platform=$(getPlatform "$xml") || return 1
count=$(xmllint --nonet --xpath 'count(/WIM/IMAGE)' - 2>/dev/null <<< "$xml") || return 0
for ((i=1; i<=count; i++)); do
@@ -220,6 +220,21 @@ getVersions() {
continue
fi
local evaluation=""
if [[ "${image,,}" == *"evaluation"* ||
"${display,,}" == *"evaluation"* ||
"${product,,}" == *"evaluation"* ||
"${edition_id,,}" == *"eval"* ||
"${flags,,}" == *"eval"* ]]; then
evaluation="-eval"
fi
if [ -n "$evaluation" ] &&
[[ "${candidate_id,,}" != *"-eval" ]]; then
candidate_id+="$evaluation"
fi
local key="${candidate_id,,}"
# Some client media use the same friendly name-derived ID for distinct
@@ -230,10 +245,10 @@ getVersions() {
case "${candidate_base,,}" in
"winvista"* | "win7"* | "win8"* | "win10"* | "win11"* )
structured=$(normalizeEditionID "${edition_id:-${flags:-}}" "$candidate_base")
structured=$(normalizeEditionID "${edition_id:-${flags:-}}" "$candidate_base") || return 1
;;
"win20"* )
structured=$(normalizeServerEditionID "${flags:-$edition_id}")
structured=$(normalizeServerEditionID "${flags:-$edition_id}") || return 1
# Some media use the same EDITIONID for Core and Desktop images.
# INSTALLATIONTYPE provides the structural distinction without
@@ -246,7 +261,7 @@ getVersions() {
esac
if [ -n "$structured" ]; then
candidate_id="$candidate_base-$structured"
candidate_id="$candidate_base-$structured$evaluation"
key="${candidate_id,,}"
fi
fi
@@ -327,7 +342,7 @@ selectEdition() {
if [ -n "$EDITION" ]; then
for base in "${edition_bases[@]}"; do
edition=$("$normalize_name" "$EDITION" "$base")
edition=$("$normalize_name" "$EDITION" "$base") || return 1
preferred+=("$base${edition:+-$edition}")
done
@@ -429,7 +444,7 @@ detectVersion() {
versions \
bases \
groups \
image_indexes
image_indexes || return 1
[ "${#versions[@]}" -eq 0 ] && return 0
@@ -464,7 +479,7 @@ detectLanguage() {
local xml="$1"
local index="${2:-}"
local xpath lang
local xpath lang culture
if [[ "$index" =~ ^[0-9]+$ ]]; then
xpath="string((/WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/DEFAULT | /WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/FALLBACK/DEFAULT)[1])"
@@ -479,9 +494,12 @@ detectLanguage() {
return 0
fi
local culture
culture=$(getLanguage "$lang" "culture")
[ -n "$culture" ] && LANGUAGE="$lang" && return 0
culture=$(getLanguage "$lang" "culture") || return 1
if [ -n "$culture" ]; then
LANGUAGE="$lang"
return 0
fi
warn "Invalid language detected: \"$lang\""
return 0
@@ -502,60 +520,105 @@ skipVersion() {
detectLegacy() {
local dir="$1"
local find
local marker
[[ "${PLATFORM,,}" != "x64" ]] && return 1
[[ "${PLATFORM,,}" == "x64" ]] || return 1
find=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit)
[ -n "$find" ] && DETECTED="win95" && return 0
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit) || return 1
find=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit)
[ -n "$find" ] && DETECTED="win98" && return 0
if [ -n "$marker" ]; then
DETECTED="win95"
return 0
fi
find=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit)
[ -n "$find" ] && DETECTED="win9x" && return 0
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit) || return 1
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_W.40 -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_S.40 -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_TS.40 -print -quit)
[ -n "$find" ] && DETECTED="winnt4" && return 0
if [ -n "$marker" ]; then
DETECTED="win98"
return 0
fi
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit)
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit) || return 1
if [ -n "$find" ]; then
if [ -n "$marker" ]; then
DETECTED="win9x"
return 0
fi
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IA.5 -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_ID.5 -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IP.5 -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IS.5 -print -quit)
[ -n "$find" ] && DETECTED="win2k" && return 0
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname CDROM_W.40 -o \
-iname CDROM_S.40 -o \
-iname CDROM_TS.40 \
\) \
-print -quit) || return 1
if [ -n "$marker" ]; then
DETECTED="winnt4"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit) || return 1
if [ -n "$marker" ]; then
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname CDROM_IA.5 -o \
-iname CDROM_ID.5 -o \
-iname CDROM_IP.5 -o \
-iname CDROM_IS.5 \
\) \
-print -quit) || return 1
if [ -n "$marker" ]; then
DETECTED="win2k"
return 0
fi
fi
find=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit)
marker=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit) || return 1
[ -n "$marker" ] || return 1
if [ -n "$find" ]; then
marker=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit) || return 1
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit)
[ -n "$find" ] && DETECTED="winxpx64" && return 0
if [ -n "$marker" ]; then
DETECTED="winxpx64"
return 0
fi
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IC -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IP -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname setupxp.htm -print -quit)
[ -n "$find" ] && DETECTED="winxpx86" && return 0
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname WIN51IC -o \
-iname WIN51IP -o \
-iname setupxp.htm \
\) \
-print -quit) || return 1
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IS -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IA -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IB -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51ID -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IL -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AA -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AD -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AS -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51MA -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51MD -print -quit)
[ -n "$find" ] && DETECTED="win2003r2" && return 0
if [ -n "$marker" ]; then
DETECTED="winxpx86"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname WIN51IS -o \
-iname WIN51IA -o \
-iname WIN51IB -o \
-iname WIN51ID -o \
-iname WIN51IL -o \
-iname WIN51AA -o \
-iname WIN51AD -o \
-iname WIN51AS -o \
-iname WIN51MA -o \
-iname WIN51MD \
\) \
-print -quit) || return 1
if [ -n "$marker" ]; then
DETECTED="win2003r2"
return 0
fi
return 1
@@ -592,15 +655,15 @@ resolveImage() {
setImage() {
skipVersion "${DETECTED,,}" && return 0
setXML "" && return 0
enabled "$MANUAL" && return 0
if ! setXML "" && ! enabled "$MANUAL"; then
MANUAL="Y"
MANUAL="Y"
local desc
desc=$(printEdition "$DETECTED" "this version")
warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
fi
local desc
desc=$(printEdition "$DETECTED" "this version") || return 1
warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
return 0
}
@@ -662,20 +725,19 @@ getSuggestion() {
validateEdition() {
[ -n "$EDITION" ] || return 0
[[ "${DETECTED,,}" == "win20"* ]] || return 0
case "${DETECTED,,}" in
"win20"* )
local edition
edition=$(normalizeServerEditionID "$EDITION")
local edition
edition=$(normalizeServerEditionID "$EDITION") || return 1
if [ -n "$edition" ] &&
[[ "${DETECTED,,}" != *"-${edition,,}" &&
"${DETECTED,,}" != *"-${edition,,}-eval" ]]; then
EDITION=""
fi
;;
esac
[ -n "$edition" ] || return 0
if [[ "${DETECTED,,}" == *"-${edition,,}" ||
"${DETECTED,,}" == *"-${edition,,}-eval" ]]; then
return 0
fi
EDITION=""
return 0
}
@@ -700,13 +762,12 @@ describeImage() {
local result_name="$3"
local result
result=$(printEdition "$DETECTED" "$DETECTED" "Y")
detectLanguage "$info_xml" "$index"
result=$(printEdition "$DETECTED" "$DETECTED" "Y") || return 1
detectLanguage "$info_xml" "$index" || return 1
if [[ "${LANGUAGE,,}" != "en" && "${LANGUAGE,,}" != "en-"* ]]; then
local language
language=$(getLanguage "$LANGUAGE" "desc")
language=$(getLanguage "$LANGUAGE" "desc") || return 1
result+=" ($language)"
fi
@@ -730,13 +791,18 @@ configureImage() {
local msg="the answer file for $desc was not found ($DETECTED.xml)"
local fallback="/run/assets/${DETECTED%%-*}.xml"
if setXML "$fallback" "$index" || enabled "$MANUAL"; then
! enabled "$MANUAL" && warn "${msg}."
else
MANUAL="Y"
warn "${msg}, $FB."
if setXML "$fallback" "$index"; then
if ! enabled "$MANUAL"; then
warn "${msg}."
fi
return 0
fi
enabled "$MANUAL" && return 0
MANUAL="Y"
warn "${msg}, $FB."
return 0
}
@@ -748,9 +814,7 @@ detectImage() {
XML=""
resolveImage "$version" || :
if [ -n "$DETECTED" ]; then
if resolveImage "$version"; then
setImage || return 1
return 0
fi
@@ -758,7 +822,7 @@ detectImage() {
info "Detecting version from ISO image..."
if detectLegacy "$dir"; then
desc=$(printEdition "$DETECTED" "$DETECTED" "Y")
desc=$(printEdition "$DETECTED" "$DETECTED" "Y") || return 1
info "Detected: $desc"
return 0
fi
@@ -791,6 +855,65 @@ detectImage() {
return 0
}
normalizeBatch() {
local file="$1"
local bom tmp encoding
[ ! -s "$file" ] && return 0
bom=$(od -An -N2 -tx1 "$file" | tr -d ' \n') || return 1
case "$bom" in
"fffe" ) encoding="UTF-16LE" ;;
"feff" ) encoding="UTF-16BE" ;;
* ) return 0 ;;
esac
if ! tmp=$(mktemp "${file}.XXXXXX"); then
error "Failed to create temporary batch file!"
return 1
fi
if ! tail -c +3 "$file" | iconv -f "$encoding" -t UTF-8 > "$tmp"; then
rm -f "$tmp"
error "Failed to convert $file from $encoding to UTF-8!"
return 1
fi
if ! chmod --reference="$file" "$tmp" || ! mv -f "$tmp" "$file"; then
rm -f "$tmp"
error "Failed to replace batch file: $file"
return 1
fi
return 0
}
reportBatchMatches() {
local file="$1"
local source="$2"
local pattern="$3"
local message="$4"
local suggestion="$5"
local matches line
matches=$(grep -Pin "$pattern" "$file" || true)
[ -n "$matches" ] || return 0
warn "$message in $source:"
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
printf ' %s\n\n' "$suggestion" >&2
return 0
}
checkBatch() {
local file="$1"
@@ -798,7 +921,7 @@ checkBatch() {
local tmp output
local matches line
[ ! -f "$file" ] && return 0
[ -s "$file" ] || return 0
if ! tmp=$(mktemp -d /tmp/blinter.XXXXXX); then
warn "failed to create temporary Blinter directory."
@@ -809,14 +932,20 @@ checkBatch() {
[ -n "${COMMAND:-}" ] && source="your COMMAND variable"
if enabled "$DEBUG"; then
report="Y"
if LC_ALL=C grep -Pq '[^\x09\x0D\x20-\x7E]' "$file"; then
warn "non-ASCII characters were detected in $source and may not execute correctly in Windows Command Prompt."
fi
else
# First pass: silently check only for Error-level findings.
cat > "$tmp/blinter.ini" <<'EOF'
cat > "$tmp/blinter.ini" <<'EOC'
[general]
min_severity = error
EOF
EOC
if ! (
cd "$tmp"
@@ -831,14 +960,14 @@ EOF
# Show useful diagnostic context, while excluding findings that are
# irrelevant to unattended OEM scripts.
cat > "$tmp/blinter.ini" <<'EOF'
cat > "$tmp/blinter.ini" <<'EOC'
[general]
min_severity = warning
show_summary = false
[rules]
disabled_rules = W001,W028,W041,SEC002,SEC005
EOF
EOC
output=$(
cd "$tmp"
@@ -879,75 +1008,344 @@ EOF
fi
rm -rf "$tmp"
rm -rf "$tmp" || true
matches=$(
grep -Pin \
'(?<!\\)\\host[.]lan[\\]' \
"$file" || true
)
reportBatchMatches \
"$file" \
"$source" \
'(?<!\\)\\host[.]lan[\\]' \
"invalid single-backslash UNC path detected" \
'Use "\\host.lan\Data\..." instead of "\host.lan\Data\...".'
if [ -n "$matches" ]; then
warn "invalid single-backslash UNC path detected in $source:"
reportBatchMatches \
"$file" \
"$source" \
'(?<![\\[:alnum:]._-])host[.]lan[\\]' \
"UNC path without leading backslashes detected" \
'Use "\\host.lan\Data\..." instead of "host.lan\Data\...".'
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
reportBatchMatches \
"$file" \
"$source" \
'//host[.]lan/' \
"invalid forward-slash UNC path detected" \
'Use "\\host.lan\Data\..." instead of "//host.lan/Data/...".'
printf '%s\n\n' \
' Use "\\host.lan\Data\..." instead of "\host.lan\Data\...".' >&2
reportBatchMatches \
"$file" \
"$source" \
'\\\\host[.]lan\\shared(?:[\\/]|$)' \
"invalid Samba share name detected" \
'The "/shared" folder is exposed to Windows as "\\host.lan\Data".'
return 0
}
getBootLoadSize() {
local iso="$1"
local desc="$2"
local boot_info value
if ! boot_info=$(isoinfo -d -i "$iso"); then
case "${DETECTED,,}" in
"win2k"* | "winxp"* | "win2003"* )
BOOT_LOAD_SIZE=4
return 0
;;
esac
error "Failed to read boot image information from $desc ISO!"
return 1
fi
matches=$(
grep -Pin \
'(?<![\\[:alnum:]._-])host[.]lan[\\]' \
"$file" || true
)
value=$(awk '/Nsect / { print $NF; exit }' <<< "$boot_info")
if [ -n "$matches" ]; then
warn "UNC path without leading backslashes detected in $source:"
if [ -z "$value" ]; then
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
case "${DETECTED,,}" in
"win2k"* | "winxp"* | "win2003"* )
BOOT_LOAD_SIZE=4
return 0
;;
esac
printf '%s\n\n' \
' Use "\\host.lan\Data\..." instead of "host.lan\Data\...".' >&2
error "Failed to determine boot image load size from $desc ISO!"
return 1
fi
matches=$(
grep -Pin \
'//host[.]lan/' \
"$file" || true
)
if [ -n "$matches" ]; then
warn "invalid forward-slash UNC path detected in $source:"
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
printf '%s\n\n' \
' Use "\\host.lan\Data\..." instead of "//host.lan/Data/...".' >&2
if [[ ! "$value" =~ ^[0-9]+$ ]] || [ "${#value}" -gt 5 ]; then
error "Invalid boot image load size found in $desc ISO!"
return 1
fi
matches=$(
grep -Pin \
'\\\\host[.]lan\\shared(?:[\\/]|$)' \
"$file" || true
)
BOOT_LOAD_SIZE=$((10#$value))
if [ -n "$matches" ]; then
warn "invalid Samba share name detected in $source:"
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
printf '%s\n\n' \
' The "/shared" folder is exposed to Windows as "\\host.lan\Data".' >&2
if (( BOOT_LOAD_SIZE < 1 || BOOT_LOAD_SIZE > 65535 )); then
error "Invalid boot image load size found in $desc ISO!"
return 1
fi
return 0
}
extractBootImage() {
local iso="$1"
local dir="$2"
local desc="$3"
local tmp="$TMP/boot-images"
local max_size=$((32 * 1024 * 1024))
local rc size offset image=""
local msg="using legacy extraction..."
local boot_info
local -a images=()
ETFS="boot.img"
[ -s "$dir/$ETFS" ] && return 0
rm -f "$dir/$ETFS" || return 1
rm -rf "$tmp" || return 1
if LC_ALL=C xorriso \
-no_rc \
-osirrox on \
-indev "$iso" \
-extract_boot_images "$tmp" >/dev/null 2>&1; then
mapfile -t images < <(
find "$tmp" \
-maxdepth 1 \
-type f \
-name 'eltorito_img*_bios.img' \
-print
)
if (( ${#images[@]} == 1 )); then
image="${images[0]}"
if [ ! -s "$image" ]; then
warn "The extracted BIOS boot image is empty, $msg"
elif ! size=$(stat -c%s "$image"); then
warn "Failed to determine the BIOS boot image size, $msg"
elif (( size > max_size )); then
warn "The extracted BIOS boot image exceeds 32 MB, $msg"
else
if ! mv -f "$image" "$dir/$ETFS"; then
rm -rf "$tmp" || true
error "Failed to save boot image from $desc ISO!"
return 1
fi
rm -rf "$tmp" || return 1
return 0
fi
elif (( ${#images[@]} > 1 )); then
warn "Multiple BIOS boot images were found, $msg"
else
warn "No BIOS boot image was found, $msg"
fi
else
rc=$?
if (( rc > 128 )); then
rm -rf "$tmp" || true
exit "$rc"
fi
warn "Failed to extract the BIOS boot image, $msg"
fi
rm -rf "$tmp" || true
local boot_info
if ! boot_info=$(isoinfo -d -i "$iso"); then
error "Failed to read boot image information from $desc ISO!"
return 1
fi
offset=$(awk '/Bootoff / { print $NF; exit }' <<< "$boot_info")
if [ -z "$offset" ]; then
error "Failed to determine boot image offset from $desc ISO!"
return 1
fi
if [[ ! "$offset" =~ ^[0-9]+$ ]]; then
error "Invalid boot image location found in $desc ISO!"
return 1
fi
if ! dd \
"if=$iso" \
"of=$dir/$ETFS" \
bs=2048 \
"count=$BOOT_LOAD_SIZE" \
"skip=$offset" \
status=none; then
rm -f "$dir/$ETFS" || true
error "Failed to extract boot image from $desc ISO!"
return 1
fi
if [ ! -s "$dir/$ETFS" ]; then
rm -f "$dir/$ETFS" || true
error "Failed to locate file \"$ETFS\" in $desc ISO image!"
return 1
fi
return 0
}
buildImage() {
local dir="$1"
local failed=""
local cat="BOOT.CAT"
local log="/run/shm/iso.log"
local base size desc
if [ -f "$BOOT" ]; then
error "File $BOOT does already exist?!" && return 1
fi
base=$(basename "$BOOT")
local out="$TMP/${base%.*}.tmp"
rm -f "$out"
desc=$(printVariant "$DETECTED" "ISO")
local msg="Building $desc image"
info "$msg..." && html "$msg..."
[ -z "$LABEL" ] && LABEL="Windows"
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
error "Failed to locate file \"$ETFS\" in ISO image!" && return 1
fi
if [[ "${BOOT_MODE,,}" == "windows_legacy" &&
"${DETECTED,,}" != "win9"* &&
-z "${BOOT_LOAD_SIZE:-}" ]]; then
error "Failed to determine the boot image load size!"
return 1
fi
size=$(du -b --max-depth=0 "$dir" | cut -f1)
checkFreeSpace "$TMP" "$size" || return 1
/run/progress.sh "$out" "$size" "$msg ([P])..." &
if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then
genisoimage \
-o "$out" \
-b "$ETFS" \
-no-emul-boot \
-c "$cat" \
-iso-level 4 \
-J \
-l \
-D \
-N \
-joliet-long \
-relaxed-filenames \
-V "${LABEL::30}" \
-udf \
-boot-info-table \
-eltorito-alt-boot \
-eltorito-boot "$EFISYS" \
-no-emul-boot \
-allow-limited-size \
-quiet \
"$dir" 2> "$log" || failed="y"
else
case "${DETECTED,,}" in
"win2k"* | "winxp"* | "win2003"* )
genisoimage \
-o "$out" \
-b "$ETFS" \
-no-emul-boot \
-boot-load-seg 1984 \
-boot-load-size "$BOOT_LOAD_SIZE" \
-c "$cat" \
-iso-level 2 \
-J \
-l \
-D \
-N \
-joliet-long \
-relaxed-filenames \
-V "${LABEL::30}" \
-quiet \
"$dir" 2> "$log" || failed="y"
;;
"win9"* )
genisoimage \
-o "$out" \
-b "$ETFS" \
-J \
-r \
-V "${LABEL::30}" \
-quiet \
"$dir" 2> "$log" || failed="y"
;;
* )
genisoimage \
-o "$out" \
-b "$ETFS" \
-no-emul-boot \
-boot-load-size "$BOOT_LOAD_SIZE" \
-c "$cat" \
-iso-level 2 \
-J \
-l \
-D \
-N \
-joliet-long \
-relaxed-filenames \
-V "${LABEL::30}" \
-udf \
-allow-limited-size \
-quiet \
"$dir" 2> "$log" || failed="y"
;;
esac
fi
fKill "progress.sh"
if [ -n "$failed" ]; then
[ -s "$log" ] && echo "$(<"$log")"
error "Failed to build image!" && return 1
fi
local err=""
local hide="Warning: creating filesystem that does not conform to ISO-9660."
[ -s "$log" ] && err="$(<"$log")"
if [ -n "$err" ] && [[ "$err" != "$hide" ]]; then
echo "$err"
fi
mv -f "$out" "$BOOT" || return 1
if ! setOwner "$BOOT"; then
warn "Failed to set the owner for \"$BOOT\" !"
fi
return 0
}
return 0
+59 -107
View File
@@ -696,25 +696,27 @@ setMachine() {
local iso="$2"
local dir="$3"
local desc="$4"
local legacy=""
if ! disabled "$REBUILD"; then
case "${id,,}" in
"win2k"* ) legacy="2k" ;;
"winxp"* ) legacy="xp" ;;
"win2003"* ) legacy="2k3" ;;
esac
if [ -n "$legacy" ]; then
if ! legacyInstall "$iso" "$dir" "$desc" "$legacy"; then
error "Failed to prepare $desc ISO!"
return 1
fi
fi
fi
case "${id,,}" in
"win9"* )
ETFS="[BOOT]/Boot-1.44M.img" ;;
"win2k"* )
if ! legacyInstall "$iso" "$dir" "$desc" "2k"; then
error "Failed to prepare $desc ISO!" && return 1
fi ;;
"winxp"* )
if ! legacyInstall "$iso" "$dir" "$desc" "xp"; then
error "Failed to prepare $desc ISO!" && return 1
fi ;;
"win2003"* )
if ! legacyInstall "$iso" "$dir" "$desc" "2k3"; then
error "Failed to prepare $desc ISO!" && return 1
fi ;;
esac
case "${id,,}" in
"win9"* )
USB="no"
VGA="cirrus"
@@ -722,6 +724,7 @@ setMachine() {
MACHINE="pc-i440fx-2.4"
BOOT_MODE="windows_legacy"
[ -z "${ADAPTER:-}" ] && ADAPTER="pcnet" ;;
"win2k"* )
VGA="cirrus"
MACHINE="pc"
@@ -729,6 +732,7 @@ setMachine() {
DISK_TYPE="auto"
BOOT_MODE="windows_legacy"
[ -z "${ADAPTER:-}" ] && ADAPTER="rtl8139" ;;
"winxp"* )
DISK_TYPE="blk"
BOOT_MODE="windows_legacy"
@@ -736,26 +740,30 @@ setMachine() {
if [ -z "${CPU_MODEL:-}" ] || [[ "${CPU_MODEL:-}" == "host" ]]; then
# Workaround for boot loop on AMD EPYC processors
if [[ "${CPU,,}" == *"amd epyc"* ]]; then
if [[ "${id,,}" == "winxpx86"* && "${CPU,,}" == *"amd epyc"* ]]; then
CPU_MODEL="qemu32"
fi
fi
;;
fi ;;
"win2003"* )
DISK_TYPE="blk"
BOOT_MODE="windows_legacy"
[ -z "${SOUND:-}" ] && SOUND="usb-audio"
;;
[ -z "${SOUND:-}" ] && SOUND="usb-audio" ;;
"winvista"* | "win7"* | "win2008"* )
BOOT_MODE="windows_legacy" ;;
esac
case "${id,,}" in
"winxp"* | "win2003"* | "winvistax86"* | "win7x86"* | "win2008r2x86"* )
if isQ35 "${MACHINE:-q35}"; then
# Prevent bluescreen if 64 bit PCI hole size is >2G.
ARGS="-global q35-pcihost.x-pci-hole64-fix=false"
fi ;;
esac
return 0
@@ -769,26 +777,35 @@ prepareImage() {
desc=$(printVariant "$DETECTED" "$DETECTED")
# Adjust QEMU machine configuration for legacy versions
setMachine "$DETECTED" "$iso" "$dir" "$desc" || return 1
disabled "$REBUILD" && return 0
if [[ "${BOOT_MODE,,}" == "windows_legacy" &&
"${DETECTED,,}" != "win9"* ]]; then
getBootLoadSize "$iso" "$desc" || return 1
fi
skipVersion "$DETECTED" && return 0
if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
[ -f "$dir/$ETFS" ] && [ -s "$dir/$ETFS" ] &&
[ -f "$dir/$EFISYS" ] && [ -s "$dir/$EFISYS" ] && return 0
extractBootImage "$iso" "$dir" "$desc" && return 0
missing=$(basename "$dir/$EFISYS")
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
missing=$(basename "$dir/$ETFS")
fi
error "Failed to locate file \"${missing,,}\" in ISO image!"
error "Failed to extract boot image from ISO image \"${iso}\"!"
return 1
fi
legacyPrepare "$iso" "$dir" "$desc" && return 0
[ -f "$dir/$ETFS" ] && [ -s "$dir/$ETFS" ] &&
[ -f "$dir/$EFISYS" ] && [ -s "$dir/$EFISYS" ] && return 0
error "Failed to extract boot image from ISO image!"
missing=$(basename "$dir/$EFISYS")
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
missing=$(basename "$dir/$ETFS")
fi
error "Failed to locate file \"${missing,,}\" in ISO image!"
return 1
}
@@ -816,6 +833,10 @@ addFolder() {
file=$(find "$dest" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1
if [ -s "$file" ]; then
normalizeBatch "$file" || return 1
fi
if [ -n "$COMMAND" ]; then
[ -z "$file" ] && file="$dest/install.bat"
@@ -828,7 +849,7 @@ addFolder() {
fi
if [ -f "$file" ]; then
if [ -s "$file" ]; then
if ! unix2dos -q "$file"; then
error "Failed to convert $file to DOS format!"
@@ -962,6 +983,11 @@ updateImage() {
local dat="${xml//.xml/.dat}"
local desc path src wim name info
if disabled "$REBUILD"; then
info "Skipping modifications to the installation image..."
return 1
fi
skipVersion "${DETECTED,,}" && return 0
if [ ! -s "$asset" ] || [ ! -f "$asset" ]; then
@@ -1106,80 +1132,6 @@ removeImage() {
return 0
}
buildImage() {
local dir="$1"
local failed=""
local cat="BOOT.CAT"
local log="/run/shm/iso.log"
local base size desc
if [ -f "$BOOT" ]; then
error "File $BOOT does already exist?!" && return 1
fi
base=$(basename "$BOOT")
local out="$TMP/${base%.*}.tmp"
rm -f "$out"
desc=$(printVariant "$DETECTED" "ISO")
local msg="Building $desc image"
info "$msg..." && html "$msg..."
[ -z "$LABEL" ] && LABEL="Windows"
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
error "Failed to locate file \"$ETFS\" in ISO image!" && return 1
fi
size=$(du -b --max-depth=0 "$dir" | cut -f1)
checkFreeSpace "$TMP" "$size" || return 1
/run/progress.sh "$out" "$size" "$msg ([P])..." &
if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then
genisoimage -o "$out" -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 4 -J -l -D -N -joliet-long -relaxed-filenames -V "${LABEL::30}" \
-udf -boot-info-table -eltorito-alt-boot -eltorito-boot "$EFISYS" -no-emul-boot -allow-limited-size -quiet "$dir" 2> "$log" || failed="y"
else
case "${DETECTED,,}" in
"win2k"* | "winxp"* | "win2003"* )
genisoimage -o "$out" -b "$ETFS" -no-emul-boot -boot-load-seg 1984 -boot-load-size 4 -c "$cat" -iso-level 2 -J -l -D -N -joliet-long \
-relaxed-filenames -V "${LABEL::30}" -quiet "$dir" 2> "$log" || failed="y" ;;
"win9"* )
genisoimage -o "$out" -b "$ETFS" -J -r -V "${LABEL::30}" -quiet "$dir" 2> "$log" || failed="y" ;;
* )
genisoimage -o "$out" -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 2 -J -l -D -N -joliet-long -relaxed-filenames -V "${LABEL::30}" \
-udf -allow-limited-size -quiet "$dir" 2> "$log" || failed="y" ;;
esac
fi
fKill "progress.sh"
if [ -n "$failed" ]; then
[ -s "$log" ] && echo "$(<"$log")"
error "Failed to build image!" && return 1
fi
local err=""
local hide="Warning: creating filesystem that does not conform to ISO-9660."
[ -s "$log" ] && err="$(<"$log")"
[[ "$err" != "$hide" ]] && echo "$err"
mv -f "$out" "$BOOT" || return 1
if ! setOwner "$BOOT"; then
warn "Failed to set the owner for \"$BOOT\" !"
fi
return 0
}
bootWindows() {
ARGS=$(readState "$STORAGE/windows.args") || return 1