Загрузка данных


# ============================================================
#  ftp.ps1 — скачивание архивов с FTP по папкам
# ============================================================
#  Скачивает архивы в соответствующие подпапки внутри App2.
#  После скачивания запускает downloader.ps1.
# ============================================================

$ftpHost     = "n5001-fap.dpc.tax.nalog.ru"
$ftpPath     = "/AisNalog3"
$ftpUser     = "anonymous"
$ftpPassword = ""

# Корневая папка, где лежат downloader.ps1 и папки KPE, PPK и т.д.
$rootAppPath = "C:\Users\n4200-01-031\Desktop\App2"
$networkBasePath = "Z:\Documents"

# Соответствие: папка на FTP -> локальная папка -> папка на Z:
$folderMapping = @(
    @{ Remote = "AisNalog3_PROM"; Local = "KPE";   Network = "KPE" }
    @{ Remote = "AisNalog3_PPK";  Local = "PPK";   Network = "PPK" }
    @{ Remote = "OE";             Local = "KOE";   Network = "KOE" }
    @{ Remote = "OE_IZ";          Local = "KOEIZ"; Network = "KOEIZ" }
)

$versionPattern = '^\d+_\d+_\d+_\d+$'
$scriptDir  = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $PSCommandPath }
$downloader = Join-Path $scriptDir "downloader.ps1"

$BufferSize      = 16MB
$ProgressEveryMs = 300
$ShowSpeed       = $true

function Show-ProgressLine {
    param([string]$FileName, [int64]$Downloaded, [int64]$Total, [int]$Width = 30)
    if ($Total -gt 0) {
        $percent = [math]::Floor(($Downloaded / $Total) * 100)
        if ($percent -gt 100) { $percent = 100 }
        $mbNow   = [math]::Round($Downloaded / 1MB, 1)
        $mbTotal = [math]::Round($Total / 1MB, 1)
        $filled  = [math]::Floor($Width * $percent / 100)
        if ($filled -gt $Width) { $filled = $Width }
        $empty = $Width - $filled
        $bar   = ("*" * $filled) + ("." * $empty)
        Write-Host ("`r  {0} : [{1}] {2}%  {3}/{4} MB" -f $FileName, $bar, $percent, $mbNow, $mbTotal) -NoNewline -ForegroundColor Cyan
    } else {
        $mbNow = [math]::Round($Downloaded / 1MB, 1)
        Write-Host ("`r  {0} : {1} MB" -f $FileName, $mbNow) -NoNewline -ForegroundColor Cyan
    }
}

function Get-FtpFoldersWithDates {
    param([string]$ftpDirPath)
    $folders = @{}
    try {
        $uri = "ftp://$ftpHost$ftpDirPath/"
        $request = [System.Net.FtpWebRequest]::Create($uri)
        $request.Method      = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
        $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
        $request.UseBinary   = $true; $request.UsePassive = $true
        $request.EnableSsl   = $false; $request.KeepAlive = $false; $request.Timeout = 30000

        $response = $request.GetResponse()
        $stream   = $response.GetResponseStream()
        $reader   = New-Object System.IO.StreamReader($stream)
        $listing  = $reader.ReadToEnd()
        $reader.Close(); $response.Close()

        $lines = $listing -split "`r?`n" | Where-Object { $_ -ne "" }

        foreach ($line in $lines) {
            $isDirectory = $false; $folderName = $null; $dateTime = $null
            if ($line -match "^d.*\s+(\S+)$") {
                $folderName = $matches[1]; $isDirectory = $true
                if ($line -match "(\w{3})\s+(\d{1,2})\s+(\d{2}:\d{2})") {
                    $month = $matches[1]; $day = $matches[2]; $time = $matches[3]
                    $currentYear = (Get-Date).Year
                    try { $dateTime = [DateTime]::ParseExact("$month $day $currentYear $time", "MMM d yyyy HH:mm", [System.Globalization.CultureInfo]::InvariantCulture) } catch {}
                }
            }
            if ($isDirectory -and $folderName -and $folderName -ne "." -and $folderName -ne "..") {
                if ($dateTime) { $folders[$folderName] = $dateTime } else { $folders[$folderName] = [DateTime]::MinValue }
            }
        }
        return $folders
    } catch { return @{} }
}

