# $OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::new($true) Write-Host "`nУстановим кодировку консоли на UTF-8" # === Проверка прав администратора === if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host "Этот скрипт требует прав администратора. Запустите его от имени администратора." -ForegroundColor Red Write-Host "`nНажмите ПРОБЕЛ, чтобы закрыть..." do { $key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } while ($key.VirtualKeyCode -ne 32) exit 1 } Write-Host "`nПроверка прав администратора" Write-Host "`nНажмите ПРОБЕЛ, чтобы закрыть..." do { $key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } while ($key.VirtualKeyCode -ne 32) ########################################### # === Функция: Проверка установленности === function Check-Installed { param ([bool]$verbose = $false) # Кэш для ускорения проверок $cache = @{ Registry = @{} Programs = @() Processes = @() Packages = @() } Write-Log "Начинаем проверку установленных программ..." -level "INFO" # 1. Проверка через Get-Package (PowerShell 5.1+) try { $cache.Packages = Get-Package -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name } catch { Write-Log "Не удалось получить список пакетов через Get-Package: $_" -level "WARNING" } # 2. Проверка в реестре $registryPaths = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" ) foreach ($path in $registryPaths) { try { Get-ItemProperty -Path $path -ErrorAction SilentlyContinue | ForEach-Object { if ($_.DisplayName) { $cache.Registry[$_.DisplayName] = $true if ($_.DisplayVersion) { $cache.Registry["$($_.DisplayName) $($_.DisplayVersion)"] = $true } } } } catch { Write-Log "Ошибка при чтении реестра ($path): $_" -level "WARNING" } } # 3. Проверка в Program Files $programDirs = @("C:\Program Files", "C:\Program Files (x86)") foreach ($dir in $programDirs) { if (Test-Path $dir) { try { $cache.Programs += Get-ChildItem -Path $dir -Directory -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name } catch { Write-Log "Ошибка при сканировании ${dir}: $_" -level "WARNING" } } } # 4. Проверка запущенных процессов try { $cache.Processes = Get-Process | Select-Object -ExpandProperty ProcessName } catch { Write-Log "Не удалось получить список процессов: $_" -level "WARNING" } # Проверка каждого установщика for ($i=0; $i -lt $installers.Count; $i++) { $installer = $installers[$i] $nameParts = $installer.name -split '\s+' | Where-Object { $_.Length -gt 2 } $isInstalled = $false # Проверка в кэше $searchPatterns = @($installer.name) + $nameParts foreach ($pattern in $searchPatterns) { # Проверка в реестре if (-not $isInstalled) { $isInstalled = $cache.Registry.Keys | Where-Object { $_ -like "*$pattern*" } | ForEach-Object { $true } } # Проверка в Program Files if (-not $isInstalled) { $isInstalled = $cache.Programs | Where-Object { $_ -like "*$pattern*" } | ForEach-Object { $true } } # Проверка в пакетах if (-not $isInstalled) { $isInstalled = $cache.Packages | Where-Object { $_ -like "*$pattern*" } | ForEach-Object { $true } } # Проверка в процессах if (-not $isInstalled) { $isInstalled = $cache.Processes | Where-Object { $_ -like "*$pattern*" } | ForEach-Object { $true } } if ($isInstalled) { break } } $isAlreadyInstalled[$i] = $isInstalled $selected[$i] = -not $isInstalled if ($verbose) { $status = if ($isInstalled) { "УЖЕ УСТАНОВЛЕНА" } else { "НЕ УСТАНОВЛЕНА" } Write-Log "$($installer.name) - $status" } } Write-Log "Проверка установленных программ завершена" -level "SUCCESS" } # === Функция: Показать меню === function Show-Menu { Clear-Host Write-Host ("Диск: {0}:\\" -f $drive.Name) Write-Host ("Объем: {0:N2} GB" -f $totalGB) Write-Host ("Свободно: {0:N2} GB" -f $freeGB) Write-Host ("Занято: {0:N2} GB" -f $usedGB) Write-Host "" Write-Host "Внимание установщик может ошибочно определять установленные программы" Write-Host "Выберите программы для установки:" Write-Host "'a' - выбрать всё, 'n' - снять всё, 'r' - перепроверить, 's' - начать установку, 'q' - выход.`n" for ($i=0; $i -lt $installers.Count; $i++) { $flag = if ($selected[$i]) { "[X]" } else { "[ ]" } $status = if ($isAlreadyInstalled[$i]) { " (Уже установлена)" } else { "" } Write-Host "$($i+1)): $flag $($installers[$i].name)$status" } } function Write-Log { param ( [string]$message, [string]$level = "INFO", [bool]$showInConsole = $true ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logEntry = "[$timestamp] [$level] $message" # Добавляем в лог-файл if (-not (Test-Path $logFile)) { "### Лог установки ###" | Out-File -FilePath $logFile -Encoding UTF8 } $logEntry | Out-File -FilePath $logFile -Encoding UTF8 -Append # Выводим в консоль с цветами if ($showInConsole) { switch ($level) { "ERROR" { Write-Host $logEntry -ForegroundColor Red } "WARNING" { Write-Host $logEntry -ForegroundColor Yellow } "SUCCESS" { Write-Host $logEntry -ForegroundColor Green } default { Write-Host $logEntry } } } } function Download-FileWithProgress1 { param ( [string]$url, [string]$destination, [int]$maxRetries = 3, [int]$retryDelay = 5 ) # Стандартные HTTP-заголовки для имитации браузера $browserHeaders = @{ "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "Accept" = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8" "Accept-Language" = "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7" "Accept-Encoding" = "gzip, deflate, br" "Connection" = "keep-alive" "Referer" = "https://www.google.com/" "DNT" = "1" "Upgrade-Insecure-Requests" = "1" } # Если UNC-путь — просто копируем if ($url.StartsWith("\\") -or $url.StartsWith("//")) { try { Copy-Item -Path $url -Destination $destination -Force Write-Host "Скопировано с UNC: $destination" return $true } catch { Write-Host "Ошибка копирования UNC: $_" return $false } } $retryCount = 0 $success = $false while ($retryCount -lt $maxRetries -and -not $success) { try { Add-Type -AssemblyName System.Net.Http # Настройка HTTP-клиента $handler = New-Object System.Net.Http.HttpClientHandler $handler.AllowAutoRedirect = $true $handler.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor [System.Net.DecompressionMethods]::Deflate $handler.MaxAutomaticRedirections = 10 $client = [System.Net.Http.HttpClient]::new($handler) $client.Timeout = [System.TimeSpan]::FromSeconds(30) # Добавляем заголовки браузера foreach ($header in $browserHeaders.GetEnumerator()) { $client.DefaultRequestHeaders.TryAddWithoutValidation($header.Key, $header.Value) } # Получаем размер файла $headRequest = New-Object System.Net.Http.HttpRequestMessage -ArgumentList @( [System.Net.Http.HttpMethod]::Head, $url ) $headResponse = $client.SendAsync($headRequest).Result if ($headResponse.StatusCode -eq [System.Net.HttpStatusCode]::Unauthorized) { Write-Host "Обнаружена ошибка 401 Unauthorized. Попытка обхода..." # Добавляем дополнительные заголовки для авторизации $client.DefaultRequestHeaders.Add("Authorization", "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("user:pass"))) $headResponse = $client.SendAsync($headRequest).Result } if (-not $headResponse.IsSuccessStatusCode) { throw "Ошибка HEAD-запроса: $($headResponse.StatusCode) $($headResponse.ReasonPhrase)" } $totalBytes = $headResponse.Content.Headers.ContentLength $headResponse.Dispose() # Проверяем существующую загрузку $existingBytes = 0 if (Test-Path $destination) { $existingBytes = (Get-Item $destination).Length if ($totalBytes -and $existingBytes -ge $totalBytes) { Write-Host "Файл уже полностью скачан" return $true } } # Настраиваем GET-запрос $request = New-Object System.Net.Http.HttpRequestMessage -ArgumentList @( [System.Net.Http.HttpMethod]::Get, $url ) if ($existingBytes -gt 0) { $request.Headers.Range = New-Object System.Net.Http.Headers.RangeHeaderValue($existingBytes, $null) Write-Host "Продолжаем загрузку с $existingBytes байт" } # Выполняем запрос $response = $client.SendAsync($request, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).Result if (-not $response.IsSuccessStatusCode -and $response.StatusCode -ne [System.Net.HttpStatusCode]::PartialContent) { throw "HTTP ошибка: $($response.StatusCode) $($response.ReasonPhrase)" } # Получаем поток для чтения $stream = $response.Content.ReadAsStreamAsync().Result # Открываем файл для записи $fileMode = if ($existingBytes -gt 0) { [System.IO.FileMode]::Append } else { [System.IO.FileMode]::Create } $fileStream = [System.IO.File]::Open($destination, $fileMode, [System.IO.FileAccess]::Write) $buffer = New-Object byte[] (1024*64) # 64KB буфер $totalRead = $existingBytes $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $lastUpdate = 0 # Чтение и запись данных с прогрессом do { $read = $stream.Read($buffer, 0, $buffer.Length) if ($read -gt 0) { $fileStream.Write($buffer, 0, $read) $totalRead += $read # Обновляем прогресс не чаще чем раз в 200мс if ($stopwatch.ElapsedMilliseconds -ge ($lastUpdate + 200)) { $lastUpdate = $stopwatch.ElapsedMilliseconds if ($totalBytes) { $percent = ($totalRead / $totalBytes) * 100 $speed = ($totalRead - $existingBytes) / ($stopwatch.ElapsedMilliseconds / 1000) / 1MB Write-Progress -Activity "Скачивание" -Status ( "{0:N1} MB из {1:N1} MB ({2:N2} MB/s)" -f ($totalRead/1MB), ($totalBytes/1MB), $speed ) -PercentComplete $percent } else { Write-Progress -Activity "Скачивание" -Status ("{0:N1} MB загружено" -f ($totalRead/1MB)) } } } } while ($read -gt 0) Write-Progress -Activity "Скачивание" -Completed $fileStream.Close() $stream.Close() $response.Dispose() $client.Dispose() Write-Host "Файл успешно скачан: $destination ($([math]::Round($totalRead/1MB,1)) MB)" $success = $true } catch { $retryCount++ Write-Host "Ошибка при скачивании (попытка $retryCount из $maxRetries): $_" -ForegroundColor Red if (Test-Path $destination) { Remove-Item $destination -Force -ErrorAction SilentlyContinue } if ($retryCount -lt $maxRetries) { Write-Host "Повторная попытка через $retryDelay секунд..." Start-Sleep -Seconds $retryDelay } } } return $success } # === Функция: Установка программы === function Install-Program { param ($installer, $downloadPath) try { $success = $false $fileExt = [System.IO.Path]::GetExtension($downloadPath).ToLower() Write-Log "Начинаем установку $($installer.name) из $downloadPath" -level "INFO" switch ($fileExt) { ".msi" { $arguments = "/i `"$downloadPath`"" if ($installer.arguments) { $arguments += " $($installer.arguments)" } Write-Log "Запуск MSI установщика: msiexec.exe $arguments" Start-Process "msiexec.exe" -ArgumentList $arguments -Wait -NoNewWindow $success = $true } ".zip" { $extractPath = Join-Path $downloadFolder $installer.name if (-not (Test-Path $extractPath)) { New-Item -Path $extractPath -ItemType Directory | Out-Null } Write-Log "Распаковка ZIP в $extractPath" Expand-Archive -Path $downloadPath -DestinationPath $extractPath -Force # Если указан файл для запуска после распаковки if ($installer.postExtract) { $executable = Join-Path $extractPath $installer.postExtract Write-Log "Запуск пост-установочного файла: $executable" Start-Process -FilePath $executable -ArgumentList $installer.arguments -Wait -NoNewWindow } $success = $true } ".exe" { $arguments = $installer.arguments # "/S /quiet /norestart" Write-Log "Запуск EXE установщика: $downloadPath $arguments" Start-Process -FilePath $downloadPath -ArgumentList $arguments -Wait -NoNewWindow $success = $true } default { Write-Log "Неизвестный тип установщика: $fileExt" -level "WARNING" Write-Log "Попытка запуска напрямую..." Start-Process -FilePath $downloadPath -ArgumentList $installer.arguments -Wait -NoNewWindow $success = $true } } if ($success) { Write-Log "$($installer.name) успешно установлена" -level "SUCCESS" return $true } } catch { Write-Log "Ошибка при установке $($installer.name): $_" -level "ERROR" return $false } } # === Функция для загрузки JSON через HttpClient === function Get-RemoteJson { param ( [string]$url, [int]$maxRetries = 3, [int]$retryDelay = 5 ) $retryCount = 0 $success = $false while ($retryCount -lt $maxRetries -and -not $success) { try { Add-Type -AssemblyName System.Net.Http # Настройка HTTP-клиента $handler = New-Object System.Net.Http.HttpClientHandler $handler.AllowAutoRedirect = $true $handler.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor [System.Net.DecompressionMethods]::Deflate $handler.MaxAutomaticRedirections = 10 $client = New-Object System.Net.Http.HttpClient($handler) $client.Timeout = [System.TimeSpan]::FromSeconds(30) # Добавляем заголовки $client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") $client.DefaultRequestHeaders.Add("Accept", "application/json") # Выполняем запрос $response = $client.GetAsync($url).Result if (-not $response.IsSuccessStatusCode) { throw "HTTP ошибка: $($response.StatusCode) $($response.ReasonPhrase)" } # Читаем содержимое $content = $response.Content.ReadAsStringAsync().Result $result = $content | ConvertFrom-Json $response.Dispose() $client.Dispose() return $result } catch { $retryCount++ Write-Host "Ошибка при загрузке JSON (попытка $retryCount из $maxRetries): $_" -ForegroundColor Yellow if ($retryCount -lt $maxRetries) { Write-Host "Повторная попытка через $retryDelay секунд..." Start-Sleep -Seconds $retryDelay } } } throw "Не удалось загрузить JSON после $maxRetries попыток" } ############################################################################### # === Запуск === # Получаем информацию о системном диске $drive = Get-PSDrive -Name C $totalGB = ($drive.Used + $drive.Free) / 1GB $freeGB = $drive.Free / 1GB $usedGB = $drive.Used / 1GB # === Инициализация путей и переменных === #$jsonPath = ".\installers.json" $jsonUrl = "https://share.anykey54.ru/list/installers.json" $downloadFolder = Join-Path ([Environment]::GetFolderPath("UserProfile")) "Downloads\MyInstallers" $logFile = Join-Path $downloadFolder "install_log.txt" if (-not (Test-Path $downloadFolder)) { New-Item -Path $downloadFolder -ItemType Directory | Out-Null } Write-Log "### Начало работы скрипта ###" -showInConsole $false # Читаем JSON # try { # $installers = Get-Content $jsonPath | ConvertFrom-Json # Write-Log "Конфигурационный файл загружен: $jsonPath" # } # catch { # Write-Log "Ошибка загрузки конфигурационного файла: $_" -level "ERROR" # exit 1 # } # Загружаем JSON с удаленного сервера try { Write-Host "Загрузка списка установщиков с $jsonUrl..." $installers = Get-RemoteJson -url $jsonUrl Write-Log "Конфигурационный файл успешно загружен с $jsonUrl" } catch { Write-Log "Ошибка загрузки конфигурационного файла: $_" -level "ERROR" Write-Host "`nНажмите ПРОБЕЛ, чтобы закрыть..." do { $key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } while ($key.VirtualKeyCode -ne 32) exit 1 } # Массивы $selected = @{} $isAlreadyInstalled = @{} $report = @{ Downloaded = @(); Installed = @(); Failed = @() } Write-Host "`nИнициализация путей и переменных $jsonPath" #Write-Host "`nНажмите ПРОБЕЛ, чтобы закрыть..." #do { $key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } while ($key.VirtualKeyCode -ne 32) ########################################### Write-Host "Выполняем начальную проверку установленных программ..." Check-Installed -verbose:$false $menu = $true while ($menu) { Show-Menu $input = Read-Host "Ваш выбор (номер, 'a', 'n', 'r', 's', 'q')" switch ($input) { 's' { $menu = $false } 'a' { for ($i=0; $i -lt $installers.Count; $i++) { if (-not $isAlreadyInstalled[$i]) { $selected[$i] = $true } } } 'n' { for ($i=0; $i -lt $installers.Count; $i++) { $selected[$i] = $false } } 'r' { Write-Host "=== Запускаем повторную проверку ===" Check-Installed -verbose:$true Write-Host "`nПроверка завершена. Нажмите ПРОБЕЛ, чтобы продолжить..." do { $key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } while ($key.VirtualKeyCode -ne 32) } 'q' { Write-Log "Выход из скрипта по запросу пользователя" -level "INFO" #[Environment]::Exit(0) #return exit } default { if ($input -match '^\d+$') { $index = [int]$input - 1 if ($index -ge 0 -and $index -lt $installers.Count) { $selected[$index] = -not $selected[$index] } } } } } # === Установка выбранных программ === $totalToInstall = ($selected.Values | Where-Object { $_ }).Count $installedCount = 0 for ($i=0; $i -lt $installers.Count; $i++) { if ($selected[$i]) { $installer = $installers[$i] $installedCount++ Write-Progress -Activity "Установка программ" -Status "Устанавливается $($installer.name)" -PercentComplete ($installedCount/$totalToInstall*100) Write-Log "`n=== Обработка: $($installer.name) ($installedCount из $totalToInstall) ===" -level "INFO" $report.Downloaded += $installer.name foreach ($url in $installer.urls) { try { $fileName = Split-Path $url -Leaf if ($fileName.EndsWith(".exe") -or $fileName.EndsWith(".msi")) { Write-Host "В пути указано расширение файла" } else { $fileName = Split-Path $installer.name -Leaf } $downloadPath = Join-Path $downloadFolder $fileName if (Test-Path $downloadPath) { Write-Log "Файл уже существует, пропускаем загрузку: $downloadPath" -level "INFO" } else { if (-not (Download-FileWithProgress1 -url $url -destination $downloadPath)) { continue } } if (Install-Program -installer $installer -downloadPath $downloadPath) { $report.Installed += $installer.name break } else { $report.Failed += $installer.name } } catch { $report.Failed += $installer.name Write-Log "Критическая ошибка при обработке $($installer.name): $_" -level "ERROR" } } } } # === Отчёт === Write-Host "`n=== Отчёт ===" Write-Host "Скачаны: $($report.Downloaded -join ', ')" Write-Host "Установлены: $($report.Installed -join ', ')" Write-Host "Ошибки: $($report.Failed -join ', ')" Write-Host "`nНажмите ПРОБЕЛ, чтобы закрыть..." do { $key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") } while ($key.VirtualKeyCode -ne 32)