Compare commits

..

2 Commits
v1.02 ... v1.04

Author SHA1 Message Date
Pete Batard
4cebdef31a Use translated messages for the PS 3.0 prompt
* Also use "Close" instead of "Cancel" for the initial 'Back' button
2019-03-12 13:58:06 +00:00
Pete Batard
bd9475773c Improve compatibility with vanilla Windows platforms
* Add a notice about PowerShell 3.0 being required.
* Enable script to run on platforms without Microsoft.mshtml assembly.
* Add a -DisableFirstRunCustomize option to prevent an exception when
  the script is run on a platform where IE has never been launched.
* Also display a notice about Microsoft anti bulk download
2019-03-11 17:24:22 +00:00

170
Fido.ps1
View File

@@ -1,5 +1,5 @@
# #
# Fido v1.02 - Retail Windows ISO Downloader # Fido v1.04 - Retail Windows ISO Downloader
# Copyright © 2019 Pete Batard <pete@akeo.ie> # Copyright © 2019 Pete Batard <pete@akeo.ie>
# ConvertTo-ImageSource: Copyright © 2016 Chris Carter # ConvertTo-ImageSource: Copyright © 2016 Chris Carter
# #
@@ -31,6 +31,10 @@ param(
# (Optional) Name of a pipe the download URL should be sent to. # (Optional) Name of a pipe the download URL should be sent to.
# If not provided, a browser window is opened instead. # If not provided, a browser window is opened instead.
[string]$PipeName, [string]$PipeName,
# (Optional) Disable IE First Run Customize so that Invoke-WebRequest
# doesn't throw an exception if the user has never launched IE.
# Note that this requires the script to run elevated.
[switch]$DisableFirstRunCustomize,
# (Optional) Toggle expert mode (additional ISOs to choose). # (Optional) Toggle expert mode (additional ISOs to choose).
[switch]$Expert = $False [switch]$Expert = $False
) )
@@ -366,6 +370,17 @@ function Get-Translation([string]$Text)
return $Text return $Text
} }
# Some PowerShells don't have Microsoft.mshtml assembly (comes with MS Office?)
# so we can't use ParsedHtml or IHTMLDocument[2|3] features there...
function GetElementById([object]$Request, [string]$Id)
{
try {
return $Request.ParsedHtml.IHTMLDocument3_GetElementByID($Id)
} catch {
return $Request.AllElements | ? {$_.id -eq $Id}
}
}
function Error([string]$ErrorMessage) function Error([string]$ErrorMessage)
{ {
Write-Host $ErrorMessage Write-Host $ErrorMessage
@@ -378,6 +393,17 @@ function Error([string]$ErrorMessage)
$script:Stage = -1 $script:Stage = -1
$Continue.IsEnabled = $True $Continue.IsEnabled = $True
} }
function Get-RandomDate()
{
[DateTime]$Min = "1/1/2008"
[DateTime]$Max = [DateTime]::Now
$RandomGen = new-object random
$RandomTicks = [Convert]::ToInt64( ($Max.ticks * 1.0 - $Min.Ticks * 1.0 ) * $RandomGen.NextDouble() + $Min.Ticks * 1.0 )
$Date = new-object DateTime($RandomTicks)
return $Date.ToString("yyyyMMdd")
}
#endregion #endregion
#region Form #region Form
@@ -401,14 +427,22 @@ $MaxStage = 4
$SessionId = "" $SessionId = ""
$ExitCode = -1 $ExitCode = -1
$Locale = "en-US" $Locale = "en-US"
$DFRCKey = "HKLM:\Software\Policies\Microsoft\Internet Explorer\Main\"
$DFRCName = "DisableFirstRunCustomize"
$DFRCAdded = $False
$RequestData = @{} $RequestData = @{}
$RequestData["GetLangs"] = @("a8f8f489-4c7f-463a-9ca6-5cff94d8d041", "GetSkuInformationByProductEdition" ) $RequestData["GetLangs"] = @("a8f8f489-4c7f-463a-9ca6-5cff94d8d041", "getskuinformationbyproductedition" )
$RequestData["GetLinks"] = @("cfa9e580-a81e-4a4b-a846-7b21bf4e2e5b", "GetProductDownloadLinksBySku" ) $RequestData["GetLinks"] = @("cfa9e580-a81e-4a4b-a846-7b21bf4e2e5b", "GetProductDownloadLinksBySku" )
# Create a semi-random Linux User-Agent string
$FirefoxVersion = Get-Random -Minimum 30 -Maximum 60
$FirefoxDate = Get-RandomDate
$UserAgent = "Mozilla/5.0 (X11; Linux i586; rv:$FirefoxVersion.0) Gecko/$FirefoxDate Firefox/$FirefoxVersion.0"
#endregion #endregion
# Localization # Localization
$EnglishMessages = "en-US|Version|Release|Edition|Language|Architecture|Download|Continue|Back|Close|Cancel|Error|Please wait...|Download using a browser" $EnglishMessages = "en-US|Version|Release|Edition|Language|Architecture|Download|Continue|Back|Close|Cancel|Error|Please wait...|" +
"Download using a browser|Temporarily banned by Microsoft for requesting too many downloads - Please try again later...|" +
"PowerShell 3.0 or later is required to run this script.|Do you want to go online and download it?"
[string[]]$English = $EnglishMessages.Split('|') [string[]]$English = $EnglishMessages.Split('|')
[string[]]$Localized = $null [string[]]$Localized = $null
if ($LocData -and (-not $LocData.StartsWith("en-US"))) { if ($LocData -and (-not $LocData.StartsWith("en-US"))) {
@@ -420,6 +454,30 @@ if ($LocData -and (-not $LocData.StartsWith("en-US"))) {
$Locale = $Localized[0] $Locale = $Localized[0]
} }
# Make sure PowerShell 3.0 or later is used (for Invoke-WebRequest)
if ($PSVersionTable.PSVersion.Major -lt 3) {
Write-Host Error: PowerShell 3.0 or later is required to run this script.
$Msg = "$(Get-Translation($English[15]))`n$(Get-Translation($English[16]))"
if ([System.Windows.MessageBox]::Show($Msg, $(Get-Translation("Error")), "YesNo", "Error") -eq "Yes") {
Start-Process -FilePath https://www.microsoft.com/download/details.aspx?id=34595
}
exit -1
}
# If asked, disable IE's first run customize prompt as it interferes with Invoke-WebRequest
if ($DisableFirstRunCustomize) {
try {
# Only create the key if it doesn't already exist
Get-ItemProperty -Path $DFRCKey -Name $DFRCName -ErrorActionPreference "Stop"
} catch {
if (-not (Test-Path $DFRCKey)) {
New-Item -Path $DFRCKey -Force | Out-Null
}
Set-ItemProperty -Path $DFRCKey -Name $DFRCName -Value 1
$DFRCAdded = $True
}
}
# Form creation # Form creation
$XMLForm = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $XAML)) $XMLForm = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $XAML))
$XAML.SelectNodes("//*[@Name]") | ForEach-Object { Set-Variable -Name ($_.Name) -Value $XMLForm.FindName($_.Name) -Scope Script } $XAML.SelectNodes("//*[@Name]") | ForEach-Object { Set-Variable -Name ($_.Name) -Value $XMLForm.FindName($_.Name) -Scope Script }
@@ -434,7 +492,7 @@ if ($Locale.StartsWith("ar") -or $Locale.StartsWith("fa") -or $Locale.StartsWit
} }
$WindowsVersionTitle.Text = Get-Translation("Version") $WindowsVersionTitle.Text = Get-Translation("Version")
$Continue.Content = Get-Translation("Continue") $Continue.Content = Get-Translation("Continue")
$Back.Content = Get-Translation("Cancel") $Back.Content = Get-Translation("Close")
# Populate the Windows versions # Populate the Windows versions
$i = 0 $i = 0
@@ -470,10 +528,12 @@ $Continue.add_click({
Write-Host Querying $url Write-Host Querying $url
try { try {
$r = Invoke-WebRequest -SessionVariable "Session" $url # Note: We can't use -UseBasicParsing since we need JS to create the session-id
$script:SessionId = $r.ParsedHtml.IHTMLDocument3_GetElementById("session-id").Value # TODO: Use -Headers @{"Cache-Control"="no-cache"}?
$r = Invoke-WebRequest -UserAgent $UserAgent -SessionVariable "Session" $url
$script:SessionId = $(GetElementById -Request $r -Id "session-id").Value
if (-not $SessionId) { if (-not $SessionId) {
$ErrorMessage = $r.ParsedHtml.IHTMLDocument3_GetElementByID("errorModalMessage").innerHtml $ErrorMessage = $(GetElementById -Request $r -Id "errorModalMessage").InnerText
if ($ErrorMessage) { if ($ErrorMessage) {
Write-Host "$(Get-Translation("Error")): ""$ErrorMessage""" Write-Host "$(Get-Translation("Error")): ""$ErrorMessage"""
} }
@@ -526,11 +586,18 @@ $Continue.add_click({
$i = 0 $i = 0
$SelectedIndex = 0 $SelectedIndex = 0
try { try {
$r = Invoke-WebRequest -WebSession $Session $url $r = Invoke-WebRequest -UserAgent $UserAgent -WebSession $Session $url
foreach ($var in $r.ParsedHtml.IHTMLDocument3_GetElementByID("product-languages")) { # Go through an XML conversion to keep all PowerShells happy...
if (-not $($r.AllElements | ? {$_.id -eq "product-languages"})) {
throw "Unexpected server response"
}
$html = $($r.AllElements | ? {$_.id -eq "product-languages"}).InnerHTML
$html = "<options>" + $html.Replace("selected value", "value") + "</options>"
$xml = [xml]$html
foreach ($var in $xml.options.option) {
$json = $var.value | ConvertFrom-Json; $json = $var.value | ConvertFrom-Json;
if ($json) { if ($json) {
$array += @(New-Object PsObject -Property @{ DisplayLanguage = $var.text; Language = $json.language; Id = $json.id }) $array += @(New-Object PsObject -Property @{ DisplayLanguage = $var.InnerText; Language = $json.language; Id = $json.id })
if (Select-Language($json.language)) { if (Select-Language($json.language)) {
$SelectedIndex = $i $SelectedIndex = $i
} }
@@ -538,7 +605,7 @@ $Continue.add_click({
} }
} }
if ($array.Length -eq 0) { if ($array.Length -eq 0) {
$ErrorMessage = $r.ParsedHtml.IHTMLDocument3_GetElementByID("errorModalMessage").innerHtml $ErrorMessage = $(GetElementById -Request $r -Id "errorModalMessage").innerText
if ($ErrorMessage) { if ($ErrorMessage) {
Write-Host "$(Get-Translation("Error")): ""$ErrorMessage""" Write-Host "$(Get-Translation("Error")): ""$ErrorMessage"""
} }
@@ -568,37 +635,55 @@ $Continue.add_click({
$SelectedIndex = 0 $SelectedIndex = 0
$array = @() $array = @()
try { try {
$r = Invoke-WebRequest -WebSession $Session $url $r = Invoke-WebRequest -UserAgent $UserAgent -WebSession $Session $url
foreach ($var in $r.ParsedHtml.IHTMLDocument3_GetElementsByTagName("span") | Where-Object { $_.className -eq "product-download-type" }) { if (-not $($r.AllElements | ? {$_.id -eq "expiration-time"})) {
$Link = $var.ParentNode | Select -Expand href $ErrorMessage = $(GetElementById -Request $r -Id "errorModalMessage").innerText
$Type = $var.innerText if ($ErrorMessage) {
# Maybe Microsoft will provide public ARM/ARM64 retail ISOs one day... Write-Host "$(Get-Translation("Error")): ""$ErrorMessage"""
if ($Type -like "*arm64*") { }
$Type = "Arm64" throw Get-Translation($English[14])
if ($ENV:PROCESSOR_ARCHITECTURE -eq "ARM64") { }
$SelectedIndex = $i $html = $($r.AllElements | ? {$_.tagname -eq "input"}).outerHTML
} # Need to fix the HTML and JSON data so that it is well-formed
} elseif ($Type -like "*arm*") { $html = $html.Replace("class=product-download-hidden", "")
$Type = "Arm" $html = $html.Replace("type=hidden", "")
if ($ENV:PROCESSOR_ARCHITECTURE -eq "ARM") { $html = $html.Replace(">", "/>")
$SelectedIndex = $i $html = $html.Replace(": I", ": ""I")
} $html = $html.Replace(" }", """ }")
} elseif ($Type -like "*x64*") { $html = "<inputs>" + $html + "</inputs>"
$Type = "x64" $xml = [xml]$html
if ($ENV:PROCESSOR_ARCHITECTURE -eq "AMD64") { foreach ($var in $xml.inputs.input) {
$SelectedIndex = $i $json = $var.value | ConvertFrom-Json;
} if ($json) {
} elseif ($Type -like "*x86*") { $Type = $json.DownloadType
$Type = "x86" # Maybe Microsoft will provide public ARM/ARM64 retail ISOs one day...
if ($ENV:PROCESSOR_ARCHITECTURE -eq "X86") { if ($Type -like "*arm64*") {
$SelectedIndex = $i $Type = "Arm64"
} if ($ENV:PROCESSOR_ARCHITECTURE -eq "ARM64") {
$SelectedIndex = $i
}
} elseif ($Type -like "*arm*") {
$Type = "Arm"
if ($ENV:PROCESSOR_ARCHITECTURE -eq "ARM") {
$SelectedIndex = $i
}
} elseif ($Type -like "*x64*") {
$Type = "x64"
if ($ENV:PROCESSOR_ARCHITECTURE -eq "AMD64") {
$SelectedIndex = $i
}
} elseif ($Type -like "*x86*") {
$Type = "x86"
if ($ENV:PROCESSOR_ARCHITECTURE -eq "X86") {
$SelectedIndex = $i
}
}
$array += @(New-Object PsObject -Property @{ Type = $Type; Link = $json.Uri })
$i++
} }
$array += @(New-Object PsObject -Property @{ Type = $Type; Link = $Link })
$i++
} }
if ($array.Length -eq 0) { if ($array.Length -eq 0) {
$ErrorMessage = $r.ParsedHtml.IHTMLDocument3_GetElementByID("errorModalMessage").innerHtml $ErrorMessage = $(GetElementById -Request $r -Id "errorModalMessage").innerText
if ($ErrorMessage) { if ($ErrorMessage) {
Write-Host "$(Get-Translation("Error")): ""$ErrorMessage""" Write-Host "$(Get-Translation("Error")): ""$ErrorMessage"""
} }
@@ -667,7 +752,7 @@ $Back.add_click({
$Back.Margin = $Margin $Back.Margin = $Margin
$script:Stage = $Stage - 1 $script:Stage = $Stage - 1
if ($Stage -eq 0) { if ($Stage -eq 0) {
$Back.Content = Get-Translation("Cancel") $Back.Content = Get-Translation("Close")
} elseif ($Stage -eq 3) { } elseif ($Stage -eq 3) {
$Continue.Content = Get-Translation("Continue") $Continue.Content = Get-Translation("Continue")
} }
@@ -699,4 +784,7 @@ $XMLForm.ShowDialog() | Out-Null
if (-not $PipeName) { if (-not $PipeName) {
Stop-Job -Job $Job Stop-Job -Job $Job
} }
if ($DFRCAdded) {
Remove-ItemProperty -Path $DFRCKey -Name $DFRCName
}
exit $ExitCode exit $ExitCode