function Get-FtpFileList {
    param([string]$ftpDirPath)
    $items = @()
    try {
        $uri = "ftp://$ftpHost$ftpDirPath/"
        $request = [System.Net.FtpWebRequest]::Create($uri)
        $request.Method      = [System.Net.WebRequestMethods+Ftp]::ListDirectory
        $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
        $request.UseBinary   = $true; $request.UsePassive = $true
        $request.EnableSsl   = $false; $request.KeepAlive = $false; $request.Timeout = 30000

        $response = $request.GetResponse()
        $stream   = $response.GetResponseStream()
        $reader   = New-Object System.IO.StreamReader($stream)
        $listing  = $reader.ReadToEnd()
        $reader.Close(); $response.Close()

        $lines = $listing -split "`r?`n" | Where-Object { $_ -ne "" }
        foreach ($line in $lines) { $items += $line.Trim() }
        return $items
    } catch { return @() }
}

function Get-FtpFileSize {
    param([string]$remoteUri)
    try {
        $request = [System.Net.FtpWebRequest]::Create($remoteUri)
        $request.Method      = [System.Net.WebRequestMethods+Ftp]::GetFileSize
        $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
        $request.UseBinary   = $true; $request.UsePassive = $true
        $request.Timeout     = 30000
        $response = $request.GetResponse()
        $size = $response.ContentLength
        $response.Close()
        if ($size -gt 0) { return [int64]$size }
    } catch {}
    return -1
}

function Get-VersionFile {
    param([string]$folderPath)
    if (!(Test-Path $folderPath)) { return $null }
    $files = Get-ChildItem -Path $folderPath -File -Force -ErrorAction SilentlyContinue
    foreach ($f in $files) {
        if ($f.BaseName -match $versionPattern) { return $f }
    }
    return $null
}

function Download-FtpFile {
    param([string]$remoteUri, [string]$localPath)
    try {
        $fileName = Split-Path $localPath -Leaf
        $knownSize = Get-FtpFileSize -remoteUri $remoteUri

        $resumeOffset = 0
        if (Test-Path $localPath) {
            $resumeOffset = (Get-Item $localPath).Length
            if ($knownSize -gt 0 -and $resumeOffset -ge $knownSize) {
                Write-Host "  Локальный файл уже полностью скачан." -ForegroundColor Green
                return $true
            }
            if ($resumeOffset -gt 0) { Write-Host "  Докачка: $([math]::Round($resumeOffset/1MB,2)) / $([math]::Round($knownSize/1MB,2)) MB" -ForegroundColor Yellow }
        }

        $request = [System.Net.FtpWebRequest]::Create($remoteUri)
        $request.Method      = [System.Net.WebRequestMethods+Ftp]::DownloadFile
        $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
        $request.UseBinary   = $true; $request.UsePassive = $true; $request.Timeout = 60000

        $resumeSupported = $false
        if ($resumeOffset -gt 0) {
            try { $request.ContentOffset = $resumeOffset; $resumeSupported = $true } catch { $resumeSupported = $false }
        }

        $response = $null
        try { $response = $request.GetResponse() } catch {
            if ($resumeSupported) {
                if (Test-Path $localPath) { Remove-Item $localPath -Force -ErrorAction SilentlyContinue }
                $resumeOffset = 0
                $request = [System.Net.FtpWebRequest]::Create($remoteUri)
                $request.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
                $request.Credentials = New-Object System.Net.NetworkCredential($ftpUser, $ftpPassword)
                $request.UseBinary = $true; $request.UsePassive = $true; $request.Timeout = 60000
                $response = $request.GetResponse()
            } else { throw }
        }

        $remaining = $response.ContentLength
        $totalBytes = if ($knownSize -gt 0) { $knownSize } else { $resumeOffset + $remaining }
        $ftpStream = $response.GetResponseStream()

        if ($resumeOffset -gt 0 -and $resumeSupported) {
            $localStream = [System.IO.File]::Open($localPath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None)
        } else {
            $localStream = [System.IO.File]::Create($localPath)
            $resumeOffset = 0
        }

        $buffer = New-Object Byte[] $BufferSize
        $bytesRead = $ftpStream.Read($buffer, 0, $BufferSize)
        $downloaded = $resumeOffset
        $lastRender = [DateTime]::MinValue

        $speedSw = $null; $speedStartBytes = 0
        if ($ShowSpeed) { $speedSw = [System.Diagnostics.Stopwatch]::StartNew(); $speedStartBytes = $downloaded }

        while ($bytesRead -gt 0) {
            $localStream.Write($buffer, 0, $bytesRead)
            $downloaded += $bytesRead
            $bytesRead = $ftpStream.Read($buffer, 0, $BufferSize)

            $now = Get-Date
            if (($now - $lastRender).TotalMilliseconds -lt $ProgressEveryMs) { continue }
            $lastRender = $now
            Show-ProgressLine -FileName $fileName -Downloaded $downloaded -Total $totalBytes
        }

        $localStream.Close(); $ftpStream.Close(); $response.Close()

        if ($totalBytes -gt 0) { Show-ProgressLine -FileName $fileName -Downloaded $totalBytes -Total $totalBytes }
        Write-Host ""

        if ($ShowSpeed -and $speedSw) {
            $speedSw.Stop()
            if ($speedSw.Elapsed.TotalSeconds -gt 0) {
                $speedMBs = [math]::Round((($downloaded - $speedStartBytes) / 1MB) / $speedSw.Elapsed.TotalSeconds, 2)
                Write-Host ("  Скорость: {0} MB/s" -f $speedMBs) -ForegroundColor Yellow
            }
        }
        return $true
    } catch {
        Write-Host "`n  Ошибка скачивания: $_" -ForegroundColor Red
        return $false
    }
}

