Compare commits

...

3 Commits
v1.61 ... v1.64

Author SHA1 Message Date
Pete Batard
96c3637863 Add UEFI Shell 24H2 downloads
* Also add a new -PlatformArch option, to help avoid the very time consuming WMI call to autodetect the native CPU arch.
2024-11-24 22:26:40 +00:00
Pete Batard
c034a4e72a Fix localization of error message 715-123130
* As usual, Microsoft are completely unable to properly process UTF-8 content, even if
  the response from an Invoke-WebRequest query explicitly specifies "charset=utf-8".
* So, once again, we need to force PowerShell's hand to treat the content as UTF-8.
* Also improve the default error message if we can't get it from Microsoft.
2024-11-20 13:51:17 +00:00
Pete Batard
ce405fa35c Switch to using the new JSON Windows download API
* This simplifies things quite a bit since we were converting HTML to XML to JSON before.
* Also drop the custom User Agent for now.
  We'll have to see how long it takes Microsoft to go back to filtering out PowerShell...
* Closes #98.
2024-11-19 22:42:39 +00:00

289
Fido.ps1
View File

@@ -1,5 +1,5 @@
# #
# Fido v1.61 - Feature ISO Downloader, for retail Windows images and UEFI Shell # Fido v1.64 - ISO Downloader, for Microsoft Windows and UEFI Shell
# Copyright © 2019-2024 Pete Batard <pete@akeo.ie> # Copyright © 2019-2024 Pete Batard <pete@akeo.ie>
# Command line support: Copyright © 2021 flx5 # Command line support: Copyright © 2021 flx5
# ConvertTo-ImageSource: Copyright © 2016 Chris Carter # ConvertTo-ImageSource: Copyright © 2016 Chris Carter
@@ -24,7 +24,7 @@
#region Parameters #region Parameters
param( param(
# (Optional) The title to display on the application window. # (Optional) The title to display on the application window.
[string]$AppTitle = "Fido - Feature ISO Downloader", [string]$AppTitle = "Fido - ISO Downloader",
# (Optional) '|' separated UI localization strings. # (Optional) '|' separated UI localization strings.
[string]$LocData, [string]$LocData,
# (Optional) Forced locale # (Optional) Forced locale
@@ -46,6 +46,9 @@ param(
[string]$Arch, [string]$Arch,
# (Optional) Only display the download URL [Toggles commandline mode] # (Optional) Only display the download URL [Toggles commandline mode]
[switch]$GetUrl = $false, [switch]$GetUrl = $false,
# (Optional) Specify the architecture of the underlying CPU.
# This avoids a VERY TIME CONSUMING call to WMI to autodetect the arch.
[string]$PlatformArch,
# (Optional) Increase verbosity # (Optional) Increase verbosity
[switch]$Verbose = $false, [switch]$Verbose = $false,
# (Optional) Produce debugging information # (Optional) Produce debugging information
@@ -156,6 +159,11 @@ $WindowsVersions = @(
) )
@( @(
@("UEFI Shell 2.2", "UEFI_SHELL 2.2"), @("UEFI Shell 2.2", "UEFI_SHELL 2.2"),
@(
"24H2 (edk2-stable202411)",
@("Release", 0),
@("Debug", 1)
),
@( @(
"24H1 (edk2-stable202405)", "24H1 (edk2-stable202405)",
@("Release", 0), @("Release", 0),
@@ -346,17 +354,6 @@ function ConvertTo-ImageSource
} }
} }
function Throw-Error([object]$Req, [string]$Alt)
{
$Err = $(GetElementById -Request $Req -Id "errorModalMessage").innerText -replace "<[^>]+>" -replace "\s+", " "
if (!$Err) {
$Err = $Alt
} else {
$Err = [System.Text.Encoding]::UTF8.GetString([byte[]][char[]]$Err)
}
throw $Err
}
# Translate a message string # Translate a message string
function Get-Translation([string]$Text) function Get-Translation([string]$Text)
{ {
@@ -398,14 +395,14 @@ function Get-Arch
} }
} }
# Some PowerShells don't have Microsoft.mshtml assembly (comes with MS Office?) # Convert a Microsoft arch type code to a formal architecture name
# so we can't use ParsedHtml or IHTMLDocument[2|3] features there... function Get-Arch-From-Type([int]$Type)
function GetElementById([object]$Request, [string]$Id)
{ {
try { switch($Type) {
return $Request.ParsedHtml.IHTMLDocument3_GetElementByID($Id) 0 { return "x86" }
} catch { 1 { return "x64" }
return $Request.AllElements | ? {$_.id -eq $Id} 2 { return "ARM64" }
default { return "Unknown"}
} }
} }
@@ -422,17 +419,6 @@ function Error([string]$ErrorMessage)
$script:ExitCode = 2 $script:ExitCode = 2
} }
} }
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
@@ -466,15 +452,8 @@ $MaxStage = 4
$SessionId = @($null) * 2 $SessionId = @($null) * 2
$ExitCode = 100 $ExitCode = 100
$Locale = $Locale $Locale = $Locale
$RequestData = @{} $OrgId = "y6jn8c31"
# This GUID applies to all visitors, regardless of their locale $ProfileId = "606624d44113"
$RequestData["GetLangs"] = @("a8f8f489-4c7f-463a-9ca6-5cff94d8d041", "getskuinformationbyproductedition" )
# This GUID applies to visitors of the en-US download page. Other locales may get a different GUID.
$RequestData["GetLinks"] = @("6e2a1789-ef16-4f27-a296-74ef7ef5d96b", "GetProductDownloadLinksBySku" )
# Create a semi-random Linux User-Agent string
$FirefoxVersion = Get-Random -Minimum 110 -Maximum 135
$FirefoxDate = Get-RandomDate
$UserAgent = "Mozilla/5.0 (X11; Linux i586; rv:$FirefoxVersion.0) Gecko/$FirefoxDate Firefox/$FirefoxVersion.0"
$Verbosity = 1 $Verbosity = 1
if ($Debug) { if ($Debug) {
$Verbosity = 5 $Verbosity = 5
@@ -483,7 +462,9 @@ if ($Debug) {
} elseif ($Cmd -and $GetUrl) { } elseif ($Cmd -and $GetUrl) {
$Verbosity = 0 $Verbosity = 0
} }
$PlatformArch = Get-Arch if (!$PlatformArch) {
$PlatformArch = Get-Arch
}
#endregion #endregion
# Localization # Localization
@@ -527,13 +508,7 @@ function Check-Locale
if ($Verbosity -ge 2) { if ($Verbosity -ge 2) {
Write-Host Querying $url Write-Host Querying $url
} }
# Looks Microsoft are filtering our script according to the first query it performs with the spoofed user agent.
# So, to continue this pointless cat and mouse game, we simply add an extra first query with the default user agent.
# Also: "Hi Microsoft. You sure have A LOT OF RESOURCES TO WASTE to have assigned folks of yours to cripple scripts
# that merely exist because you have chosen to make the user experience from your download website utterly subpar.
# And while I am glad senpai noticed me (UwU), I feel compelled to ask: Don't you guys have better things to do?"
Invoke-WebRequest -UseBasicParsing -TimeoutSec $DefaultTimeout -MaximumRedirection 0 $url | Out-Null Invoke-WebRequest -UseBasicParsing -TimeoutSec $DefaultTimeout -MaximumRedirection 0 $url | Out-Null
Invoke-WebRequest -UseBasicParsing -TimeoutSec $DefaultTimeout -MaximumRedirection 0 -UserAgent $UserAgent $url | Out-Null
} catch { } catch {
# Of course PowerShell 7 had to BREAK $_.Exception.Status on timeouts... # Of course PowerShell 7 had to BREAK $_.Exception.Status on timeouts...
if ($_.Exception.Status -eq "Timeout" -or $_.Exception.GetType().Name -eq "TaskCanceledException") { if ($_.Exception.Status -eq "Timeout" -or $_.Exception.GetType().Name -eq "TaskCanceledException") {
@@ -543,6 +518,33 @@ function Check-Locale
} }
} }
function Get-Code-715-123130-Message
{
try {
$url = "https://www.microsoft.com/" + $QueryLocale + "/software-download/windows11"
if ($Verbosity -ge 2) {
Write-Host Querying $url
}
$r = Invoke-WebRequest -UseBasicParsing -TimeoutSec $DefaultTimeout -MaximumRedirection 0 $url
# Microsoft's handling of UTF-8 content is soooooooo *UTTERLY BROKEN*!!!
$r = [System.Text.Encoding]::UTF8.GetString($r.RawContentStream.ToArray())
# PowerShell 7 forces us to parse the HTML ourselves
$r = $r -replace "`n" -replace "`r"
$pattern = '.*<input id="msg-01" type="hidden" value="(.*?)"/>.*'
$msg = [regex]::Match($r, $pattern).Groups[1].Value
$msg = $msg -replace "&lt;", "<" -replace "<[^>]+>" -replace "\s+", " "
if (($msg -eq $null) -or !($msg -match "715-123130")) {
throw
}
} catch {
$msg = "Your IP address has been banned by Microsoft for issuing too many ISO download requests or for "
$msg += "belonging to a region of the world where sanctions currently apply. Please try again later.`r`n"
$msg += "If you believe this ban to be in error, you can try contacting Microsoft by referring to "
$msg += "message code 715-123130 and session ID "
}
return $msg
}
# Return an array of releases (e.g. 20H2, 21H1, ...) for the selected Windows version # Return an array of releases (e.g. 20H2, 21H1, ...) for the selected Windows version
function Get-Windows-Releases([int]$SelectedVersion) function Get-Windows-Releases([int]$SelectedVersion)
{ {
@@ -584,55 +586,49 @@ function Get-Windows-Languages([int]$SelectedVersion, [object]$SelectedEdition)
foreach ($EditionId in $SelectedEdition) { foreach ($EditionId in $SelectedEdition) {
$SessionId[$SessionIndex] = [guid]::NewGuid() $SessionId[$SessionIndex] = [guid]::NewGuid()
# Microsoft download protection now requires the sessionId to be whitelisted through vlscppe.microsoft.com/tags # Microsoft download protection now requires the sessionId to be whitelisted through vlscppe.microsoft.com/tags
$url = "https://vlscppe.microsoft.com/tags?org_id=y6jn8c31&session_id=" + $SessionId[$SessionIndex] $url = "https://vlscppe.microsoft.com/tags"
$url += "?org_id=" + $OrgId
$url += "&session_id=" + $SessionId[$SessionIndex]
if ($Verbosity -ge 2) { if ($Verbosity -ge 2) {
Write-Host Querying $url Write-Host Querying $url
} }
try { try {
Invoke-WebRequest -UseBasicParsing -TimeoutSec $DefaultTimeout -MaximumRedirection 0 -UserAgent $UserAgent $url | Out-Null Invoke-WebRequest -UseBasicParsing -TimeoutSec $DefaultTimeout -MaximumRedirection 0 $url | Out-Null
} catch { } catch {
Error($_.Exception.Message) Error($_.Exception.Message)
return @() return @()
} }
$url = "https://www.microsoft.com/" + $QueryLocale + "/api/controls/contentinclude/html" $url = "https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition"
$url += "?pageId=" + $RequestData["GetLangs"][0] $url += "?profile=" + $ProfileId
$url += "&host=www.microsoft.com"
$url += "&segments=software-download," + $WindowsVersions[$SelectedVersion][0][1]
$url += "&query=&action=" + $RequestData["GetLangs"][1]
$url += "&sessionId=" + $SessionId[$SessionIndex]
$url += "&productEditionId=" + $EditionId $url += "&productEditionId=" + $EditionId
$url += "&sdVersion=2" $url += "&SKU=undefined"
$url += "&friendlyFileName=undefined"
$url += "&Locale=" + $QueryLocale
$url += "&sessionID=" + $SessionId[$SessionIndex]
if ($Verbosity -ge 2) { if ($Verbosity -ge 2) {
Write-Host Querying $url Write-Host Querying $url
} }
try { try {
$r = Invoke-WebRequest -Method Post -UseBasicParsing -TimeoutSec $DefaultTimeout -UserAgent $UserAgent -SessionVariable "Session" $url $r = Invoke-RestMethod -UseBasicParsing -TimeoutSec $DefaultTimeout -SessionVariable "Session" $url
if ($r -eq $null) {
throw "Could not retrieve languages from server"
}
if ($Verbosity -ge 5) { if ($Verbosity -ge 5) {
Write-Host "==============================================================================" Write-Host "=============================================================================="
Write-Host $r Write-Host ($r | ConvertTo-Json)
Write-Host "==============================================================================" Write-Host "=============================================================================="
} }
if ($r -match "errorModalMessage") { if ($r.Errors) {
Throw-Error -Req $r -Alt "Could not retrieve languages from server" throw $r.Errors[0].Value
} }
$r = $r -replace "`n" -replace "`r" foreach ($Sku in $r.Skus) {
$pattern = '.*<select id="product-languages"[^>]*>(.*)</select>.*' if (!$languages.Contains($Sku.Language)) {
$html = [regex]::Match($r, $pattern).Groups[1].Value $languages[$Sku.Language] = @{ DisplayName = $Sku.LocalizedLanguage; Data = @() }
# Go through an XML conversion to keep all PowerShells happy...
$html = $html.Replace("selected value", "value")
$html = "<options>" + $html + "</options>"
$xml = [xml]$html
foreach ($var in $xml.options.option) {
$json = $var.value | ConvertFrom-Json;
if ($json) {
if (!$languages.Contains($json.language)) {
$languages[$json.language] = @{ DisplayName = $var.InnerText; Data = @() }
}
$languages[$json.language].Data += @{ SessionIndex = $SessionIndex; SkuId = $json.id }
} }
$languages[$Sku.Language].Data += @{ SessionIndex = $SessionIndex; SkuId = $Sku.Id }
} }
if ($languages.Length -eq 0) { if ($languages.Length -eq 0) {
Throw-Error -Req $r -Alt "Could not parse languages" throw "Could not parse languages"
} }
} catch { } catch {
Error($_.Exception.Message) Error($_.Exception.Message)
@@ -682,65 +678,48 @@ function Get-Windows-Download-Links([int]$SelectedVersion, [int]$SelectedRelease
$archs += $sep + $arch $archs += $sep + $arch
$sep = ", " $sep = ", "
} }
$links += @(New-Object PsObject -Property @{ Type = $archs; Url = $link }) $links += @(New-Object PsObject -Property @{ Arch = $archs; Url = $link })
} catch { } catch {
Error($_.Exception.Message) Error($_.Exception.Message)
return @() return @()
} }
} else { } else {
foreach ($Entry in $SelectedLanguage.Data) { foreach ($Entry in $SelectedLanguage.Data) {
$url = "https://www.microsoft.com/" + $QueryLocale + "/api/controls/contentinclude/html" $url = "https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku"
$url += "?pageId=" + $RequestData["GetLinks"][0] $url += "?profile=" + $ProfileId
$url += "&host=www.microsoft.com" $url += "&productEditionId=undefined"
$url += "&segments=software-download," + $WindowsVersions[$SelectedVersion][0][1] $url += "&SKU=" + $Entry.SkuId
$url += "&query=&action=" + $RequestData["GetLinks"][1] $url += "&friendlyFileName=undefined"
$url += "&sessionId=" + $SessionId[$Entry.SessionIndex] $url += "&Locale=" + $QueryLocale
$url += "&skuId=" + $Entry.SkuId $url += "&sessionID=" + $SessionId[$Entry.SessionIndex]
$url += "&language=" + $SelectedLanguage.Name
$url += "&sdVersion=2"
if ($Verbosity -ge 2) { if ($Verbosity -ge 2) {
Write-Host Querying $url Write-Host Querying $url
} }
try { try {
# Must add a referer for this request, else Microsoft's servers will deny it # Must add a referer for this request, else Microsoft's servers may deny it
$ref = "https://www.microsoft.com/software-download/windows11" $ref = "https://www.microsoft.com/software-download/windows11"
$r = Invoke-WebRequest -Method Post -Headers @{ "Referer" = $ref } -UseBasicParsing -TimeoutSec $DefaultTimeout -UserAgent $UserAgent -WebSession $Session $url $r = Invoke-RestMethod -Headers @{ "Referer" = $ref } -UseBasicParsing -TimeoutSec $DefaultTimeout -SessionVariable "Session" $url
if ($r -eq $null) {
throw "Could not retrieve architectures from server"
}
if ($Verbosity -ge 5) { if ($Verbosity -ge 5) {
Write-Host "==============================================================================" Write-Host "=============================================================================="
Write-Host $r Write-Host ($r | ConvertTo-Json)
Write-Host "==============================================================================" Write-Host "=============================================================================="
} }
if ($r -match "errorModalMessage") { if ($r.Errors) {
$Alt = [regex]::Match($r.Content, '<p id="errorModalMessage">(.+?)<\/p>').Groups[1].Value -replace "<[^>]+>" -replace "\s+", " " -replace "\?\?\?", "-" if ( $r.Errors[0].Type -eq 9) {
$Alt = [System.Text.Encoding]::UTF8.GetString([byte[]][char[]]$Alt) $msg = Get-Code-715-123130-Message
if (!$Alt) { throw $msg + $SessionId[$Entry.SessionIndex] + "."
$Alt = "Could not retrieve architectures from server" } else {
} elseif ($Alt -match "715-123130") { throw $r.Errors[0].Value
$Alt += " " + $SessionId[$Entry.SessionIndex] + "."
} }
Throw-Error -Req $r -Alt $Alt
}
$pattern = '(?s)(<input.*?></input>)'
ForEach-Object { [regex]::Matches($r, $pattern) } | ForEach-Object { $html += $_.Groups[1].value }
# Need to fix the HTML and JSON data so that it is well-formed
$html = $html.Replace("class=product-download-hidden", "")
$html = $html.Replace("type=hidden", "")
$html = $html.Replace("&nbsp;", " ")
$html = $html.Replace("IsoX86", "&quot;x86&quot;")
$html = $html.Replace("IsoX64", "&quot;x64&quot;")
# As usual Microsoft's left hand is completely uncoordinated with right hand
$html = $html.Replace("Unknown", "&quot;ARM64&quot;")
$html = "<inputs>" + $html + "</inputs>"
$xml = [xml]$html
foreach ($var in $xml.inputs.input) {
$json = $var.value | ConvertFrom-Json;
if ($json) {
$links += @(New-Object PsObject -Property @{ Type = $json.DownloadType; Url = $json.Uri })
} }
foreach ($ProductDownloadOption in $r.ProductDownloadOptions) {
$links += @(New-Object PsObject -Property @{ Arch = (Get-Arch-From-Type $ProductDownloadOption.DownloadType); Url = $ProductDownloadOption.Uri })
} }
if ($links.Length -eq 0) { if ($links.Length -eq 0) {
Throw-Error -Req $r -Alt "Could not retrieve ISO download links" throw "Could not retrieve ISO download links"
} }
} catch { } catch {
Error($_.Exception.Message) Error($_.Exception.Message)
@@ -751,7 +730,7 @@ function Get-Windows-Download-Links([int]$SelectedVersion, [int]$SelectedRelease
$i = 0 $i = 0
$script:SelectedIndex = 0 $script:SelectedIndex = 0
foreach($link in $links) { foreach($link in $links) {
if ($link.Type -eq $PlatformArch) { if ($link.Arch -eq $PlatformArch) {
$script:SelectedIndex = $i $script:SelectedIndex = $i
} }
$i++ $i++
@@ -921,12 +900,12 @@ if ($Cmd) {
$i = 0 $i = 0
foreach ($link in $links) { foreach ($link in $links) {
if ($Arch -eq "List") { if ($Arch -eq "List") {
Write-Host " -" $link.Type Write-Host " -" $link.Arch
} elseif ((!$Arch -and $script:SelectedIndex -eq $i) -or ($Arch -and $link.Type -match $Arch)) { } elseif ((!$Arch -and $script:SelectedIndex -eq $i) -or ($Arch -and $link.Arch -match $Arch)) {
if (!$Arch -and $Verbosity -ge 1) { if (!$Arch -and $Verbosity -ge 1) {
Write-Host "No architecture specified (-Arch). Defaulting to '$($link.Type)'." Write-Host "No architecture specified (-Arch). Defaulting to '$($link.Arch)'."
} }
$Selected += ", [" + $link.Type + "]" $Selected += ", [" + $link.Arch + "]"
$winLink = $link $winLink = $link
break; break;
} }
@@ -1032,7 +1011,7 @@ $Continue.add_click({
if ($links.Length -eq 0) { if ($links.Length -eq 0) {
break break
} }
$script:Architecture = Add-Entry $Stage "Architecture" $links "Type" $script:Architecture = Add-Entry $Stage "Architecture" $links "Arch"
if ($PipeName) { if ($PipeName) {
$XMLForm.Height += $dh / 2; $XMLForm.Height += $dh / 2;
$Margin = $Continue.Margin $Margin = $Continue.Margin
@@ -1104,8 +1083,8 @@ exit $ExitCode
# SIG # Begin signature block # SIG # Begin signature block
# MIItPAYJKoZIhvcNAQcCoIItLTCCLSkCAQExDzANBglghkgBZQMEAgEFADB5Bgor # MIItPAYJKoZIhvcNAQcCoIItLTCCLSkCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDFXTWq2lpU/6fR # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBW4641oExc2Li+
# Ct1W7yWDg6b1THz8JSMDTQ5AW7weJ6CCEkAwggVvMIIEV6ADAgECAhBI/JO0YFWU # R8MjLng+Yc3oW9s9v92HpNoDMS9k3aCCEkAwggVvMIIEV6ADAgECAhBI/JO0YFWU
# jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI # jTanyYqJ1pQWMA0GCSqGSIb3DQEBDAUAMHsxCzAJBgNVBAYTAkdCMRswGQYDVQQI
# DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM # DBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoM
# EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy # EUNvbW9kbyBDQSBMaW1pdGVkMSEwHwYDVQQDDBhBQUEgQ2VydGlmaWNhdGUgU2Vy
@@ -1207,23 +1186,23 @@ exit $ExitCode
# MS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgQ0EgRVYgUjM2 # MS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBDb2RlIFNpZ25pbmcgQ0EgRVYgUjM2
# AhA3xQo8HaADcccNx8YmkC/lMA0GCWCGSAFlAwQCAQUAoHwwEAYKKwYBBAGCNwIB # AhA3xQo8HaADcccNx8YmkC/lMA0GCWCGSAFlAwQCAQUAoHwwEAYKKwYBBAGCNwIB
# DDECMAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # DDECMAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJOGS5HWMc2rDXvPKvX2nFz6 # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIEQXpKnZ+mzHqQT6pR2XLo1J
# Kgz3N3nMaspPXtcMKTojMA0GCSqGSIb3DQEBAQUABIICAJ74B4pGm7vaEYuhs3NT # CTx/O5OYvM95ZSSvMip1MA0GCSqGSIb3DQEBAQUABIICAKDIHzxw40Hcx4XIYMgK
# DVwFMiBWnu7mul6K6Van7Itz0bECfra3r9wpUvcsAoOKEqZ9VX1AmJ/zoIQhQgq3 # 1pHGdXQq/pFmUUAfe/wdTM2oT77nSXdCzaGyu1fPke7mJ5a/wndpAd6xky1B/ZlX
# gPMhbPLeJuIwnjExCLHSwaJ9tXbETIMX50vMBOt6aLsVgnzT2VJTC5UTzvwcW/Lm # 3MqULIkOXAylzQmPWvyt1taN37GCvZgZegNx7GNSSTaiCA+VB8VUHsf1kihYyGhD
# Gaucxnm1bFMNmTMdeOZFLPZBB+WPaJ3dT2lgNU/usB0DyF+i5ilFPFUzesrMDzcV # X1/8MpJfhBp1CUQtNonicUQrDosdn1H/owJAq9h4rb/1y/I2hOTgIZnaVqgFTU35
# x+ijkEOz1jxNxXGbTotPvXjMPkUnBIaYsTmeKhrzn085p8O5l0eUAqjEnYPvAq/n # KBy4qzZzyS4MiHBg+FCEWm4AhyIzk66jUFDPzixvmtsgGzoBjkBKPCvgomk6bUun
# WSvpohmXZa3V7pWRum5XPZ2fe4cs6GjMYmdmD+8ovW4G6KVbNkf75JFKJZ82Y9lq # hwlHU3B3KjkC/wEOydJlx1rUFvZTfZ0f0ptkVp76HtNJR3ZFtqF9lvEIfvP4fZRm
# qn4VO0r5vV4ffD0erFF/KGTavhvz/VlG2par1O/jfa6ue9yPaLLpWiynThz9/lGI # y6Zw7eT2XU8lCDKsQmQ82DJ0K7KJCwGwD1YJKMliAP47mYfHYBqMmJ4cLaHgrVB9
# zbvmAGJuIxCjRcA/Dp/QRkl1a8+VvXtaopd3hQTwgHhlXhnOLKzDKuLoJd9CsDPL # EhIUNW/l/h1vyT9VqXnoiZqQlluhXCDn4mJBgwPasfOIOvCsU5wN8ZRfJgH/aWGr
# 0krYSqps1PD7sAdpr4rx4Gkv+iT9vLJYD+N9KSpfW8BmrHKaKp2BnK79ERxo4fQm # 1SasQhFSzzYbIzxXQ+PqVVqqf7cH0v50+9+3pdPWzNsSFwCWyeXf1T1OEtj+L3o5
# bagOlVCX/FBmSYU+HW52fb2b9fwS7xVupOoruHiIksjTy7YWNgck3CW4WVKxp35y # dqYDekFTZT8ZNX2npAmULY/3OHvx3nVVHNtL9YcMkar86EFQemFmICykD1O9BIf/
# i6SEO4u+1ivx9uhIYQnDJ4mEcezLU1QBFoc5UG6IW1QgbnYVbBwhlrp/poF90NHp # 5MBIi8OdZMscikkn754Lee0lKf6j3BUa9DNKo56bd8DmVkENfVrBUsTcuYjntI/c
# TC2UDXb1m+JfMPdjaOBZdothoYIXOjCCFzYGCisGAQQBgjcDAwExghcmMIIXIgYJ # 5cTqLbVHmjEw74iiN8K+KZQ3oYIXOjCCFzYGCisGAQQBgjcDAwExghcmMIIXIgYJ
# KoZIhvcNAQcCoIIXEzCCFw8CAQMxDzANBglghkgBZQMEAgEFADB4BgsqhkiG9w0B # KoZIhvcNAQcCoIIXEzCCFw8CAQMxDzANBglghkgBZQMEAgEFADB4BgsqhkiG9w0B
# CRABBKBpBGcwZQIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIBw/qIiW # CRABBKBpBGcwZQIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIDuJjw0C
# ZirjZAJAqvzJLoq2PiAMMR2U/b8N3B7swhynAhEArbB7KWdDox91SH7wp5yr5hgP # /S4qlcbKUAofED0hE2uVhSUMkT9inqk0tvwnAhEA2KxVC3z0nr/9UDUnSx3iiBgP
# MjAyNDExMTgwMTQzNDFaoIITAzCCBrwwggSkoAMCAQICEAuuZrxaun+Vh8b56QTj # MjAyNDExMjQyMjI1MTNaoIITAzCCBrwwggSkoAMCAQICEAuuZrxaun+Vh8b56QTj
# MwQwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD # MwQwDQYJKoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD
# ZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYg # ZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYg
# U0hBMjU2IFRpbWVTdGFtcGluZyBDQTAeFw0yNDA5MjYwMDAwMDBaFw0zNTExMjUy # U0hBMjU2IFRpbWVTdGFtcGluZyBDQTAeFw0yNDA5MjYwMDAwMDBaFw0zNTExMjUy
@@ -1328,20 +1307,20 @@ exit $ExitCode
# ATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkG # ATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkG
# A1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3Rh # A1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3Rh
# bXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTjMwQwDQYJYIZIAWUDBAIBBQCggdEwGgYJ # bXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTjMwQwDQYJYIZIAWUDBAIBBQCggdEwGgYJ
# KoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNDExMTgw # KoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNDExMjQy
# MTQzNDFaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFNvThe5i29I+e+T2cUhQhyTV # MjI1MTNaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFNvThe5i29I+e+T2cUhQhyTV
# hltFMC8GCSqGSIb3DQEJBDEiBCCLPWAm+aNyc4DrqvDwqzC7ow3BipFMugVd26EO # hltFMC8GCSqGSIb3DQEJBDEiBCDo4CLpbNKqBx9KM0bBeErpQGYTusNqmfcls8we
# cUUgxTA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCB2dp+o8mMvH0MLOiMwrtZWdf7X # ULQC+jA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCB2dp+o8mMvH0MLOiMwrtZWdf7X
# c9sF1mW5BZOYQ4+a2zANBgkqhkiG9w0BAQEFAASCAgA1D9F+ChwLLlPfrnjVN7y8 # c9sF1mW5BZOYQ4+a2zANBgkqhkiG9w0BAQEFAASCAgA+71TZvJSRS/R+RIBINFye
# Raa08Q1yJoUiIPZw/m4rknXNCjyL768gux2rys7Y0ofhaTL8qB2U5605DNmkuDAw # 0NsNr4ODwMISXyNCaVmkkSx0ZrwysauyT4TjFfEHhEHTYdFi1+QHxfQjHmGI52GY
# NwDXME2bka4xBxDJrI43XFVtu3SpKRbNcv5ujZs0jF4gA3GQykM9UahyusmaxwtQ # HdTN50C/Icy9YSmRssFnqxcv1fjw57ORyqNY8U40KSaWEs/sWHab+rCd2fnlVeGY
# DILavBxCuVye9HX86Z1Bdac1wCWY6hvbtoT4ffm+/Uondgh8VzEWd36ImGOpAXQM # t+IyajmkYO98DECC+8JBHDpW/FG+k+avx/YRoHyjfXJFL4KPY/u3w+IdEf1YluM2
# +UOR8t4JG5777yIBTv51W3qnEx7e4bazezEXIgC7DdXFSq18ypjJuCkLMrb2bWgp # eBKK1cC3PAZJZwp9JCzpura5nqoNblZVHm0umahLRmg7IQP1rBxHnYAHzywXxWzb
# /CxGT2ff4itbsv8Z++oJI2oOewhlLiA5tVMkbB0lVGQbkeEP4nqUKwAvOt7g1i7f # 94ufKc+xrKdnsVrvd/Yb5Wmq/rf2XKFJs0i19c2LOhUoNm/u9JzMr9SCoqDTmm4b
# uSxGdoNbaesSNefScqii1OTXHsLSjNciRQQJZMxJI44F15X7LOVdaaIbvgoLfIWy # jV7Xmll458jxVHNXw9c3pvykePan6CnODMep9sP297uxvHbgrwugWqGihDs0BMu9
# T9s+OgUxmAvDdZKpRcHO//tW6JqFCoi+gdW04mUSvME+MRx6RFZe1cjZNPpJEnwh # nocghVhhhL5zQ4WyJS0hEaMv7EaZOArJAzZUjqXBohYuPjMcpZhvVojNepCKyRHQ
# kmKGhndTmaBjg1nJH0okrfmR87jjIhulemrdJsUZtcdzCBxT1gW5AaHFOMUuHBkR # YOT4KIQ1AKpMuHGSMyfezRCjaoKewg40Xnj2L6o9yPGW3TUg0LXih4efzSw9zlST
# P9h0VG3f12fejgEotaDyTmPYBazFlwSrXtrr1uFmS0SDw681z5xGjK92279MlUpo # Q/mxi0d+HQ+lwEHDCxPmbsRJbJMBjveAuce2sepT8DydpkhltMCImzRBJwSWoCzv
# 8RBOupy5LSPC59Oi1j7SSeW+xyBfS+0aiyKEAYfk1qO0f3Eql9kTQBZjaizDdKmq # pykblSwPL8j51/YfMleZIRkNojUyetKIVj346BWKcDLKFVN7rVcekQ40wfeNZGT4
# ubbhLFZ/dE5qax8nkz2Rxw== # PBooDJtw2GKgsSkAX1niNw==
# SIG # End signature block # SIG # End signature block