$rawScriptUrl = 'https://raw.githubusercontent.com/SpotX-Official/SpotX/refs/heads/main/run.ps1' $mirrorScriptUrl = 'https://spotx-official.github.io/SpotX/run.ps1' $downloadHost = 'loadspot.amd64fox1.workers.dev' $defaultLatestFull = '1.2.86.502.g8cd7fb22' $stableFull = '1.2.13.661.ga588f749' $reportLines = New-Object 'System.Collections.Generic.List[string]' $sensitivePatterns = New-Object 'System.Collections.Generic.List[object]' function Add-SensitivePattern { param( [string]$Pattern, [string]$Replacement = '[redacted]' ) if (-not [string]::IsNullOrWhiteSpace($Pattern)) { $script:sensitivePatterns.Add([PSCustomObject]@{ Pattern = $Pattern Replacement = $Replacement }) | Out-Null } } function Protect-DiagnosticText { param( [AllowNull()] [string]$Text ) if ($null -eq $Text) { return $null } $value = [string]$Text foreach ($item in $script:sensitivePatterns) { $value = [regex]::Replace($value, $item.Pattern, $item.Replacement) } $value } function Add-ReportLine { param( [AllowEmptyString()] [string]$Line = '' ) [void]$script:reportLines.Add((Protect-DiagnosticText -Text $Line)) } function Add-ReportSection { param( [Parameter(Mandatory = $true)] [string]$Title ) if ($script:reportLines.Count -gt 0) { Add-ReportLine } Add-ReportLine ("== {0} ==" -f $Title) } function Add-CommandOutput { param( [string[]]$Lines ) foreach ($line in $Lines) { Add-ReportLine ([string]$line) } } function Invoke-WebRequestCompat { param( [Parameter(Mandatory = $true)] [string]$Uri ) $params = @{ Uri = $Uri ErrorAction = 'Stop' } if ($PSVersionTable.PSVersion.Major -lt 6) { $params.UseBasicParsing = $true } Invoke-WebRequest @params } function Invoke-WebClientStep { param( [Parameter(Mandatory = $true)] [string]$Title, [Parameter(Mandatory = $true)] [string]$Uri ) Invoke-PowerShellStep -Title $Title -Action { $wc = New-Object System.Net.WebClient $stream = $null try { $stream = $wc.OpenRead($Uri) $buffer = New-Object byte[] 1 [void]$stream.Read($buffer, 0, 1) $lines = New-Object 'System.Collections.Generic.List[string]' $lines.Add('WEBCLIENT_OK') | Out-Null $lines.Add(("Url: {0}" -f $Uri)) | Out-Null if ($wc.ResponseHeaders) { foreach ($headerName in $wc.ResponseHeaders.AllKeys) { $lines.Add(("{0}: {1}" -f $headerName, $wc.ResponseHeaders[$headerName])) | Out-Null } } $lines } finally { if ($stream) { $stream.Dispose() } $wc.Dispose() } } } function Get-DiagnosticArchitecture { $arch = $env:PROCESSOR_ARCHITEW6432 if ([string]::IsNullOrWhiteSpace($arch)) { $arch = $env:PROCESSOR_ARCHITECTURE } switch -Regex ($arch) { 'ARM64' { return 'arm64' } '64' { return 'x64' } default { return 'x86' } } } if (-not [string]::IsNullOrWhiteSpace($env:COMPUTERNAME)) { Add-SensitivePattern -Pattern ([regex]::Escape($env:COMPUTERNAME)) -Replacement '[redacted-host]' } if (-not [string]::IsNullOrWhiteSpace($env:USERNAME)) { Add-SensitivePattern -Pattern ([regex]::Escape($env:USERNAME)) -Replacement '[redacted-user]' } if (-not [string]::IsNullOrWhiteSpace($env:USERPROFILE)) { Add-SensitivePattern -Pattern ([regex]::Escape($env:USERPROFILE)) -Replacement '[redacted-profile]' } Add-SensitivePattern -Pattern '(?&1 Add-CommandOutput -Lines ($output | ForEach-Object { [string]$_ }) Add-ReportLine ("ExitCode: {0}" -f $LASTEXITCODE) } catch { Add-CommandOutput -Lines (Format-ExceptionDetails -Exception $_.Exception) } } function Invoke-PowerShellStep { param( [Parameter(Mandatory = $true)] [string]$Title, [Parameter(Mandatory = $true)] [scriptblock]$Action ) Add-ReportSection -Title $Title try { $result = & $Action if ($null -eq $result) { Add-ReportLine return } if ($result -is [System.Array]) { Add-CommandOutput -Lines ($result | ForEach-Object { [string]$_ }) return } Add-ReportLine ([string]$result) } catch { Add-CommandOutput -Lines (Format-ExceptionDetails -Exception $_.Exception) } } $architecture = Get-DiagnosticArchitecture $latestFull = $defaultLatestFull $bootstrapResults = New-Object 'System.Collections.Generic.List[object]' foreach ($source in @( [PSCustomObject]@{ Name = 'raw'; Url = $rawScriptUrl }, [PSCustomObject]@{ Name = 'mirror'; Url = $mirrorScriptUrl } )) { try { $response = Invoke-WebRequestCompat -Uri $source.Url $content = [string]$response.Content if ($latestFull -eq $defaultLatestFull) { $match = [regex]::Match($content, '\[string\]\$latest_full\s*=\s*"([^"]+)"') if ($match.Success) { $latestFull = $match.Groups[1].Value } } $bootstrapResults.Add([PSCustomObject]@{ Name = $source.Name Url = $source.Url Success = $true StatusCode = [int]$response.StatusCode Length = $content.Length }) | Out-Null } catch { $bootstrapResults.Add([PSCustomObject]@{ Name = $source.Name Url = $source.Url Success = $false StatusCode = $null Length = $null Error = $_.Exception.Message }) | Out-Null } } $tempUrl = if ($architecture -eq 'x64') { 'https://{0}/temporary-download/spotify_installer-{1}-x64.exe' -f $downloadHost, $latestFull } else { $null } $directUrl = 'https://{0}/download/spotify_installer-{1}-{2}.exe' -f $downloadHost, $stableFull, $architecture Add-ReportSection -Title 'Environment' Add-ReportLine ("Date: {0}" -f (Get-Date).ToString('yyyy-MM-dd HH:mm:ss zzz')) Add-ReportLine ("ComputerName: {0}" -f $env:COMPUTERNAME) Add-ReportLine ("UserName: {0}" -f $env:USERNAME) Add-ReportLine ("PowerShell: {0}" -f $PSVersionTable.PSVersion) Add-ReportLine ("Architecture: {0}" -f $architecture) Add-ReportLine ("LatestFull: {0}" -f $latestFull) Add-ReportLine ("StableFull: {0}" -f $stableFull) Add-ReportLine ("TempUrl: {0}" -f $(if ($tempUrl) { $tempUrl } else { 'skipped for non-x64 system' })) Add-ReportLine ("DirectUrl: {0}" -f $directUrl) Add-ReportSection -Title 'Bootstrap check' foreach ($item in $bootstrapResults) { Add-ReportLine ("Source: {0}" -f $item.Name) Add-ReportLine ("Url: {0}" -f $item.Url) Add-ReportLine ("Success: {0}" -f $item.Success) if ($item.Success) { Add-ReportLine ("StatusCode: {0}" -f $item.StatusCode) Add-ReportLine ("ContentLength: {0}" -f $item.Length) } else { Add-ReportLine ("Error: {0}" -f $item.Error) } Add-ReportLine } Invoke-CurlStep -Title 'curl version' -Arguments @('-V') Invoke-PowerShellStep -Title 'DNS' -Action { Resolve-DnsName $downloadHost | Format-Table -AutoSize | Out-String -Width 4096 } if ($tempUrl) { Invoke-CurlStep -Title 'HEAD temp' -Arguments @('-I', '-L', '-k', '--ssl-no-revoke', $tempUrl) Invoke-CurlStep -Title '1MB temp' -Arguments @('-L', '-k', '--ssl-no-revoke', '--fail-with-body', '--connect-timeout', '15', '-r', '0-1048575', '-o', 'NUL', '-D', '-', '-w', "`nHTTP_STATUS:%{http_code}`n", $tempUrl) } else { Add-ReportSection -Title 'HEAD temp' Add-ReportLine 'Skipped because temporary route is only used for x64 latest build' Add-ReportSection -Title '1MB temp' Add-ReportLine 'Skipped because temporary route is only used for x64 latest build' } Invoke-CurlStep -Title 'HEAD direct stable' -Arguments @('-I', '-L', '-k', '--ssl-no-revoke', $directUrl) Invoke-CurlStep -Title '1MB direct stable' -Arguments @('-L', '-k', '--ssl-no-revoke', '--fail-with-body', '--connect-timeout', '15', '-r', '0-1048575', '-o', 'NUL', '-D', '-', '-w', "`nHTTP_STATUS:%{http_code}`n", $directUrl) if ($tempUrl) { Invoke-WebClientStep -Title 'WebClient temp' -Uri $tempUrl } else { Add-ReportSection -Title 'WebClient temp' Add-ReportLine 'Skipped because temporary route is only used for x64 latest build' } Invoke-WebClientStep -Title 'WebClient direct stable' -Uri $directUrl Write-Host Write-Host '----- BEGIN DIAGNOSTICS -----' -ForegroundColor Cyan foreach ($line in $reportLines) { Write-Output $line } Write-Host '----- END DIAGNOSTICS -----' -ForegroundColor Cyan