# ============================================================
#  MAIN
# ============================================================

Write-Host "========================================" -ForegroundColor Cyan
Write-Host "FTP-хост:  $ftpHost" -ForegroundColor Yellow
Write-Host "База:      $rootAppPath" -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""

$downloadedCount = 0

foreach ($map in $folderMapping) {
    $remoteFolder = $map.Remote
    $localFolder  = $map.Local
    $networkFolder = $map.Network
    
    $remoteBasePath = "$ftpPath/$remoteFolder"
    $localFolderPath = Join-Path $rootAppPath $localFolder
    $networkFolderPath = Join-Path $networkBasePath $networkFolder

    Write-Host ">> [Скачивание] $remoteFolder -> $localFolder" -ForegroundColor Magenta

    if (!(Test-Path $localFolderPath)) { New-Item -ItemType Directory -Path $localFolderPath -Force | Out-Null }

    # Проверяем актуальность (маркер версии лежит на Z:)
    $foldersWithDates = Get-FtpFoldersWithDates -ftpDirPath $remoteBasePath
    if ($foldersWithDates.Count -eq 0) {
        Write-Host "  В папке $remoteFolder нет подпапок" -ForegroundColor Red
        continue
    }

    $latestFolder = $null; $latestDate = [DateTime]::MinValue
    foreach ($f in $foldersWithDates.Keys) {
        if ($foldersWithDates[$f] -gt $latestDate) { $latestDate = $foldersWithDates[$f]; $latestFolder = $f }
    }
    Write-Host "  Самая новая папка: $latestFolder ($($latestDate.ToString('dd.MM.yyyy HH:mm')))" -ForegroundColor Green

    $versionFile = Get-VersionFile -folderPath $networkFolderPath
    if ($versionFile -and $versionFile.BaseName -eq $latestFolder) {
        Write-Host "  Версия актуальна ($($versionFile.BaseName) = $latestFolder). Пропускаем." -ForegroundColor Green
        continue
    }

    # Ищем архивы в EKP
    $ekpPath = "$remoteBasePath/$latestFolder/EKP"
    $ekpFiles = Get-FtpFileList -ftpDirPath $ekpPath
    $archives = @()
    foreach ($file in $ekpFiles) { if ($file -match "\.rar$") { $archives += $file } }

    if ($archives.Count -eq 0) {
        Write-Host "  В EKP не найдено RAR архивов" -ForegroundColor Red
        continue
    }

    $archiveToDownload = $archives[0]
    $localFilePath = Join-Path $localFolderPath $archiveToDownload
    $downloadUri = "ftp://$ftpHost$ekpPath/$archiveToDownload"
    
    Write-Host "  Скачивание: $archiveToDownload" -ForegroundColor Cyan
    if (Download-FtpFile -remoteUri $downloadUri -localPath $localFilePath) {
        $downloadedCount++
    }
}

Write-Host "`nСкачано архивов: $downloadedCount. Запуск downloader.ps1..." -ForegroundColor Green

# ============================================================
#  Запуск downloader.ps1
# ============================================================

