From a009651ca43cb7b0787baca08aae1e552912da7a Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Sun, 13 Jan 2019 03:34:17 +0000 Subject: [PATCH] Hook up with Microsoft's web services --- DealBreaker.ps1 | 273 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 195 insertions(+), 78 deletions(-) diff --git a/DealBreaker.ps1 b/DealBreaker.ps1 index 5d4aee7..1333dfc 100644 --- a/DealBreaker.ps1 +++ b/DealBreaker.ps1 @@ -77,7 +77,7 @@ function Add-Title([string]$Name) $Title.HorizontalAlignment = "Left" $Title.VerticalAlignment = "Top" $Margin = $WindowsVersionTitle.Margin - $Margin.Top += $global:stage * $dh + $Margin.Top += $script:stage * $script:dh $Title.Margin = $Margin $Title.Text = $Name return $Title @@ -92,16 +92,16 @@ function Add-Combo $Combo.HorizontalAlignment = "Left" $Combo.VerticalAlignment = "Top" $Margin = $WindowsVersion.Margin - $Margin.Top += $global:stage * $dh + $Margin.Top += $script:stage * $script:dh $Combo.Margin = $Margin $Combo.SelectedIndex = 0 return $Combo } -# Form +# XAML Form +# TODO: Use relative extracted icon +# TODO: Add FlowDirection = "RightToLeft" to for RTL mode [xml]$Form = @" - - @@ -111,22 +111,27 @@ function Add-Combo "@ -$XMLReader = (New-Object System.Xml.XmlNodeReader $Form) +# Globals +$dh = 58; +$Stage = 0 +$SessionId = "" + +$XMLReader = New-Object System.Xml.XmlNodeReader $Form $XMLForm = [Windows.Markup.XamlReader]::Load($XMLReader) $XMLGrid = $XMLForm.FindName("Grid") $Confirm = $XMLForm.FindName('Confirm') -# Fill in the first dropdown +# Populate in the Windows Version dropdown $WindowsVersionTitle = $XMLForm.FindName('WindowsVersionTitle') $WindowsVersion = $XMLForm.FindName('WindowsVersion') +$array = @() +$i = 0 foreach($Version in $WindowsVersions) { - $null = $WindowsVersion.Items.Add($Version[0]) + $array += @(New-Object PsObject -Property @{ Version = $Version[0]; Index = $i }) + $i++ } -$VersionIndex = 0 -$ReleaseIndex = 0 -$Sku = 0 -$Stage = 0 -$dh = 58; +$WindowsVersion.ItemsSource = $array +$WindowsVersion.DisplayMemberPath = 'Version' # Button Action $Confirm.add_click({ @@ -135,6 +140,170 @@ $Confirm.add_click({ } $script:Stage++ + switch ($Stage) { + + 1 { # Windows Version selection => Check server connection and populate Windows Release + $WindowsVersion.IsEnabled = $False; + $Confirm.IsEnabled = $False + # Force a refresh of the Confirm button so it is actually disabled + $Confirm.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler] { $Confirm.UpdateLayout() }, $null, $null) + + try { + $r = Invoke-WebRequest -SessionVariable 'Session' "https://www.microsoft.com/en-us/software-download/windows10ISO/" + $script:SessionId = $r.ParsedHtml.IHTMLDocument3_GetElementById("session-id").Value + if (-not $SessionId) { + throw "Could not read Session ID" + } + Write-Host "Session ID: $SessionId" + } + catch { + Write-Host $_.Exception.Message + $UserInput = [System.Windows.MessageBox]::Show("Error: " + $_.Exception.Message, "Error", "OK", "Error") + # TODO: Don't use exit but set a global and close the dialog gracefully + exit 1 + } + $script:WindowsReleaseTitle = Add-Title($WindowsVersion.SelectedValue.Version + " Release") + $script:WindowsRelease = Add-Combo + + $i = 0 + $array = @() + foreach ($Version in $WindowsVersions[$WindowsVersion.SelectedValue.Index]) { + if ($Version -is [array]) { + $array += @(New-Object PsObject -Property @{ Release = $Version[0]; Index = $i }) + } + $i++ + } + $WindowsRelease.ItemsSource = $array + $WindowsRelease.DisplayMemberPath = "Release" + + $XMLGrid.AddChild($WindowsReleaseTitle) + $XMLGrid.AddChild($WindowsRelease) + $Confirm.IsEnabled = $True + } + + 2 { # Windows Release selection => Populate Product Edition + $WindowsRelease.IsEnabled = $False + $ProductEditionTitle = Add-Title("Edition") + $script:ProductEdition = Add-Combo + + $array = @() + foreach ($Release in $WindowsVersions[$WindowsVersion.SelectedValue.Index][$WindowsRelease.SelectedValue.Index]) + { + if ($Release -is [array]) { + $array += @(New-Object PsObject -Property @{ Edition = $Release[0]; Id = $Release[1] }) + } + } + $ProductEdition.ItemsSource = $array + $ProductEdition.DisplayMemberPath = "Edition" + + $XMLGrid.AddChild($ProductEditionTitle) + $XMLGrid.AddChild($ProductEdition) + } + + 3 { # Product Edition selection => Request and populate Languages + $ProductEdition.IsEnabled = $False + $Confirm.IsEnabled = $False + $Confirm.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler] { $Confirm.UpdateLayout() }, $null, $null) + $LanguageTitle = Add-Title("Language") + $script:Language = Add-Combo + # Get the Product Edition + + $url = "https://www.microsoft.com/en-us/api/controls/contentinclude/html" + $url += "?pageId=a8f8f489-4c7f-463a-9ca6-5cff94d8d041" + $url += "&host=www.microsoft.com" + $url += "&segments=software-download,windows10ISO" + $url += "&query=&action=GetSkuInformationByProductEdition" + $url += "&sessionId=" + $SessionId + $url += "&productEditionId=" + $ProductEdition.SelectedValue.Id + $url += "&sdVersion=2" + + try { + $r = Invoke-WebRequest -WebSession $Session $url + $array = @() + foreach ($var in $r.ParsedHtml.IHTMLDocument3_GetElementByID("product-languages")) { + $json = $var.value | ConvertFrom-Json; + if ($json) { + $array += @(New-Object PsObject -Property @{ Language = $json.language; Id = $json.id }) + } + } + if ($array.Length -eq 0) { + throw "Could not parse languages" + } + } + catch { + Write-Host $_.Exception.Message + $UserInput = [System.Windows.MessageBox]::Show("Error: " + $_.Exception.Message, "Error", "OK", "Error") + exit 3 + } + $Language.ItemsSource = $array + $Language.DisplayMemberPath = "Language" + # TODO: Select language that matches current MUI settings + $XMLGrid.AddChild($LanguageTitle) + $XMLGrid.AddChild($Language) + $Confirm.IsEnabled = $True + } + + 4 { # Language selection => Request and populate Arch download links + $Language.IsEnabled = $False + $Confirm.IsEnabled = $False + $Confirm.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler] { $Confirm.UpdateLayout() }, $null, $null) + $ArchTitle = Add-Title("Architecture") + $script:Arch = Add-Combo + + $url = "https://www.microsoft.com/en-us/api/controls/contentinclude/html" + $url += "?pageId=cfa9e580-a81e-4a4b-a846-7b21bf4e2e5b" + $url += "&host=www.microsoft.com" + $url += "&segments=software-download,windows10ISO" + $url += "&query=&action=GetProductDownloadLinksBySku" + $url += "&sessionId=" + $SessionId + $url += "&skuId=" + $Language.SelectedValue.Id + $url += "&language=" + $Language.SelectedValue.Language + $url += "&sdVersion=2" + + try { + $r = Invoke-WebRequest -WebSession $Session $url + $array = @() + foreach ($var in $r.ParsedHtml.IHTMLDocument3_GetElementsByTagName("span") | Where-Object { $_.className -eq "product-download-type" }) { + $Link = $var.ParentNode | Select -Expand href + $Type = $var.innerText + # Maybe Microsoft will provide ARM/ARM64 download links one day... + if ($Type -like "*arm64*") { + $Type = "Arm64" + } elseif ($Type -like "*arm*") { + $Type = "Arm" + } elseif ($Type -like "*x64*") { + $Type = "x64" + } elseif ($Type -like "*x86*") { + $Type = "x86" + } + $array += @(New-Object PsObject -Property @{ Type = $Type; Link = $Link }) + } + if ($array.Length -eq 0) { + throw "Could not fetch download links" + Write-Host $r.ParsedHtml.body.innerText + } + } + catch { + Write-Host $_.Exception.Message + $UserInput = [System.Windows.MessageBox]::Show("Error: " + $_.Exception.Message, "Error", "OK", "Error") + exit 4 + } + # TODO: Select Arch that matches current host + $Arch.ItemsSource = $array + $Arch.DisplayMemberPath = "Type" + $XMLGrid.AddChild($ArchTitle) + $XMLGrid.AddChild($Arch) + $Confirm.Content = "Download" + $Confirm.IsEnabled = $True + } + + 5 { # Arch selection => Return selected download link + $Arch.IsEnabled = $False + $Confirm.IsEnabled = $False + Write-Host $Arch.SelectedValue.Link + } + } + if ($Stage -lt 5) { $XMLForm.Height += $dh; $Margin = $Confirm.Margin @@ -142,71 +311,19 @@ $Confirm.add_click({ $Confirm.Margin = $Margin } - switch ($Stage) { - 1 { - $WindowsReleaseTitle = Add-Title($WindowsVersion.SelectedItem + " Release") - $script:WindowsRelease = Add-Combo - for ($i = 0; $i -lt $WindowsVersions.Length; $i++) { - if ($WindowsVersions[$i][0] -eq $WindowsVersion.SelectedItem) { - $script:VersionIndex = $i - } - } - for($i = 1; $i -lt $WindowsVersions[$VersionIndex].Length; $i++) { - $WindowsRelease.Items.Add($WindowsVersions[$VersionIndex][$i][0]) - } - $WindowsVersion.IsEnabled = $False; - $XMLGrid.AddChild($WindowsReleaseTitle) - $XMLGrid.AddChild($WindowsRelease) - } - 2 { - $ProductEditionTitle = Add-Title("Edition") - $script:ProductEdition = Add-Combo - for ($i = 1; $i -lt $WindowsVersions[$VersionIndex].Length; $i++) { - if ($WindowsVersions[$VersionIndex][$i][0] -eq $WindowsRelease.SelectedItem) { - $script:ReleaseIndex = $i - } - } - for($i = 1; $i -lt $WindowsVersions[$VersionIndex][$ReleaseIndex].Length; $i++) { - # Yeah, none of the examples to associate a value to a ComboBox entry in PS *actually* work - $ProductEdition.Items.Add($WindowsVersions[$VersionIndex][$ReleaseIndex][$i][0]) - } - $WindowsRelease.IsEnabled = $False - $XMLGrid.AddChild($ProductEditionTitle) - $XMLGrid.AddChild($ProductEdition) - } - 3 { - $LanguageTitle = Add-Title("Language") - $script:Language = Add-Combo - # At last, we can get the SKU - for ($i = 1; $i -lt $WindowsVersions[$VersionIndex][$ReleaseIndex].Length; $i++) { - if ($WindowsVersions[$VersionIndex][$ReleaseIndex][$i][0] -eq $ProductEdition.SelectedItem) { - $script:Sku = $WindowsVersions[$VersionIndex][$ReleaseIndex][$i][1] - Write-Host "SKU: $Sku" - } - } - $Language.Items.Add("English") - $Language.Items.Add("German") - $Language.Items.Add("French") - $ProductEdition.IsEnabled = $False - $XMLGrid.AddChild($LanguageTitle) - $XMLGrid.AddChild($Language) - } - 4 { - $ArchTitle = Add-Title("Architecture") - $script:Arch = Add-Combo - $Arch.Items.add("x64") - $Arch.Items.add("x86") - $Language.IsEnabled = $False - $XMLGrid.AddChild($ArchTitle) - $XMLGrid.AddChild($Arch) - $Confirm.Content = "Download" - } - 5 { - $Arch.IsEnabled = $False - $Confirm.IsEnabled = $False - } - } }) -# Show XMLform +# We need a job in the background to close the obnoxious Windows "Do you want to accept this cookie" alerts +$CloseStuff = { + while ($True) { + # TODO: We need to get this string from urlmon.dll.mui + Get-Process | Where-Object { $_.MainWindowTitle -match "Windows Security Warning" } | ForEach-Object { $_.CloseMainWindow() } + Start-Sleep -Milliseconds 200 + } +} +$job = Start-Job -ScriptBlock $CloseStuff + +# Display the dialog $null = $XMLForm.ShowDialog() + +Stop-Job -Job $job