#!/usr/bin/env bash set -Eeuo pipefail handleCurlError() { local code="$1" local server="$2" local reason="${3:-}" local signal="" if [ -n "$reason" ] && (( code <= 125 )); then error "Request to $server servers failed: ${reason%.}." return 1 fi case "$code" in 126) error "The curl command could not be executed." ;; 127) error "The curl command was not found." ;; *) if (( code < 129 )); then error "Request to $server servers failed with curl exit status $code." return 1 fi signal=$(kill -l "$((code - 128))" 2>/dev/null || true) case "$signal" in INT) error "Curl was interrupted." ;; SEGV | ABRT) error "Curl crashed with signal $signal." ;; "") error "Curl terminated with exit status $code." ;; *) error "Curl terminated due to signal $signal." ;; esac ;; esac return 1 } curlRequest() { local output="$1" local server="$2" local agent="$3" shift 3 local log reason local rc=0 response="" if ! log=$(mktemp); then error "Failed to create a temporary curl log." return 1 fi { response=$(LC_ALL=C curl \ --silent \ --show-error \ --max-time 30 \ --user-agent "$agent" \ --fail \ --proto =https \ --tlsv1.2 \ --http1.1 \ "$@" 2>"$log") rc=$? } || : if (( rc != 0 )); then reason=$(sed -nE 's/^curl: \([0-9]+\) //p' "$log" | tail -n 1) rm -f "$log" handleCurlError "$rc" "$server" "$reason" return 1 fi rm -f "$log" if [ -n "$output" ]; then printf -v "$output" '%s' "$response" fi return 0 } getAgent() { local browser_version # Approximate Firefox version, increasing every two weeks browser_version="$((152 + ($(date +%s) - 1781568000) / 1209600))" echo "Mozilla/5.0 (X11; Linux x86_64; rv:${browser_version}.0) Gecko/20100101 Firefox/${browser_version}.0" return 0 } downloadWindows() { local id="$1" local lang="$2" local desc="$3" local ovToken="" ovTicks="" ovTime="" local skuId="" skuUrl="" skuJson="" local linkUrl="" linkJson="" link="" local language="" orgId="" ovData="" local instance="" vlsUrl="" ovUrl="" local session="" agent="" type="" local winVer="" page="" productId="" local rc=0 profile="606624d44113" agent=$(getAgent) language=$(getLanguage "$lang" "name") case "${id,,}" in "win11x64" ) winVer="11" && type="1" ;; "win11arm64" ) winVer="11arm64" && type="2" ;; * ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;; esac local url="https://www.microsoft.com/en-us/software-download/windows$winVer" # uuidgen: For MacOS (installed by default) and other systems (e.g. with no /proc) that don't have a kernel interface for generating random UUIDs if ! session=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random); then error "Failed to generate session ID!" return 1 fi session="${session//[![:print:]]/}" if [ -z "$session" ]; then error "Failed to generate session ID!" return 1 fi # Get product edition ID for latest release of given Windows version # Product edition ID: This specifies both the Windows release (e.g. 22H2) and edition ("multi-edition" is default, either Home/Pro/Edu/etc., we select "Pro" in the answer files) in one number # This is the *only* request we make that Fido doesn't. Fido manually maintains a list of all the Windows release/edition product edition IDs in its script (see: $WindowsVersions array). This is helpful for downloading older releases (e.g. Windows 10 1909, 21H1, etc.) but we always want to get the newest release which is why we get this value dynamically # Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden # Remove "Accept" header that curl sends by default enabled "$DEBUG" && echo "Parsing download page: ${url}" curlRequest page "Microsoft" "$agent" \ --header "Accept:" \ --max-filesize 1M \ -- "$url" || return 1 enabled "$DEBUG" && echo -n "Getting Product edition ID: " productId=$(echo "$page" | grep -Eo '