if (Test-Path $downloader) {
    $dlArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$downloader`"")
    $dlProc = Start-Process powershell.exe -ArgumentList $dlArgs -Wait -PassThru -NoNewWindow
    if ($dlProc.ExitCode -ne 0) {
        Write-Host "`ndownloader.ps1 завершился с кодом $($dlProc.ExitCode)." -ForegroundColor Red
        Read-Host "ENTER для закрытия"
        exit $dlProc.ExitCode
    }
} else {
    Write-Host "ОШИБКА: downloader.ps1 не найден" -ForegroundColor Red
}

Read-Host "ENTER для закрытия"

# ============================================================
#  downloader.ps1 — установка MSI из подпапок и архивация на Z:
# ============================================================

# --- Проверка прав администратора ---
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
    Write-Host "ОШИБКА: скрипт запущен без прав администратора!" -ForegroundColor Red
    Start-Sleep -Seconds 5
    exit 1
}

Write-Host "=== УСТАНОВКА ПО ИЗ ПОДПАПОК ===" -ForegroundColor Cyan
Write-Host ""

# --- Пути ---
$rootAppPath     = "C:\Users\n4200-01-031\Desktop\App2"
$networkBasePath = "Z:\Documents"
$workBase        = Join-Path $env:TEMP ("downloader_" + [guid]::NewGuid().ToString("N"))

# --- WinRAR ---
$winRarPath = "C:\Program Files\WinRAR\WinRAR.exe"
if (!(Test-Path $winRarPath)) { $winRarPath = "C:\Program Files (x86)\WinRAR\WinRAR.exe" }
if (!(Test-Path $winRarPath)) {
    Write-Host "ОШИБКА: WinRAR не найден!" -ForegroundColor Red
    Start-Sleep -Seconds 5
    exit 1
}

# --- Список программ ---
$programs = @(
    @{ LocalFolder = "KPE";   NetworkFolder = "KPE";   Name = "Клиент АИС Налог-3 Пром версия";     InstallFolder = "Ais3Prom" }
    @{ LocalFolder = "PPK";   NetworkFolder = "PPK";   Name = "Клиент АИС Налог-3 ПредПром версия"; InstallFolder = "Ais3PredProm" }
    @{ LocalFolder = "KOE";   NetworkFolder = "KOE";   Name = "Клиент АИС Налог-3 ОЭ версия";       InstallFolder = "Ais3FB3OE" }
    @{ LocalFolder = "KOEIZ"; NetworkFolder = "KOEIZ"; Name = "Клиент АИС Налог-3 ОЭ ИЗ версия";    InstallFolder = "Ais3OEIZ" }
)

$versionPattern = '^\d+_\d+_\d+_\d+$'

# ============================================================
#  Вспомогательные функции
# ============================================================

function Extract-RarArchive {
    param([string]$archivePath, [string]$destinationPath)
    Write-Host "  Распаковка: $(Split-Path $archivePath -Leaf)" -ForegroundColor Cyan
    if (!(Test-Path $destinationPath)) { New-Item -ItemType Directory -Path $destinationPath -Force | Out-Null }
    try {
        $arguments = "x -o+ -y `"$archivePath`" `"$destinationPath\`""
        $process = Start-Process -FilePath $winRarPath -ArgumentList $arguments -Wait -PassThru -NoNewWindow
        if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 1) {
            Write-Host "  Распаковка успешна!" -ForegroundColor Green
            return $true
        } else {
            Write-Host "  Ошибка распаковки! Код: $($process.ExitCode)" -ForegroundColor Red
            return $false
        }
    } catch {
        Write-Host "  Ошибка: $($_.Exception.Message)" -ForegroundColor Red
        return $false
    }
}

function Compress-FolderToRar {
    param([string]$sourceFolder, [string]$rarPath)
    Write-Host "  Архивация: $(Split-Path $sourceFolder -Leaf) -> $(Split-Path $rarPath -Leaf)" -ForegroundColor Cyan
    try {
        $parent = Split-Path -Parent $rarPath
        if (!(Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null }
        if (Test-Path $rarPath) { Remove-Item -Path $rarPath -Force -ErrorAction SilentlyContinue }

        # -ep1 сохраняет только верхнюю папку (например, Ais3Prom)
        $arguments = "a -r -ep1 -o+ -y `"$rarPath`" `"$sourceFolder\`""
        $process = Start-Process -FilePath $winRarPath -ArgumentList $arguments -Wait -PassThru -NoNewWindow
        if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 1) {
            $sizeMb = [math]::Round((Get-Item $rarPath).Length / 1MB, 2)
            Write-Host "  Архив создан ($sizeMb MB)" -ForegroundColor Green
            return $true
        } else {
            Write-Host "  Ошибка архивации! Код: $($process.ExitCode)" -ForegroundColor Red
            return $false
        }
    } catch {
        Write-Host "  Ошибка: $($_.Exception.Message)" -ForegroundColor Red
        return $false
    }
}

