Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $installerApiUrl = "https://sonarify.ru/sonarify/download/" $tempRoot = Join-Path $env:TEMP "SonarifySetupInstall" $installerPath = Join-Path $tempRoot "SonarifySetup.exe" function Write-InstallLog([string]$message) { $time = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Host "[$time] [INFO] $message" -ForegroundColor Cyan } function Write-InstallError([string]$message) { $time = Get-Date -Format "yyyy-MM-dd HH:mm:ss" Write-Host "[$time] [ERROR] $message" -ForegroundColor Red } function Format-Bytes([object]$bytes) { if ($bytes -is [System.Array]) { if ($bytes.Count -gt 0) { $bytes = $bytes[0] } else { $bytes = 0 } } try { $value = [double]$bytes } catch { $value = 0 } if ($value -lt 0) { $value = 0 } if ($value -eq 0) { return "0 MB" } $mb = [Math]::Round(($value / 1MB), 2) return "$mb MB" } function To-Long([object]$value) { if ($value -is [System.Array]) { if ($value.Count -gt 0) { $value = $value[0] } else { $value = 0 } } try { return [long]$value } catch { return 0L } } function Get-RemainingTime([double]$remainingBytes, [double]$bytesPerSecond) { if ($bytesPerSecond -le 0 -or $remainingBytes -le 0) { return "00:00:00" } return [timespan]::FromSeconds([int]($remainingBytes / $bytesPerSecond)).ToString("hh\:mm\:ss") } function Download-WithProgress { param( [string]$Url, [string]$OutFile ) $request = [System.Net.WebRequest]::Create($Url) $response = $request.GetResponse() try { $totalSize = To-Long $response.ContentLength $input = $response.GetResponseStream() $output = [System.IO.File]::Create($OutFile) $buffer = New-Object byte[] 1048576 $downloaded = 0L $startTime = Get-Date $lastUiUpdate = Get-Date $bufferSize = $buffer.Length while ($true) { $read = $input.Read($buffer, 0, $bufferSize) if ($read -le 0) { break } $output.Write($buffer, 0, $read) $downloaded += $read $now = Get-Date if (($now - $lastUiUpdate).TotalMilliseconds -lt 500) { continue } $lastUiUpdate = $now $elapsed = [Math]::Max(0.001, ($now - $startTime).TotalSeconds) $speed = $downloaded / $elapsed $percent = 0 $leftBytes = 0L $eta = "00:00:00" if ($totalSize -gt 0) { $percent = [Math]::Min(100, [Math]::Round(($downloaded * 100.0) / $totalSize, 1)) $leftBytes = [Math]::Max(0, $totalSize - $downloaded) $eta = Get-RemainingTime -remainingBytes $leftBytes -bytesPerSecond $speed } $downloadedText = Format-Bytes $downloaded $speedText = Format-Bytes $speed if ($totalSize -gt 0) { $totalSizeText = Format-Bytes $totalSize $status = "Загружено $downloadedText / $totalSizeText ($percent%), осталось: $eta, скорость: $speedText/с" } else { $status = "Загружено $downloadedText, скорость: $speedText/с" } Write-Progress -Activity "Скачивание установщика Sonarify" -Status $status -PercentComplete $percent } Write-Progress -Activity "Скачивание установщика Sonarify" -Status "Загрузка завершена: $(Format-Bytes $downloaded)" -PercentComplete 100 -Completed } finally { if ($null -ne $output) { $output.Close() } if ($null -ne $input) { $input.Close() } if ($null -ne $response) { $response.Close() } } } function Get-LatestFileName([string]$apiUrl, [string]$extension, [string]$archHint) { $payload = Invoke-RestMethod -Uri $apiUrl -Method Get $files = @($payload.files) $candidates = @($files | Where-Object { $_ -is [string] -and $_.ToLower().EndsWith($extension.ToLower()) }) if (-not $candidates -or $candidates.Count -eq 0) { return $null } if ($archHint) { $archCandidates = @($candidates | Where-Object { $_ -match $archHint }) if ($archCandidates -and $archCandidates.Count -gt 0) { $candidates = $archCandidates } } $sorted = @() foreach ($file in $candidates) { if ($file -match "(?\d+)\.(?\d+)\.(?\d+)") { $version = "{0:D3}.{1:D3}.{2:D3}" -f [int]$matches["major"], [int]$matches["minor"], [int]$matches["patch"] } else { $version = "000.000.000" } $sorted += [pscustomobject]@{ File = [string]$file; Version = $version } } return ($sorted | Sort-Object -Property @{Expression = "Version"; Descending = $true} | Select-Object -First 1).File } try { if (Test-Path $tempRoot) { Remove-Item -Recurse -Force $tempRoot } New-Item -ItemType Directory -Path $tempRoot | Out-Null Write-InstallLog "Запрашиваю список установщиков Sonarify..." $archHint = "" if ($env:PROCESSOR_ARCHITECTURE -match "AMD64|IA64|x64") { $archHint = "amd64|x64|64" } elseif ($env:PROCESSOR_ARCHITECTURE -match "ARM64|ARM") { $archHint = "arm64|aarch64|arm" } else { $archHint = "386|x86" } $candidate = Get-LatestFileName -apiUrl $installerApiUrl -extension ".exe" -archHint $archHint if (-not $candidate) { throw "Не найден установщик Sonarify для Windows (.exe)." } $installerUrl = "{0}?file={1}" -f $installerApiUrl, [uri]::EscapeDataString($candidate) Write-InstallLog "Скачиваю $candidate ..." Download-WithProgress -Url $installerUrl -OutFile $installerPath Unblock-File -Path $installerPath if (-not (Test-Path $installerPath)) { throw "Не удалось скачать исполняемый файл установщика." } Write-InstallLog "Файл сохранен: $installerPath" Write-InstallLog "Запускаю установщик..." Start-Process -FilePath $installerPath Write-InstallLog "Скрипт завершил передачу файла, установка продолжается в мастере Sonarify." } catch { Write-InstallError "Не удалось скачать и подготовить установщик Sonarify: $($_.Exception.Message)" exit 1 } finally { if (Test-Path $tempRoot) { Remove-Item -Recurse -Force $tempRoot -ErrorAction SilentlyContinue } }