feat: Improve download function (#1826)

This commit is contained in:
Kroese
2026-07-07 22:49:06 +02:00
committed by GitHub
parent 2436d0e764
commit 23a1028d8c
+184 -164
View File
@@ -1,42 +1,42 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
handle_curl_error() { handleCurlError() {
local error_code="$1" local code="$1"
local server_name="$2" local server="$2"
case "$error_code" in case "$code" in
1) error "Unsupported protocol!" ;; 1) error "Unsupported protocol!" ;;
2) error "Failed to initialize curl!" ;; 2) error "Failed to initialize curl!" ;;
3) error "The URL format is malformed!" ;; 3) error "The URL format is malformed!" ;;
5) error "Failed to resolve address of proxy host!" ;; 5) error "Failed to resolve address of proxy host!" ;;
6) error "Failed to resolve $server_name servers! Is there an Internet connection?" ;; 6) error "Failed to resolve $server servers! Is there an Internet connection?" ;;
7) error "Failed to contact $server_name servers! Is there an Internet connection or is the server down?" ;; 7) error "Failed to contact $server servers! Is there an Internet connection or is the server down?" ;;
8) error "$server_name servers returned a malformed HTTP response!" ;; 8) error "$server servers returned a malformed HTTP response!" ;;
16) error "A problem was detected in the HTTP2 framing layer!" ;; 16) error "A problem was detected in the HTTP2 framing layer!" ;;
22) error "$server_name servers returned a failing HTTP status code!" ;; 22) error "$server servers returned a failing HTTP status code!" ;;
23) error "Failed at writing Windows media to disk! Out of disk space or permission error?" ;; 23) error "Failed at writing Windows media to disk! Out of disk space or permission error?" ;;
26) error "Failed to read Windows media from disk!" ;; 26) error "Failed to read Windows media from disk!" ;;
27) error "Ran out of memory during download!" ;; 27) error "Ran out of memory during download!" ;;
28) error "Connection timed out to $server_name server!" ;; 28) error "Connection timed out to $server server!" ;;
35) error "SSL connection error from $server_name server!" ;; 35) error "SSL connection error from $server server!" ;;
36) error "Failed to continue earlier download!" ;; 36) error "Failed to continue earlier download!" ;;
52) error "Received no data from the $server_name server!" ;; 52) error "Received no data from the $server server!" ;;
63) error "$server_name servers returned an unexpectedly large response!" ;; 63) error "$server servers returned an unexpectedly large response!" ;;
126) error "Curl command cannot be executed!" ;; 126) error "Curl command cannot be executed!" ;;
127) error "Curl command not found!" ;; 127) error "Curl command not found!" ;;
*) *)
if (( error_code <= 125 )); then if (( code <= 125 )); then
# Must be some other server or network error (possibly with this specific request/file) # Must be some other server or network error (possibly with this specific request/file)
# This is when accounting for all possible errors in the curl manual assuming a correctly formed # This is when accounting for all possible errors in the curl manual assuming a correctly formed
# curl command and an HTTP(S) request, using only the curl features we're using, and a sane build. # curl command and an HTTP(S) request, using only the curl features we're using, and a sane build.
error "Miscellaneous server or network error, reason: $error_code" error "Miscellaneous server or network error, reason: $code"
else else
case "$(kill -l "$error_code" 2>/dev/null || true)" in case "$(kill -l "$code" 2>/dev/null || true)" in
INT) error "Curl was interrupted!" ;; INT) error "Curl was interrupted!" ;;
SEGV | ABRT) error "Curl crashed! Please report any core dumps to curl developers." ;; SEGV | ABRT) error "Curl crashed! Please report any core dumps to curl developers." ;;
*) error "Curl terminated due to fatal signal $error_code !" ;; *) error "Curl terminated due to fatal signal $code !" ;;
esac esac
fi fi
;; ;;
@@ -45,7 +45,7 @@ handle_curl_error() {
return 1 return 1
} }
get_agent() { getAgent() {
local browser_version local browser_version
@@ -56,41 +56,42 @@ get_agent() {
return 0 return 0
} }
download_windows() { downloadWindows() {
local id="$1" local id="$1"
local lang="$2" local lang="$2"
local desc="$3" local desc="$3"
local rc=0 local rc=0
local ovw="" rticks="" mdt="" sku_id="" sku_url="" local ovToken="" ovTicks="" ovTime=""
local iso_url="" iso_json="" language="" org_id="" local skuId="" skuUrl="" skuJson=""
local instance_id="" vls_url="" ov_url="" ov_data="" local linkUrl="" linkJson="" link=""
local session_id="" user_agent="" download_type="" local language="" orgId=""
local windows_version="" iso_download_link="" local instance="" vlsUrl="" ovUrl="" ovData=""
local download_page_html="" product_edition_id="" local session="" agent="" type=""
local language_skuid_json="" local winVer="" page="" productId=""
local profile="606624d44113" local profile="606624d44113"
user_agent=$(get_agent) agent=$(getAgent)
language=$(getLanguage "$lang" "name") language=$(getLanguage "$lang" "name")
case "${id,,}" in case "${id,,}" in
"win11x64" ) windows_version="11" && download_type="1" ;; "win11x64" ) winVer="11" && type="1" ;;
"win11arm64" ) windows_version="11arm64" && download_type="2" ;; "win11arm64" ) winVer="11arm64" && type="2" ;;
* ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;; * ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
esac esac
local url="https://www.microsoft.com/en-us/software-download/windows$windows_version" 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 # 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_id=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random); then if ! session=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random); then
error "Failed to generate session ID!" error "Failed to generate session ID!"
return 1 return 1
fi fi
session_id="${session_id//[![:print:]]/}" session="${session//[![:print:]]/}"
if [ -z "$session_id" ]; then if [ -z "$session" ]; then
error "Failed to generate session ID!" error "Failed to generate session ID!"
return 1 return 1
fi fi
@@ -101,189 +102,189 @@ download_windows() {
# Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden # 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 # Remove "Accept" header that curl sends by default
enabled "$DEBUG" && echo "Parsing download page: ${url}" enabled "$DEBUG" && echo "Parsing download page: ${url}"
download_page_html=$(curl --silent --max-time 30 --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || { page=$(curl --silent --max-time 30 --user-agent "$agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
handle_curl_error "$?" "Microsoft" handleCurlError "$?" "Microsoft"
return $? return $?
} }
enabled "$DEBUG" && echo -n "Getting Product edition ID: " enabled "$DEBUG" && echo -n "Getting Product edition ID: "
product_edition_id=$(echo "$download_page_html" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16) productId=$(echo "$page" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)
enabled "$DEBUG" && echo "$product_edition_id" enabled "$DEBUG" && echo "$productId"
if [ -z "$product_edition_id" ]; then if [ -z "$productId" ]; then
error "Product edition ID not found!" error "Product edition ID not found!"
return 1 return 1
fi fi
# Microsoft download "protection" requires the sessionId to be whitelisted through vlscppe.microsoft.com/tags # Microsoft download "protection" requires the sessionId to be whitelisted through vlscppe.microsoft.com/tags
org_id="y6jn8c31" orgId="y6jn8c31"
vls_url="https://vlscppe.microsoft.com/tags?org_id=$org_id&session_id=$session_id" vlsUrl="https://vlscppe.microsoft.com/tags?org_id=$orgId&session_id=$session"
enabled "$DEBUG" && echo "Getting Session ID: $session_id" enabled "$DEBUG" && echo "Getting Session ID: $session"
# Permit Session ID # Permit Session ID
curl --silent --max-time 30 --output /dev/null --user-agent "$user_agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$vls_url" || { curl --silent --max-time 30 --output /dev/null --user-agent "$agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$vlsUrl" || {
# This should only happen if there's been some change to how this API works # This should only happen if there's been some change to how this API works
handle_curl_error "$?" "Microsoft" handleCurlError "$?" "Microsoft"
return $? return $?
} }
# Microsoft download "protection" also requires an ov-df.microsoft.com request/reply # Microsoft download "protection" also requires an ov-df.microsoft.com request/reply
# 1) Request mdt.js to get w and rticks. InstanceId is (currently) constant. # 1) Request mdt.js to get w and rticks. InstanceId is (currently) constant.
instance_id="560dc9f3-1aa5-4a2f-b63c-9e18f8d0e175" instance="560dc9f3-1aa5-4a2f-b63c-9e18f8d0e175"
ov_url="https://ov-df.microsoft.com/mdt.js?instanceId=$instance_id&PageId=si&session_id=$session_id" ovUrl="https://ov-df.microsoft.com/mdt.js?instanceId=$instance&PageId=si&session_id=$session"
enabled "$DEBUG" && echo -n "Getting OV data: " enabled "$DEBUG" && echo -n "Getting OV data: "
ov_data=$(curl --silent --max-time 30 --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$ov_url") || { ovData=$(curl --silent --max-time 30 --user-agent "$agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$ovUrl") || {
handle_curl_error "$?" "Microsoft" handleCurlError "$?" "Microsoft"
return $? return $?
} }
if [[ $ov_data =~ [\?\&]w=([A-Fa-f0-9]+) ]]; then if [[ $ovData =~ [\?\&]w=([A-Fa-f0-9]+) ]]; then
ovw="${BASH_REMATCH[1]}" ovToken="${BASH_REMATCH[1]}"
fi fi
if [[ $ov_data =~ rticks=\"\+?([0-9]+) ]]; then if [[ $ovData =~ rticks=\"\+?([0-9]+) ]]; then
rticks="${BASH_REMATCH[1]}" ovTicks="${BASH_REMATCH[1]}"
fi fi
if [[ -z $ovw || -z $rticks ]]; then if [[ -z $ovToken || -z $ovTicks ]]; then
error "Could not extract ov-df data from Microsoft server!" error "Could not extract ov-df data from Microsoft server!"
return 1 return 1
fi fi
enabled "$DEBUG" && echo "$ovw" enabled "$DEBUG" && echo "$ovToken"
sleep 0.2 sleep 0.2
# 2) Send a reply with session ID, current epoch and previously retrieved w and rticks # 2) Send a reply with session ID, current epoch and previously retrieved w and rticks
mdt=$(date +%s%3N) ovTime=$(date +%s%3N)
ov_url="https://ov-df.microsoft.com/?session_id=$session_id&CustomerId=$instance_id&PageId=si&w=$ovw&mdt=$mdt&rticks=$rticks" ovUrl="https://ov-df.microsoft.com/?session_id=$session&CustomerId=$instance&PageId=si&w=$ovToken&mdt=$ovTime&rticks=$ovTicks"
enabled "$DEBUG" && echo "Sending OV reply: $instance_id" enabled "$DEBUG" && echo "Sending OV reply: $instance"
curl --silent --max-time 30 --output /dev/null --user-agent "$user_agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$ov_url" || { curl --silent --max-time 30 --output /dev/null --user-agent "$agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$ovUrl" || {
# This should only happen if there's been some change to how this API works # This should only happen if there's been some change to how this API works
handle_curl_error "$?" "Microsoft" handleCurlError "$?" "Microsoft"
return $? return $?
} }
enabled "$DEBUG" && echo -n "Getting language SKU ID: " enabled "$DEBUG" && echo -n "Getting language SKU ID: "
sku_url="https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=$profile&ProductEditionId=$product_edition_id&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=$session_id" skuUrl="https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=$profile&ProductEditionId=$productId&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
language_skuid_json=$(curl --silent --max-time 30 --request GET --user-agent "$user_agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$sku_url") || { skuJson=$(curl --silent --max-time 30 --request GET --user-agent "$agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$skuUrl") || {
handle_curl_error "$?" "Microsoft" handleCurlError "$?" "Microsoft"
return $? return $?
} }
{ sku_id=$(echo "$language_skuid_json" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || : { skuId=$(echo "$skuJson" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :
if [ -z "$sku_id" ] || [[ "${sku_id,,}" == "null" ]] || (( rc != 0 )); then if [ -z "$skuId" ] || [[ "${skuId,,}" == "null" ]] || (( rc != 0 )); then
language=$(getLanguage "$lang" "desc") language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!" error "No download in the $language language available for $desc!"
return 1 return 1
fi fi
enabled "$DEBUG" && echo "$sku_id" enabled "$DEBUG" && echo "$skuId"
enabled "$DEBUG" && echo "Getting ISO download link..." enabled "$DEBUG" && echo "Getting ISO download link..."
# Get ISO download link # Get ISO download link
# If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed) # If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
iso_url="https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=$profile&ProductEditionId=undefined&SKU=$sku_id&friendlyFileName=undefined&Locale=en-US&sessionID=$session_id" linkUrl="https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=$profile&ProductEditionId=undefined&SKU=$skuId&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
iso_json=$(curl --silent --max-time 30 --request GET --user-agent "$user_agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_url") || { linkJson=$(curl --silent --max-time 30 --request GET --user-agent "$agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$linkUrl") || {
handle_curl_error "$?" "Microsoft" handleCurlError "$?" "Microsoft"
return $? return $?
} }
if ! [ "$iso_json" ]; then if ! [ "$linkJson" ]; then
# This should only happen if there's been some change to how this API works # This should only happen if there's been some change to how this API works
error "Microsoft servers gave us an empty response to our request for an automated download." error "Microsoft servers gave us an empty response to our request for an automated download."
return 1 return 1
fi fi
if echo "$iso_json" | grep -q "Sentinel marked this request as rejected."; then if echo "$linkJson" | grep -q "Sentinel marked this request as rejected."; then
error "Microsoft blocked the automated download request based on your IP address." error "Microsoft blocked the automated download request based on your IP address."
return 1 return 1
fi fi
if echo "$iso_json" | grep -q "We are unable to complete your request at this time."; then if echo "$linkJson" | grep -q "We are unable to complete your request at this time."; then
error "Microsoft blocked the automated download request based on your IP address." error "Microsoft blocked the automated download request based on your IP address."
return 1 return 1
fi fi
{ iso_download_link=$(echo "$iso_json" | jq --argjson TYPE "$download_type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || : { link=$(echo "$linkJson" | jq --argjson TYPE "$type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || :
if [ -z "$iso_download_link" ] || [[ "${iso_download_link,,}" == "null" ]] || (( rc != 0 )); then if [ -z "$link" ] || [[ "${link,,}" == "null" ]] || (( rc != 0 )); then
error "Microsoft server gave us no download link to our request for an automated download!" error "Microsoft server gave us no download link to our request for an automated download!"
info "Response: $iso_json" info "Response: $linkJson"
return 1 return 1
fi fi
MIDO_URL="$iso_download_link" MIDO_URL="$link"
return 0 return 0
} }
download_windows_eval() { downloadWindowsEval() {
local id="$1" local id="$1"
local lang="$2" local lang="$2"
local desc="$3" local desc="$3"
local filter="" culture="" compare="" language="" local filter="" culture="" compare="" language=""
local user_agent="" enterprise_type="" windows_version="" local agent="" type="" winVer=""
case "${id,,}" in case "${id,,}" in
"win11${PLATFORM,,}-enterprise-eval" ) "win11${PLATFORM,,}-enterprise-eval" )
enterprise_type="enterprise" type="enterprise"
windows_version="windows-11-enterprise" ;; winVer="windows-11-enterprise" ;;
"win11${PLATFORM,,}-enterprise-iot-eval" ) "win11${PLATFORM,,}-enterprise-iot-eval" )
enterprise_type="iot" type="iot"
windows_version="windows-11-iot-enterprise-ltsc-eval" ;; winVer="windows-11-iot-enterprise-ltsc-eval" ;;
"win11${PLATFORM,,}-enterprise-ltsc-eval" ) "win11${PLATFORM,,}-enterprise-ltsc-eval" )
enterprise_type="iot" type="iot"
windows_version="windows-11-iot-enterprise-ltsc-eval" ;; winVer="windows-11-iot-enterprise-ltsc-eval" ;;
"win2025-eval" ) "win2025-eval" )
enterprise_type="server" type="server"
windows_version="windows-server-2025" ;; winVer="windows-server-2025" ;;
"win2022-eval" ) "win2022-eval" )
enterprise_type="server" type="server"
windows_version="windows-server-2022" ;; winVer="windows-server-2022" ;;
"win2019-hv" ) "win2019-hv" )
enterprise_type="server" type="server"
windows_version="hyper-v-server-2019" ;; winVer="hyper-v-server-2019" ;;
"win2019-eval" ) "win2019-eval" )
enterprise_type="server" type="server"
windows_version="windows-server-2019" ;; winVer="windows-server-2019" ;;
"win2016-eval" ) "win2016-eval" )
enterprise_type="server" type="server"
windows_version="windows-server-2016" ;; winVer="windows-server-2016" ;;
"win2012r2-eval" ) "win2012r2-eval" )
enterprise_type="server" type="server"
windows_version="windows-server-2012-r2" ;; winVer="windows-server-2012-r2" ;;
* ) * )
error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;; error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
esac esac
user_agent=$(get_agent) agent=$(getAgent)
culture=$(getLanguage "$lang" "culture") culture=$(getLanguage "$lang" "culture")
local country="${culture#*-}" local country="${culture#*-}"
local iso_download_link="" local link=""
local iso_download_links="" local links=""
local iso_download_page_html="" local page=""
local url="https://www.microsoft.com/en-us/evalcenter/download-$windows_version" local url="https://www.microsoft.com/en-us/evalcenter/download-$winVer"
enabled "$DEBUG" && echo "Parsing download page: ${url}" enabled "$DEBUG" && echo "Parsing download page: ${url}"
iso_download_page_html=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || { page=$(curl --silent --max-time 30 --user-agent "$agent" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
handle_curl_error "$?" "Microsoft" handleCurlError "$?" "Microsoft"
return $? return $?
} }
if ! [ "$iso_download_page_html" ]; then if ! [ "$page" ]; then
# This should only happen if there's been some change to where this download page is located # This should only happen if there's been some change to where this download page is located
error "Windows server download page gave us an empty response" error "Windows server download page gave us an empty response"
return 1 return 1
@@ -293,11 +294,11 @@ download_windows_eval() {
filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}" filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
if ! echo "$iso_download_page_html" | grep -io "$filter" > /dev/null; then if ! echo "$page" | grep -io "$filter" > /dev/null; then
filter="https://go.microsoft.com/fwlink/p/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}" filter="https://go.microsoft.com/fwlink/p/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
fi fi
iso_download_links=$(echo "$iso_download_page_html" | grep -io "$filter") || { links=$(echo "$page" | grep -io "$filter") || {
# This should only happen if there's been some change to the download endpoint web address # This should only happen if there's been some change to the download endpoint web address
if [[ "${lang,,}" == "en" || "${lang,,}" == "en-"* ]]; then if [[ "${lang,,}" == "en" || "${lang,,}" == "en-"* ]]; then
error "Windows server download page gave us no download link!" error "Windows server download page gave us no download link!"
@@ -308,60 +309,60 @@ download_windows_eval() {
return 1 return 1
} }
case "$enterprise_type" in case "$type" in
"iot" | "ltsc" ) "iot" | "ltsc" )
case "${PLATFORM,,}" in case "${PLATFORM,,}" in
"x64" ) "x64" )
if [[ "$windows_version" != "windows-10"* ]]; then if [[ "$winVer" != "windows-10"* ]]; then
iso_download_link=$(echo "$iso_download_links" | head -n 1) link=$(echo "$links" | head -n 1)
else else
iso_download_link=$(echo "$iso_download_links" | head -n 4 | tail -n 1) link=$(echo "$links" | head -n 4 | tail -n 1)
fi ;; fi ;;
"arm64" ) "arm64" )
iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1) ;; link=$(echo "$links" | head -n 2 | tail -n 1) ;;
esac ;; esac ;;
"enterprise" ) "enterprise" )
case "${PLATFORM,,}" in case "${PLATFORM,,}" in
"x64" ) "x64" )
if [[ "$windows_version" != "windows-10"* ]]; then if [[ "$winVer" != "windows-10"* ]]; then
iso_download_link=$(echo "$iso_download_links" | head -n 1) link=$(echo "$links" | head -n 1)
else else
iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1) link=$(echo "$links" | head -n 2 | tail -n 1)
fi ;; fi ;;
"arm64" ) "arm64" )
iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1) ;; link=$(echo "$links" | head -n 2 | tail -n 1) ;;
esac ;; esac ;;
"server" ) "server" )
case "${PLATFORM,,}" in case "${PLATFORM,,}" in
"x64" ) "x64" )
iso_download_link=$(echo "$iso_download_links" | head -n 1) ;; link=$(echo "$links" | head -n 1) ;;
esac ;; esac ;;
* ) * )
error "Invalid type specified, value \"$enterprise_type\" is not recognized!" && return 1 ;; error "Invalid type specified, value \"$type\" is not recognized!" && return 1 ;;
esac esac
[ -z "$iso_download_link" ] && error "Could not parse download link from page!" && return 1 [ -z "$link" ] && error "Could not parse download link from page!" && return 1
# Follow redirect so proceeding log message is useful # Follow redirect so proceeding log message is useful
# This is a request we make that Fido doesn't # This is a request we make that Fido doesn't
iso_download_link=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_download_link") || { link=$(curl --silent --max-time 30 --user-agent "$agent" --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$link") || {
# This should only happen if the Microsoft servers are down # This should only happen if the Microsoft servers are down
handle_curl_error "$?" "Microsoft" handleCurlError "$?" "Microsoft"
return $? return $?
} }
case "${PLATFORM,,}" in case "${PLATFORM,,}" in
"x64" ) "x64" )
if [[ "${iso_download_link,,}" != *"x64"* ]]; then if [[ "${link,,}" != *"x64"* ]]; then
echo "Found download link: $iso_download_link" echo "Found download link: $link"
error "Download link is for the wrong platform? Please report this at $SUPPORT/issues" error "Download link is for the wrong platform? Please report this at $SUPPORT/issues"
return 1 return 1
fi ;; fi ;;
"arm64" ) "arm64" )
if [[ "${iso_download_link,,}" != *"a64"* && "${iso_download_link,,}" != *"arm64"* ]]; then if [[ "${link,,}" != *"a64"* && "${link,,}" != *"arm64"* ]]; then
if enabled "$DEBUG"; then if enabled "$DEBUG"; then
echo "Found download link: $iso_download_link" echo "Found download link: $link"
echo "Link for ARM platform currently not available!" echo "Link for ARM platform currently not available!"
fi fi
return 1 return 1
@@ -370,12 +371,12 @@ download_windows_eval() {
if enabled "$DEBUG" && enabled "$VERIFY" && [[ "${lang,,}" == "en"* ]]; then if enabled "$DEBUG" && enabled "$VERIFY" && [[ "${lang,,}" == "en"* ]]; then
compare=$(getMido "$id" "$lang" "") compare=$(getMido "$id" "$lang" "")
if [[ "${iso_download_link,,}" != "${compare,,}" ]]; then if [[ "${link,,}" != "${compare,,}" ]]; then
echo "Retrieved link does not match the fixed link: $compare" echo "Retrieved link does not match the fixed link: $compare"
fi fi
fi fi
MIDO_URL="$iso_download_link" MIDO_URL="$link"
return 0 return 0
} }
@@ -413,13 +414,13 @@ getWindows() {
case "${version,,}" in case "${version,,}" in
"win11${PLATFORM,,}" ) "win11${PLATFORM,,}" )
download_windows "$version" "$lang" "$edition" && return 0 downloadWindows "$version" "$lang" "$edition" && return 0
;; ;;
"win11${PLATFORM,,}-enterprise"* ) "win11${PLATFORM,,}-enterprise"* )
download_windows_eval "$version" "$lang" "$edition" && return 0 downloadWindowsEval "$version" "$lang" "$edition" && return 0
;; ;;
"win2025-eval" | "win2022-eval" | "win2019-eval" | "win2019-hv" | "win2016-eval" | "win2012r2-eval" ) "win2025-eval" | "win2022-eval" | "win2019-eval" | "win2019-hv" | "win2016-eval" | "win2012r2-eval" )
download_windows_eval "$version" "$lang" "$edition" && return 0 downloadWindowsEval "$version" "$lang" "$edition" && return 0
;; ;;
"win2008r2" | "win81${PLATFORM,,}"* | "win10${PLATFORM,,}-enterprise"* ) "win2008r2" | "win81${PLATFORM,,}"* | "win10${PLATFORM,,}-enterprise"* )
;; ;;
@@ -506,6 +507,16 @@ getCatalog() {
return 0 return 0
} }
getXmlTag() {
local tag="$1"
local file="$2"
xmllint --nonet --xpath "//$tag" "$file" 2>/dev/null | sed -E -e "s/<[\/]?$tag>//g" || true
return 0
}
getESD() { getESD() {
local dir="$1" local dir="$1"
@@ -515,6 +526,10 @@ getESD() {
local rc=0 local rc=0
local file result culture local file result culture
local language edition catalog local language edition catalog
local xmlFile="products.xml"
local esdFile="esd_edition.xml"
local filterFile="products_filter.xml"
local query
file=$(getCatalog "$version" "file") file=$(getCatalog "$version" "file")
catalog=$(getCatalog "$version" "url") catalog=$(getCatalog "$version" "url")
@@ -522,7 +537,8 @@ getESD() {
edition=$(getCatalog "$version" "edition") edition=$(getCatalog "$version" "edition")
if [ -z "$file" ] || [ -z "$catalog" ]; then if [ -z "$file" ] || [ -z "$catalog" ]; then
error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1 error "Invalid VERSION specified, value \"$version\" is not recognized!"
return 1
fi fi
local msg="Downloading catalog from the Microsoft servers..." local msg="Downloading catalog from the Microsoft servers..."
@@ -531,13 +547,10 @@ getESD() {
rm -rf "$dir" rm -rf "$dir"
if ! makeDir "$dir"; then if ! makeDir "$dir"; then
error "Failed to create directory \"$dir\" !" && return 1 error "Failed to create directory \"$dir\" !"
return 1
fi fi
local xFile="products.xml"
local eFile="esd_edition.xml"
local fFile="products_filter.xml"
{ wget "$catalog" -O "$dir/$file" -q --timeout=30 --no-http-keep-alive; rc=$?; } || : { wget "$catalog" -O "$dir/$file" -q --timeout=30 --no-http-keep-alive; rc=$?; } || :
msg="Failed to download $catalog" msg="Failed to download $catalog"
@@ -548,8 +561,8 @@ getESD() {
if [[ "$file" == *".xml" ]]; then if [[ "$file" == *".xml" ]]; then
if ! mv -f "$dir/$file" "$dir/$xFile"; then if ! mv -f "$dir/$file" "$dir/$xmlFile"; then
error "Failed to rename $file to $xFile." error "Failed to rename $file to $xmlFile."
return 1 return 1
fi fi
@@ -559,65 +572,68 @@ getESD() {
cd "$dir" || exit 1 cd "$dir" || exit 1
cabextract "$file" > /dev/null cabextract "$file" > /dev/null
); then ); then
error "Failed to extract $file!" && return 1 error "Failed to extract $file!"
return 1
fi fi
fi fi
if [ ! -s "$dir/$xFile" ]; then if [ ! -s "$dir/$xmlFile" ]; then
error "Failed to find $xFile in $file!" && return 1 error "Failed to find $xmlFile in $file!"
return 1
fi fi
local edQuery='//File[Architecture="'${PLATFORM,,}'"]'"${edition}"'' query='//File[Architecture="'${PLATFORM,,}'"]'"${edition}"''
result=$(xmllint --nonet --xpath "${edQuery}" "$dir/$xFile" 2>/dev/null || true) result=$(xmllint --nonet --xpath "${query}" "$dir/$xmlFile" 2>/dev/null || true)
if [ -z "$result" ]; then if [ -z "$result" ]; then
edQuery='//File[Architecture="'${PLATFORM^^}'"]'"${edition}"'' query='//File[Architecture="'${PLATFORM^^}'"]'"${edition}"''
result=$(xmllint --nonet --xpath "${query}" "$dir/$xmlFile" 2>/dev/null || true)
result=$(xmllint --nonet --xpath "${edQuery}" "$dir/$xFile" 2>/dev/null || true)
if [ -z "$result" ]; then if [ -z "$result" ]; then
desc=$(printEdition "$version" "$desc") desc=$(printEdition "$version" "$desc")
language=$(getLanguage "$lang" "desc") language=$(getLanguage "$lang" "desc")
error "No download link available for $desc!" && return 1 error "No download link available for $desc!"
return 1
fi fi
fi fi
echo -e '<Catalog>' > "$dir/$fFile" echo -e '<Catalog>' > "$dir/$filterFile"
echo "$result" >> "$dir/$fFile" echo "$result" >> "$dir/$filterFile"
echo -e '</Catalog>'>> "$dir/$fFile" echo -e '</Catalog>'>> "$dir/$filterFile"
result=$(xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$fFile" 2>/dev/null || true) result=$(xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$filterFile" 2>/dev/null || true)
if [ -z "$result" ]; then if [ -z "$result" ]; then
desc=$(printEdition "$version" "$desc") desc=$(printEdition "$version" "$desc")
language=$(getLanguage "$lang" "desc") language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!" && return 1 error "No download in the $language language available for $desc!"
return 1
fi fi
echo "$result" > "$dir/$eFile" echo "$result" > "$dir/$esdFile"
tag="FilePath" ESD=$(getXmlTag "FilePath" "$dir/$esdFile")
ESD=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" 2>/dev/null | sed -E -e "s/<[\/]?$tag>//g" || true)
if [ -z "$ESD" ]; then if [ -z "$ESD" ]; then
error "Failed to find ESD URL in $eFile!" && return 1 error "Failed to find ESD URL in $esdFile!"
return 1
fi fi
tag="Sha1" ESD_SUM=$(getXmlTag "Sha1" "$dir/$esdFile")
ESD_SUM=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" 2>/dev/null | sed -E -e "s/<[\/]?$tag>//g" || true)
if [ -z "$ESD_SUM" ]; then if [ -z "$ESD_SUM" ]; then
error "Failed to find ESD checksum in $eFile!" && return 1 error "Failed to find ESD checksum in $esdFile!"
return 1
fi fi
tag="Size" ESD_SIZE=$(getXmlTag "Size" "$dir/$esdFile")
ESD_SIZE=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" 2>/dev/null | sed -E -e "s/<[\/]?$tag>//g" || true)
if [ -z "$ESD_SIZE" ]; then if [ -z "$ESD_SIZE" ]; then
error "Failed to find ESD filesize in $eFile!" && return 1 error "Failed to find ESD filesize in $esdFile!"
return 1
fi fi
rm -rf "$dir" rm -rf "$dir"
@@ -690,7 +706,7 @@ downloadFile() {
local msg="Downloading $desc" local msg="Downloading $desc"
local rc total total_gb progress domain dots agent space folder local rc total total_gb progress domain dots agent space folder
agent=$(get_agent) agent=$(getAgent)
if [ -n "$size" ] && [[ "$size" != "0" ]]; then if [ -n "$size" ] && [[ "$size" != "0" ]]; then
@@ -740,14 +756,16 @@ downloadFile() {
total_gb=$(formatBytes "$total") total_gb=$(formatBytes "$total")
if [ "$total" -lt 100000000 ]; then if [ "$total" -lt 100000000 ]; then
error "Invalid download link: $url (is only $total_gb ?). Please report this at $SUPPORT/issues" && return 1 error "Invalid download link: $url (is only $total_gb ?). Please report this at $SUPPORT/issues"
return 1
fi fi
verifyFile "$iso" "$size" "$total" "$sum" || return 1 verifyFile "$iso" "$size" "$total" "$sum" || return 1
isCompressed "$url" && UNPACK="Y" isCompressed "$url" && UNPACK="Y"
html "Download finished successfully..." && return 0 html "Download finished successfully..."
return 0
fi fi
msg="Failed to download $url" msg="Failed to download $url"
@@ -802,7 +820,7 @@ downloadImage() {
local tried="n" local tried="n"
local success="n" local success="n"
local seconds="5" local seconds="5"
local url sum size base desc language local url sum size base desc language i
if [[ "${version,,}" == "http"* ]]; then if [[ "${version,,}" == "http"* ]]; then
@@ -814,7 +832,8 @@ downloadImage() {
fi fi
if ! validVersion "$version" "en"; then if ! validVersion "$version" "en"; then
error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1 error "Invalid VERSION specified, value \"$version\" is not recognized!"
return 1
fi fi
desc=$(printVersion "$version" "") desc=$(printVersion "$version" "")
@@ -823,7 +842,8 @@ downloadImage() {
language=$(getLanguage "$lang" "desc") language=$(getLanguage "$lang" "desc")
if ! validVersion "$version" "$lang"; then if ! validVersion "$version" "$lang"; then
desc=$(printEdition "$version" "$desc") desc=$(printEdition "$version" "$desc")
error "The $language language version of $desc is not available, please switch to English." && return 1 error "The $language language version of $desc is not available, please switch to English."
return 1
fi fi
desc+=" in $language" desc+=" in $language"
fi fi