function Find-InstalledProgram {
    param([string]$exactName)
    $foundPackage = Get-Package -Name $exactName -ErrorAction SilentlyContinue | Select-Object -First 1
    if (-not $foundPackage) {
        $foundPackage = Get-Package -Name "*$exactName*" -ErrorAction SilentlyContinue | Select-Object -First 1
    }
    if ($foundPackage) { Write-Host "  Найдена старая версия: $($foundPackage.Name) (v$($foundPackage.Version))" -ForegroundColor Gray }
    return $foundPackage
}

function Uninstall-Program {
    param($package)
    Write-Host "  Удаление: $($package.Name)" -ForegroundColor Yellow
    try {
        Uninstall-Package -InputObject $package -Force -ErrorAction Stop
        Write-Host "  Удаление успешно" -ForegroundColor Green
        Start-Sleep -Seconds 3
    } catch {
        Write-Host "  Ошибка удаления: $($_.Exception.Message)" -ForegroundColor Red
    }
}

function Install-Msi {
    param([string]$msiPath)
    $processToKill = "CommonComponents.Catalog.IndexationUtility"
    Write-Host "  Установка MSI: $(Split-Path $msiPath -Leaf)" -ForegroundColor Cyan
    $installProcess = Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$msiPath`" /quiet" -PassThru -WindowStyle Hidden

    $targetProcess = $null; $timeout = 1000; $elapsed = 0
    while ($elapsed -lt $timeout) {
        $targetProcess = Get-Process -Name $processToKill -ErrorAction SilentlyContinue
        if ($targetProcess) {
            Stop-Process -Id $targetProcess.Id -Force
            Write-Host "  Процесс $processToKill убит." -ForegroundColor Gray
            break
        }
        Start-Sleep -Seconds 2; $elapsed += 2
    }
    Wait-Process -Id $installProcess.Id -ErrorAction SilentlyContinue
    Write-Host "  Установка завершена." -ForegroundColor Green
    Start-Sleep -Seconds 3
}

function Find-ProgramInstallFolder {
    param([string]$installFolder)
    $programsPath = "C:\Program Files (x86)"
    if (!(Test-Path $programsPath)) { return $null }
    $found = Get-ChildItem $programsPath -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq $installFolder } | Select-Object -First 1
    if (-not $found) {
        $found = Get-ChildItem $programsPath -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "*$installFolder*" } | Select-Object -First 1
    }
    if ($found) { return $found.FullName }
    return $null
}

function Invoke-IndexationUtility {
    param([string]$installFolderPath)
    $exeToRun = "CommonComponents.Catalog.IndexationUtility.exe"
    $exePath = Join-Path $installFolderPath "Client" | Join-Path -ChildPath $exeToRun
    $exePathRoot = Join-Path $installFolderPath $exeToRun
    $finalExe = $null
    if (Test-Path $exePath) { $finalExe = $exePath }
    elseif (Test-Path $exePathRoot) { $finalExe = $exePathRoot }

    if ($finalExe) {
        Write-Host "  Запуск утилиты индексации..." -ForegroundColor Cyan
        Start-Process -FilePath $finalExe -Wait -PassThru -WindowStyle Hidden -ErrorAction SilentlyContinue | Out-Null
    }
}

function Get-VersionFile {
    param([string]$folderPath)
    if (!(Test-Path $folderPath)) { return $null }
    $files = Get-ChildItem -Path $folderPath -File -Force -ErrorAction SilentlyContinue
    foreach ($f in $files) { if ($f.BaseName -match $versionPattern) { return $f } }
    return $null
}

function Update-VersionFile {
    param([string]$folderPath, [string]$latestFolder, [System.IO.FileInfo]$versionFile)
    try {
        if ($versionFile) {
            if ($versionFile.BaseName -eq $latestFolder) { return }
            $newName = "$latestFolder$($versionFile.Extension)"
            Rename-Item -Path $versionFile.FullName -NewName $newName -Force
            Write-Host "  Файл версии переименован: $newName" -ForegroundColor Green
        } else {
            $newPath = Join-Path $folderPath "$latestFolder.txt"
            New-Item -ItemType File -Path $newPath -Force | Out-Null
            Write-Host "  Создан файл версии: $latestFolder.txt" -ForegroundColor Green
        }
    } catch { Write-Host "  Ошибка обновления маркера: $_" -ForegroundColor Red }
}

# ============================================================
#  MAIN
# ============================================================

$successCount = 0

foreach ($prog in $programs) {
    $localFolder = $prog.LocalFolder
    $networkFolder = $prog.NetworkFolder
    $programName = $prog.Name
    $installFolder = $prog.InstallFolder

    $sourceFolder = Join-Path $rootAppPath $localFolder
    $targetNetworkFolder = Join-Path $networkBasePath $networkFolder

    Write-Host "`n=====================================================" -ForegroundColor Magenta
    Write-Host ">>> Программа: $programName (папка $localFolder)" -ForegroundColor Magenta
    Write-Host "=====================================================" -ForegroundColor Magenta

    if (-not (Test-Path $sourceFolder)) {
        Write-Host "  Папка $sourceFolder не найдена. Пропускаем." -ForegroundColor Yellow
        continue
    }

    # Ищем .rar в локальной папке
    $rarFiles = Get-ChildItem -Path $sourceFolder -Filter "*.rar" -File -ErrorAction SilentlyContinue
    if ($rarFiles.Count -eq 0) {
        Write-Host "  В папке нет .rar архивов. Пропускаем." -ForegroundColor Yellow
        continue
    }
    $rarToUse = $rarFiles | Select-Object -First 1
    Write-Host "  Найден архив: $($rarToUse.Name)" -ForegroundColor Green

    # 1. Распаковка
    $extractDir = Join-Path $workBase $localFolder
    if (-not (Extract-RarArchive -archivePath $rarToUse.FullName -destinationPath $extractDir)) {
        continue
    }

    # 2. Поиск MSI
    $msiFile = Get-ChildItem -Path $extractDir -Filter "*.msi" -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
    if (-not $msiFile) {
        Write-Host "  MSI не найден! Пропускаем." -ForegroundColor Red
        Remove-Item -Path $extractDir -Recurse -Force -ErrorAction SilentlyContinue
        continue
    }

    # 3. Удаление старой версии
    $existingPackage = Find-InstalledProgram -exactName $programName
    if ($existingPackage) { Uninstall-Program -package $existingPackage }

    # 4. Установка
    Install-Msi -msiPath $msiFile.FullName

    # 5. Запуск утилиты индексации
    $installedFolder = Find-ProgramInstallFolder -installFolder $installFolder
    if ($installedFolder) {
        Invoke-IndexationUtility -installFolderPath $installedFolder
    } else {
        Write-Host "  Папка установки '$installFolder' не найдена!" -ForegroundColor Red
        Remove-Item -Path $extractDir -Recurse -Force -ErrorAction SilentlyContinue
        continue
    }

    # 6. Архивация установленной папки на Z:
    if (!(Test-Path $targetNetworkFolder)) { New-Item -ItemType Directory -Path $targetNetworkFolder -Force | Out-Null }

    # Имя архива: например, KPE.rar
    $finalRarName = "$localFolder.rar"
    $finalRarPath = Join-Path $targetNetworkFolder $finalRarName

    if (Compress-FolderToRar -sourceFolder $installedFolder -rarPath $finalRarPath) {
        # 7. Обновление маркера версии (берем версию из имени исходного архива)
        if ($rarToUse.Name -match '(\d+[\._]\d+[\._]\d+[\._]\d+)') {
            $ver = $matches[1] -replace '\.', '_'
            $vFile = Get-VersionFile -folderPath $targetNetworkFolder
            Update-VersionFile -folderPath $targetNetworkFolder -latestFolder $ver -versionFile $vFile
        }
        $successCount++
    }

    # 8. Очистка временной папки
    Remove-Item -Path $extractDir -Recurse -Force -ErrorAction SilentlyContinue
    Write-Host "  Временные файлы удалены." -ForegroundColor Gray
}

if (Test-Path $workBase) { Remove-Item -Path $workBase -Recurse -Force -ErrorAction SilentlyContinue }

Write-Host "`n=====================================================" -ForegroundColor Green
Write-Host "УСПЕШНО ОБРАБОТАНО: $successCount из $($programs.Count)" -ForegroundColor Green
Write-Host "=====================================================" -ForegroundColor Green

exit 0