# Скрипт: gper-install.ps1 # Описание: Установщик GPER PowerShell Module # Версия: 1.1.0 # Дата: 2026-05-18 # Запуск: # irm https://ps.gper.ru/gper-install.ps1 | iex # #Requires -Version 5.0 # ============================================ # КОНФИГУРАЦИЯ # ============================================ $Script:GPER_VERSION = @{ Major = 1 Minor = 1 Build = 0 BuildDate = @{ year = 2026 month = 5 day = 18 } } $MODULE_URL = 'https://ps.gper.ru/gper.psm1' $LOCAL_DIR = Join-Path $($env:APPDATA) 'gper.ru' $LOCAL_PSM1 = Join-Path $LOCAL_DIR 'gper.psm1' $PROFILE_PATH = $($PROFILE.CurrentUserAllHosts) $TASK_NAME = 'GperModuleUpdate' $TASK_INTERVAL = 6 # часов между проверками обновлений # ============================================ # ИНИЦИАЛИЗАЦИЯ (TLS 1.2 для irm на PS5) # ============================================ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force -ErrorAction SilentlyContinue # ============================================ # ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ВЫВОДА # ============================================ function Write-Ok { param($m) Microsoft.PowerShell.Utility\Write-Host "[OK] $m" -ForegroundColor Green } function Write-Warn { param($m) Microsoft.PowerShell.Utility\Write-Host "[!] $m" -ForegroundColor Yellow } function Write-Err { param($m) Microsoft.PowerShell.Utility\Write-Host "[X] $m" -ForegroundColor Red } function Write-Info { param($m) Microsoft.PowerShell.Utility\Write-Host " $m" -ForegroundColor Gray } function Write-Step { param($m) Microsoft.PowerShell.Utility\Write-Host "--- $m" -ForegroundColor Cyan } # ============================================ # ЗАГОЛОВОК # ============================================ Microsoft.PowerShell.Utility\Write-Host '' Microsoft.PowerShell.Utility\Write-Host ' GPER Module Setup' -ForegroundColor Cyan Microsoft.PowerShell.Utility\Write-Host ' https://gper.ru' -ForegroundColor DarkGray Microsoft.PowerShell.Utility\Write-Host '' # ============================================ # ШАГ 1: Скачать gper.psm1 с UTF-8 BOM # ============================================ Write-Step 'Загрузка модуля...' if (-not (Test-Path $LOCAL_DIR)) { New-Item -ItemType Directory -Path $LOCAL_DIR -Force | Out-Null Write-Info "Создан каталог: $LOCAL_DIR" } try { $wc = New-Object System.Net.WebClient $bytes = $wc.DownloadData($MODULE_URL) # PS5 требует UTF-8 BOM для корректного отображения кириллицы в .psm1 if (-not ($bytes.Count -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF)) { $bytes = [byte[]](0xEF, 0xBB, 0xBF) + $bytes } [System.IO.File]::WriteAllBytes($LOCAL_PSM1, $bytes) Write-Ok "Модуль сохранён: $LOCAL_PSM1" } catch { Write-Err "Не удалось скачать модуль: $_" exit 1 } # ============================================ # ШАГ 2: Прописать в $PROFILE (идемпотентно) # ============================================ Write-Step 'Настройка профиля PowerShell...' if (-not (Test-Path $PROFILE_PATH)) { New-Item -ItemType File -Path $PROFILE_PATH -Force | Out-Null Write-Info "Создан профиль: $PROFILE_PATH" } $MARKER_START = '# === GPER MODULE START ===' $MARKER_END = '# === GPER MODULE END ===' $VERSION_HEADER = @" #Major = $($Script:GPER_VERSION.Major) #Minor = $($Script:GPER_VERSION.Minor) #Build = $($Script:GPER_VERSION.Build) #BuildDate = "$($Script:GPER_VERSION.BuildDate.year)-$($Script:GPER_VERSION.BuildDate.month)-$($Script:GPER_VERSION.BuildDate.day)" "@ $BODY = @" `$FCMD = 'Show-Page' `$FPATH = Join-Path "`${env:AppData}" 'gper.ru\gper.psm1' if (Test-Path "`$FPATH") { Import-Module -Name "`$FPATH" -Force -DisableNameChecking if (`$FCMD -and (Get-Command -Name `$FCMD -ErrorAction SilentlyContinue)) { & `$FCMD } } if (Get-Module -ListAvailable 'Microsoft.PowerShell.SecretManagement') { Import-Module -Name Microsoft.PowerShell.SecretManagement -Force -DisableNameChecking } "@ # Финальный блок с гарантированным переводом строки после END $NEW_BLOCK = "$MARKER_START`r`n$VERSION_HEADER`r`n$BODY`r`n$MARKER_END`r`n" # Читаем профиль, чистим UTF-8 BOM если есть $existing = if ((Get-Item $PROFILE_PATH).Length -gt 0) { [System.IO.File]::ReadAllText($PROFILE_PATH, [System.Text.Encoding]::UTF8) } else { '' } # Удаляем любой существующий GPER-блок (между маркерами включительно) $blockRegex = '(?s)' + [regex]::Escape($MARKER_START) + '.*?' + [regex]::Escape($MARKER_END) + '\s*' $cleaned = [regex]::Replace($existing, $blockRegex, '') # Чистим возможные legacy строки (старые установки без маркеров) $legacyPatterns = @( [regex]::Escape("Import-Module `"$LOCAL_PSM1`" -DisableNameChecking") + '\s*', [regex]::Escape("Import-Module `"$LOCAL_PSM1`"") + '\s*' ) foreach ($lp in $legacyPatterns) { $cleaned = [regex]::Replace($cleaned, $lp, '') } # Собираем: наш блок в начало, остальное — после $final = $NEW_BLOCK + $cleaned.TrimStart("`r","`n") # Сравнение: если ничего реально не изменилось — не пишем if ($final -ceq $existing) { Write-Ok 'Профиль уже актуален — пропуск' } else { [System.IO.File]::WriteAllText($PROFILE_PATH, $final, (New-Object System.Text.UTF8Encoding($false))) Write-Ok "Профиль обновлён: $PROFILE_PATH" } # ============================================ # ШАГ 3: Задача планировщика для автообновления # ============================================ Write-Step "Настройка автообновления (каждые $TASK_INTERVAL ч)..." # Скрипт обновления через EncodedCommand — избегает проблем с экранированием # Скачивает модуль с BOM, как и при установке $updateScript = @" `$b = (New-Object Net.WebClient).DownloadData('$MODULE_URL') if (-not (`$b[0] -eq 0xEF -and `$b[1] -eq 0xBB -and `$b[2] -eq 0xBF)) { `$b = [byte[]](0xEF, 0xBB, 0xBF) + `$b } [IO.File]::WriteAllBytes('$LOCAL_PSM1', `$b) "@ $encodedCmd = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($updateScript)) # schtasks.exe вместо New-ScheduledTask* — модуль ScheduledTasks недоступен на Win7 $startTime = (Get-Date).AddMinutes(1).ToString('HH:mm') $trValue = "powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand $encodedCmd" $result = & schtasks.exe /Create /F /TN $TASK_NAME /TR $trValue /SC HOURLY /MO $TASK_INTERVAL /ST $startTime 2>&1 if ($LASTEXITCODE -eq 0) { Write-Ok "Задача создана/обновлена: $TASK_NAME" } else { Write-Warn "Не удалось создать задачу: $result" } # ============================================ # ШАГ 4: Загрузить модуль в текущий сеанс # ============================================ Write-Step 'Загрузка модуля в текущий сеанс...' try { Import-Module $LOCAL_PSM1 -Force -DisableNameChecking -ErrorAction Stop Write-Ok 'Модуль загружен' } catch { Write-Warn "Не удалось загрузить модуль в текущий сеанс: $_" } # ============================================ # ГОТОВО # ============================================ Microsoft.PowerShell.Utility\Write-Host '' Microsoft.PowerShell.Utility\Write-Host ' Установка завершена!' -ForegroundColor Green Microsoft.PowerShell.Utility\Write-Host '' Write-Info "Модуль: $LOCAL_PSM1" Write-Info "Профиль: $PROFILE_PATH" Write-Info "Задача: $TASK_NAME (каждые $TASK_INTERVAL ч)" Microsoft.PowerShell.Utility\Write-Host '' Show-Page