# # Fido - Full Windows ISO Downloader # Copyright © 2019 Pete Batard # ConvertTo-ImageSource: Copyright © 2016 Chris Carter # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # NB: You must have a BOM on your .ps1 if you want Powershell to actually # realise it should use Unicode for the UI controls and not ISO-8859-1 # TODO: # - Add a -NoHide param # - Add translations # - Sort Windows 7 downloads # - Display all arch links? # - Icon does not display in taskbar when shell window is reduced # - Validate that download links are from Microsoft servers # - Add Expert mode for Home China and suff? # Parameters param( # (Optional) Name of a pipe the download URL should be sent to. # If not provided, a browser window is opened instead. [string]$PipeName, # (Optional) Name of the perferred locale to use for the UI (e.g. "en-US", "fr-FR") # If not provided, the current Windows UI locale is used. [string]$Locale = [System.Globalization.CultureInfo]::CurrentUICulture.Name, # (Optional) Path to the file that should be used for the UI icon. [string]$Icon, # (Optional) The title to display on the application window [string]$AppTitle = "Fido - Windows Retail ISO Downloader" ) $Debug = $False $Testing = $False if ($Testing) { $Locale = "fr-CA" } $TestLangs = '{"languages":[ { "language":"English", "text":"Anglais", "id":"100" }, { "language":"English (International)", "text":"Anglais (International)", "id":"101" }, { "language":"French", "text":"Français", "id":"102" }, { "language":"French (Canadian)", "text":"Français (Canadien)", "id":"103" } ]}' Write-Host Please Wait... # Custom Assembly Types $code = @" [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] internal static extern IntPtr LoadLibrary(string lpLibFileName); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax); [DllImport("Shell32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] internal static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons); [DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state); // Returns a localized MUI string from the specified DLL public static string GetMuiString(string dll, uint index) { int MAX_PATH = 255; string muiPath = Environment.SystemDirectory + @"\" + CultureInfo.CurrentUICulture.Name + @"\" + dll + ".mui"; if (!File.Exists(muiPath)) muiPath = Environment.SystemDirectory + @"\en-US\" + dll + ".mui"; IntPtr hMui = LoadLibrary(muiPath); if (hMui == null) return ""; StringBuilder szString = new StringBuilder(MAX_PATH); LoadString(hMui, (uint)index, szString, MAX_PATH); return szString.ToString(); } // Extract an icon from a DLL public static Icon ExtractIcon(string file, int number, bool largeIcon) { IntPtr large, small; ExtractIconEx(file, number, out large, out small, 1); try { return Icon.FromHandle(largeIcon ? large : small); } catch { return null; } } "@ Add-Type -MemberDefinition $code -Namespace Gui -UsingNamespace "System.IO", "System.Text", "System.Drawing", "System.Globalization" -ReferencedAssemblies System.Drawing -Name Utils -ErrorAction Stop Add-Type -AssemblyName PresentationFramework # Hide the powershell window: https://stackoverflow.com/a/27992426/1069307 $null = [Gui.Utils]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle, 0) # Data # TODO: Fetch this as JSON data? $WindowsVersions = @( @( "Windows 10", @( "1809 R2 (Build 17763.107 - 2018.10)", @("Windows 10 Home/Pro", 1060), @("Windows 10 Education", 1056), @("Windows 10 Home China ", 1061) ), @( "1809 R1 (Build 17763.1 - 2018.09)", @("Windows 10 Home/Pro", 1019), @("Windows 10 Education", 1021), @("Windows 10 Home China ", 1020) ), @( "1803 (Build 17134.1 - 2018.04)", @("Windows 10 Home/Pro", 651), @("Windows 10 Education", 655), @("Windows 10 Enterprise Eval", 629) @("Windows 10 COEM 1803 Home China", 640), @("Windows 10 COEM 1803", 639), @("Windows 10 1803 Home China", 638), @("Windows 10 1803", 637), @("Windows 10 COEM 1803_1 Home China", 654), @("Windows 10 COEM 1803_1", 653), @("Windows 10 1803_1 Home China", 652) ), @( "1709 (Build 16299.15 - 2017.09)", @("Windows 10 Education 1709", 488), @("Windows 10 COEM 1709 Home China", 487), @("Windows 10 COEM 1709", 486), @("Windows 10 1709 Home China", 485), @("Windows 10 1709", 484) ), @( "1703 (Build 15063.0 - 2017.03)", @("Windows 10 1703 Education N", 424), @("Windows 10 1703 Education", 423), @("Windows 10 COEM 1703 Home China", 372), @("Windows 10 COEM 1703 Single Language", 371), @("Windows 10 COEM 1703 N", 370), @("Windows 10 COEM 1703", 369), @("Windows 10 1703 Home China (Redstone 2)", 364), @("Windows 10 1703 Single Language (Redstone 2)", 363), @("Windows 10 1703 N (Redstone 2)", 362), @("Windows 10 1703 (Redstone 2)", 361) ), @( "1607 (Build 14393.0 - 2017.07)", @("Windows 10 China Get Genuine (Redstone 1)", 247), @("Windows 10 Single Language (Redstone 1)", 246), @("Windows 10 N (Redstone 1)", 245), @("Windows 10 (Redstone 1)", 244), @("Windows 10 Education N (Redstone 1)", 243), @("Windows 10 Education (Redstone 1)", 242) ), @( "1511 R3 (Build 10586.164 - 2016.04)", @("Windows 10 China Get Genuine (Threshold 2, April 2016 Update)", 185), @("Windows 10 Single Language (Threshold 2, April 2016 Update)", 184), @("Windows 10 N (Threshold 2, April 2016 Update)", 183), @("Windows 10 KN (Threshold 2, April 2016 Update)", 182), @("Windows 10 Education N (Threshold 2, April 2016 Update)", 181), @("Windows 10 Education KN (Threshold 2, April 2016 Update)", 180), @("Windows 10 Education (Threshold 2, April 2016 Update)", 179), @("Windows 10 (Threshold 2, April 2016 Update)", 178) ), @( "1511 R2 (Build 10586.104 - 2016.02)", @("Windows 10 Single Language (Threshold 2, February 2016 Update)", 116), @("Windows 10 N (Threshold 2, February 2016 Update)", 115), @("Windows 10 KN (Threshold 2, February 2016 Update)", 114), @("Windows 10 China Get Genuine (Threshold 2, February 2016 Update)", 113), @("Windows 10 Education N (Threshold 2, February 2016 Update)", 112), @("Windows 10 Education KN (Threshold 2, February 2016 Update)", 111), @("Windows 10 Education (Threshold 2, February 2016 Update)", 110), @("Windows 10 (Threshold 2, February 2016 Update)", 109) ), @( "1511 R1 (Build 10586.0 - 2015.11)", @("Windows 10 Single Language (Threshold 2)", 106), @("Windows 10 N (Threshold 2)", 105), @("Windows 10 KN (Threshold 2)", 104), @("Windows 10 China Get Genuine (Threshold 2)", 103), @("Windows 10 Education N (Threshold 2)", 102), @("Windows 10 Education KN (Threshold 2)", 101), @("Windows 10 Education (Threshold 2)", 100), @("Windows 10 (Threshold 2)", 99) ), @( "1507 (Build 10240.16384 - 2015.07)", @("Windows 10 Single Language (Threshold 1)", 82), @("Windows 10 N (Threshold 1)", 81), @("Windows 10 KN (Threshold 1)", 80), @("Windows 10 (Threshold 1)", 79), @("Windows 10 China Get Genuine (Threshold 1)", 78), @("Windows 10 Education N (Threshold 1)", 77), @("Windows 10 Education KN (Threshold 1)", 76), @("Windows 10 Education (Threshold 1)", 75) ) ), @( "Windows 8.1", @( "Update 3 (build 9600)", @("Windows 8.1/Windows 8.1 Pro", 52), @("Windows 8.1/Windows 8.1 Pro N", 55) @("Windows 8.1 Single Language", 48), @("Windows 8.1 Professional LE N", 71), @("Windows 8.1 Professional LE KN", 70), @("Windows 8.1 Professional LE K", 69), @("Windows 8.1 Professional LE", 68), @("Windows 8.1 KN", 62), @("Windows 8.1 K", 61) ) ), @( "Windows 7", @( "Windows 7 with SP1 (build 7601)", @("Windows 7 Ultimate", 8), @("Windows 7 Pro", 4), @("Windows 7 Home Premium", 6), @("Windows 7 Home Basic", 2), @("Windows 7 Professional KN SP1 COEM", 98), @("Windows 7 Home Premium KN SP1 COEM", 97), @("Windows 7 Ultimate SP1 COEM", 96), @("Windows 7 Ultimate N SP1 COEM", 95), @("Windows 7 Ultimate KN SP1 COEM", 94), @("Windows 7 Ultimate K SP1 COEM", 93), @("Windows 7 Starter SP1 COEM", 92), @("Windows 7 Professional SP1 COEM", 91), @("Windows 7 Professional N SP1 COEM", 90), @("Windows 7 Home Premium K SP1 COEM", 89), @("Windows 7 Home Premium SP1 COEM GGK", 88), @("Windows 7 Home Premium SP1 COEM", 87), @("Windows 7 Home Premium N SP1 COEM", 86), @("Windows 7 Home Basic SP1 COEM GGK", 85), @("Windows 7 Home Basic SP1 COEM", 83), @("Windows 7 Starter SP1", 28), @("Windows 7 Ultimate K SP1", 26), @("Windows 7 Ultimate KN SP1", 24), @("Windows 7 Home Premium KN SP1", 22), @("Windows 7 Home Premium K SP1", 20), @("Windows 7 Professional KN SP1", 18), @("Windows 7 Professional K SP1", 16), @("Windows 7 Ultimate N SP1", 14), @("Windows 7 Professional N SP1", 12), @("Windows 7 Home Premium N SP1", 10), @("Windows 7 Ultimate SP1", 8), @("Windows 7 Home Premium SP1", 6), @("Windows 7 Professional SP1", 4), @("Windows 7 Home Basic SP1", 2) ) ) ) # Translated messages. Empty string means same as English # TODO: Fetch this as JSON data? $Translations = @( @( "en-US" "Version" "Release" "Edition" "Language" "Arch" "Download" "Confirm" "Close" "Cancel" "Error" ), @( "fr-FR" "" "" "Édition" "Langue de produit" "" "Télécharger" "Confirmer" "Fermer" "Abandonner" "Erreur" ) ) # Functions function Select-Language([int]$ArrayIndex, [string]$LangName) { if (($Locale.StartsWith("ar") -and $LangName -like "*Arabic*") -or ` ($Locale -eq "pt-BR" -and $LangName -like "*Brazil*") -or ` ($Locale.StartsWith("ar") -and $LangName -like "*Bulgar*") -or ` ($Locale -eq "zh-CN" -and $LangName -like "*Chinese*" -and $LangName -like "*simp*") -or ` ($Locale -eq "zh-TW" -and $LangName -like "*Chinese*" -and $LangName -like "*trad*") -or ` ($Locale.StartsWith("hr") -and $LangName -like "*Croat*") -or ` ($Locale.StartsWith("cz") -and $LangName -like "*Czech*") -or ` ($Locale.StartsWith("da") -and $LangName -like "*Danish*") -or ` ($Locale.StartsWith("nl") -and $LangName -like "*Dutch*") -or ` ($Locale -eq "en-US" -and $LangName -eq "English") -or ` ($Locale.StartsWith("en") -and $LangName -like "*English*" -and $LangName -like "*inter*") -or ` ($Locale.StartsWith("et") -and $LangName -like "*Eston*") -or ` ($Locale.StartsWith("fi") -and $LangName -like "*Finn*") -or ` ($Locale -eq "fr-CA" -and $LangName -like "*French*" -and $LangName -like "*Canad*") -or ` ($Locale.StartsWith("fr") -and $LangName -eq "French") -or ` ($Locale.StartsWith("de") -and $LangName -like "*German*") -or ` ($Locale.StartsWith("el") -and $LangName -like "*Greek*") -or ` ($Locale.StartsWith("he") -and $LangName -like "*Hebrew*") -or ` ($Locale.StartsWith("hu") -and $LangName -like "*Hungar*") -or ` ($Locale.StartsWith("id") -and $LangName -like "*Indones*") -or ` ($Locale.StartsWith("it") -and $LangName -like "*Italia*") -or ` ($Locale.StartsWith("ja") -and $LangName -like "*Japan*") -or ` ($Locale.StartsWith("ko") -and $LangName -like "*Korea*") -or ` ($Locale.StartsWith("lv") -and $LangName -like "*Latvia*") -or ` ($Locale.StartsWith("lt") -and $LangName -like "*Lithuania*") -or ` ($Locale.StartsWith("ms") -and $LangName -like "*Malay*") -or ` ($Locale.StartsWith("nb") -and $LangName -like "*Norw*") -or ` ($Locale.StartsWith("fa") -and $LangName -like "*Persia*") -or ` ($Locale.StartsWith("pl") -and $LangName -like "*Polish*") -or ` ($Locale -eq "pt-PT" -and $LangName -eq "Portuguese") -or ` ($Locale.StartsWith("ro") -and $LangName -like "*Romania*") -or ` ($Locale.StartsWith("ru") -and $LangName -like "*Russia*") -or ` ($Locale.StartsWith("sr") -and $LangName -like "*Serbia*") -or ` ($Locale.StartsWith("sk") -and $LangName -like "*Slovak*") -or ` ($Locale.StartsWith("sl") -and $LangName -like "*Slovenia*") -or ` ($Locale -eq "es-ES" -and $LangName -eq "Spanish") -or ` ($Locale.StartsWith("es") -and $Locale -ne "es-ES" -and $LangName -like "*Spanish*") -or ` ($Locale.StartsWith("sv") -and $LangName -like "*Swed*") -or ` ($Locale.StartsWith("th") -and $LangName -like "*Thai*") -or ` ($Locale.StartsWith("tr") -and $LangName -like "*Turk*") -or ` ($Locale.StartsWith("uk") -and $LangName -like "*Ukrain*") -or ` ($Locale.StartsWith("vi") -and $LangName -like "*Vietnam*")) { return $ArrayIndex } return -1 } function Add-Title([string]$Name) { $Title = New-Object System.Windows.Controls.TextBlock $Title.FontSize = $WindowsVersionTitle.FontSize $Title.Height = $WindowsVersionTitle.Height; $Title.Width = $WindowsVersionTitle.Width; $Title.HorizontalAlignment = "Left" $Title.VerticalAlignment = "Top" $Margin = $WindowsVersionTitle.Margin $Margin.Top += $script:Stage * $script:dh $Title.Margin = $Margin $Title.Text = $Name return $Title } function Add-Combo { $Combo = New-Object System.Windows.Controls.ComboBox $Combo.FontSize = $WindowsVersion.FontSize $Combo.Height = $WindowsVersion.Height; $Combo.Width = $WindowsVersion.Width; $Combo.HorizontalAlignment = "Left" $Combo.VerticalAlignment = "Top" $Margin = $WindowsVersion.Margin $Margin.Top += $script:Stage * $script:dh $Combo.Margin = $Margin $Combo.SelectedIndex = 0 return $Combo } function Refresh-Control([object]$Control) { $Control.Dispatcher.Invoke("Render", [Windows.Input.InputEventHandler] { $Confirm.UpdateLayout() }, $null, $null) } function Send-Message([string]$PipeName, [string]$Message) { [System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8 $Pipe = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream -ArgumentList ".", $PipeName, ([System.IO.Pipes.PipeDirection]::Out), ([System.IO.Pipes.PipeOptions]::None), ([System.Security.Principal.TokenImpersonationLevel]::Impersonation) try { Write-Host Connecting to $PipeName $Pipe.Connect(1000) } catch { Write-Host $_.Exception.Message } $bRequest = $Encoding.GetBytes($Message) $cbRequest = $bRequest.Length; $Pipe.Write($bRequest, 0, $cbRequest); $Pipe.Dispose() } # From https://www.powershellgallery.com/packages/IconForGUI/1.5.2 # Copyright © 2016 Chris Carter. All rights reserved. # License: https://creativecommons.org/licenses/by-sa/4.0/ function ConvertTo-ImageSource { [CmdletBinding()] Param( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Drawing.Icon]$Icon ) Process { foreach ($i in $Icon) { [System.Windows.Interop.Imaging]::CreateBitmapSourceFromHIcon( $i.Handle, (New-Object System.Windows.Int32Rect -Args 0,0,$i.Width, $i.Height), [System.Windows.Media.Imaging.BitmapSizeOptions]::FromEmptyOptions() ) } } } # Translate a message string function Get-Translation([string]$Text) { if (-not $Translations[0].Contains($Text)) { Write-Host "Error: '$Text' is not a translatable string" return "(Untranslated)" } foreach($Translation in $Translations) { if ($Translation[0].StartsWith($ShortLocale)) { for ($i = 1; $i -lt $Translation.Length; $i++) { if ($Translations[0][$i] -eq $Text) { if ($Translation[$i]) { return $Translation[$i] } } } } } return $Text } function Error([string]$ErrorMessage) { Write-Host $ErrorMessage $XMLForm.Title = $(Get-Translation("Error")) + ": " + $ErrorMessage Refresh-Control($XMLForm) $Confirm.Content = Get-Translation("Close") Refresh-Control($Confirm) $UserInput = [System.Windows.MessageBox]::Show($XMLForm.Title, $(Get-Translation("Error")), "OK", "Error") $script:ExitCode = $Stage $script:Stage = -1 $Confirm.IsEnabled = $True } # XAML Form [xml]$Form = @"