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


1)Создаем на локалке папку App туда кидаем скрипт downloader.ps1 и watcher.ps1 также создаем папку EKP.
2)Создаем папку App на VDI тука кидаем скрипт ftp.ps1
3)На сетевой папке создаем папки KPE, PPK, KOE, KOEIZ

-- Поправить пути к файлам под себя


Если все нормально можно сделать автозапуск через шедулер

schtasks /Create /TN "MyPowerShellScript" /TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"C:\Users\vboxuser\Desktop\123.ps1"" /SC DAILY /ST 00:22 /RU "vboxuser" /RP "1234" /RL HIGHEST

Включить в настройках в Параметрах - > Немедленно запускать задачу, если пропущен плановый запуск


downloader.ps1
# --- 1. Проверка прав администратора ---
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

if (-not $isAdmin) {
    Write-Host "ОШИБКА: скрипт запущен без прав администратора!" -ForegroundColor Red
    Write-Host "Проверьте, что watcher запущен от имени администратора." -ForegroundColor Red
    Start-Sleep -Seconds 5
    exit 1
}

Write-Host "=== АВТОМАТИЧЕСКАЯ УСТАНОВКА/ПЕРЕУСТАНОВКА ПРОГРАММ ===" -ForegroundColor Cyan
Write-Host "Запущено от имени администратора" -ForegroundColor Green

# --- 2. Пути ---
# Папка-источник, где лежат KPE, PPK, KOE, KOE IZ
$rootFolder = "D:\win10\Share\App"

# Рабочая папка для распаковки
$workBase  = "D:\services\Рабочий стол\App2"
$ekpFolder = Join-Path $workBase "EKP"

# Путь к 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
    Write-Host "Укажите правильный путь в переменной `$winRarPath" -ForegroundColor Red
    Start-Sleep -Seconds 10
    exit 1
}

Write-Host "Папка-источник: $rootFolder" -ForegroundColor Cyan
Write-Host "Рабочая папка: $workBase" -ForegroundColor Cyan
Write-Host "EKP: $ekpFolder" -ForegroundColor Cyan
Write-Host "WinRAR: $winRarPath" -ForegroundColor Cyan
Write-Host ""

# --- 3. Список программ ---
$programs = @(
    @{ Folder = "KPE";    Name = "АИС Пром КПЕ" }
    @{ Folder = "PPK";    Name = "АИС Пром ППК" }
    @{ Folder = "KOE";    Name = "АИС Пром КОЕ" }
    @{ Folder = "KOEIZ"; Name = "АИС Пром КОЕ ИЗ" }
)

# Поиск установленной программы
function Find-InstalledProgram {
    param([string]$exactName)

    Write-Host "Поиск программы: $exactName" -ForegroundColor Cyan

    $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 Green
    } else {
        Write-Host "Программа не найдена" -ForegroundColor Gray
    }

    return $foundPackage
}

# Удаление программы
function Uninstall-Program {
    param(
        [string]$displayName,
        $package
    )

    Write-Host "Удаление программы: $displayName" -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 Clear-FolderFull {
    param([string]$FolderPath)

    if (-not (Test-Path $FolderPath)) {
        Write-Host "Папка для очистки не найдена: $FolderPath" -ForegroundColor Yellow
        return
    }

    Write-Host "Полная очистка папки: $FolderPath" -ForegroundColor Cyan

    try {
        Get-ChildItem -Path $FolderPath -Force -ErrorAction SilentlyContinue |
            ForEach-Object {
                Write-Host "   Удаление: $($_.FullName)" -ForegroundColor Gray
                Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
            }
        Write-Host "Очистка завершена" -ForegroundColor Green
    }
    catch {
        Write-Host "Ошибка при очистке: $($_.Exception.Message)" -ForegroundColor Red
    }
}

# Очистка папки, кроме .txt
function Clear-FolderExceptTxt {
    param([string]$FolderPath)

    if (-not (Test-Path $FolderPath)) {
        Write-Host "Папка для очистки не найдена: $FolderPath" -ForegroundColor Yellow
        return
    }

    Write-Host "Очистка папки (кроме .txt): $FolderPath" -ForegroundColor Cyan

    try {
        Get-ChildItem -Path $FolderPath -Directory -Recurse -ErrorAction SilentlyContinue |
            ForEach-Object {
                Write-Host "   Удаление папки: $($_.FullName)" -ForegroundColor Gray
                Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
            }

        Get-ChildItem -Path $FolderPath -File -Recurse -ErrorAction SilentlyContinue |
            Where-Object { $_.Extension -ne ".txt" } |
            ForEach-Object {
                Write-Host "   Удаление файла: $($_.Name)" -ForegroundColor Gray
                Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue
            }

        Write-Host "Очистка завершена (оставлены только .txt)" -ForegroundColor Green
    }
    catch {
        Write-Host "Ошибка при очистке: $($_.Exception.Message)" -ForegroundColor Red
    }
}

# Распаковка RAR через WinRAR
function Extract-RarArchive {
    param(
        [string]$archivePath,
        [string]$destinationPath
    )

    Write-Host "Распаковка: $archivePath" -ForegroundColor Cyan
    Write-Host "     в:    $destinationPath" -ForegroundColor Gray

    try {
        $arguments = "x -o+ -y `"$archivePath`" `"$destinationPath\`""
        $process = Start-Process -FilePath $winRarPath -ArgumentList $arguments -Wait -PassThru -NoNewWindow

        if ($process.ExitCode -eq 0) {
            Write-Host "Распаковка успешно завершена!" -ForegroundColor Green
            return $true
        } else {
            Write-Host "Ошибка распаковки! Код: $($process.ExitCode)" -ForegroundColor Red
            return $false
        }
    }
    catch {
        Write-Host "Ошибка при распаковке: $($_.Exception.Message)" -ForegroundColor Red
        return $false
    }
}

# Установка MSI
function Install-Msi {
    param([string]$msiPath)

    Write-Host "Установка MSI: $msiPath" -ForegroundColor Cyan

    $installProcess = Start-Process -FilePath "msiexec.exe" `
        -ArgumentList "/i `"$msiPath`" /quiet" `
        -PassThru `
        -WindowStyle Hidden

    Write-Host "Установка запущена (PID: $($installProcess.Id))" -ForegroundColor Green

    Write-Host "Ожидание завершения установки..." -ForegroundColor Cyan
    Wait-Process -Id $installProcess.Id -ErrorAction SilentlyContinue

    Write-Host "Установка MSI завершена" -ForegroundColor Green
}

# Поиск и запуск утилиты индексации
function Invoke-IndexationUtility {
    param([string]$msiPath)

    $exeToRun = "CommonComponents.Catalog.IndexationUtility.exe"

    Write-Host "Поиск папки с установленной программой..." -ForegroundColor Cyan

    $msiBaseName = [System.IO.Path]::GetFileNameWithoutExtension($msiPath)
    Write-Host "Имя установщика: $msiBaseName" -ForegroundColor Gray

    $programFolder = Get-ChildItem "C:\Program Files", "C:\Program Files (x86)" -Directory -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -eq $msiBaseName } |
        Select-Object -First 1

    if (-not $programFolder) {
        Write-Host "Точное совпадение не найдено, ищем по частичному..." -ForegroundColor Yellow
        $programFolder = Get-ChildItem "C:\Program Files", "C:\Program Files (x86)" -Directory -ErrorAction SilentlyContinue |
            Where-Object { $_.Name -like "*$msiBaseName*" } |
            Select-Object -First 1
    }

    if (-not $programFolder) {
        Write-Host "Внимание: Папка с программой '$msiBaseName' не найдена" -ForegroundColor Yellow
        return
    }

    Write-Host "Папка программы найдена: $($programFolder.FullName)" -ForegroundColor Green

    $exePath     = Join-Path $programFolder.FullName "Client" | Join-Path -ChildPath $exeToRun
    $exePathRoot = Join-Path $programFolder.FullName $exeToRun

    $finalExe = $null
    if (Test-Path $exePath) {
        $finalExe = $exePath
    } elseif (Test-Path $exePathRoot) {
        $finalExe = $exePathRoot
    }

    if (-not $finalExe) {
        Write-Host "Внимание: Утилита $exeToRun не найдена" -ForegroundColor Red
        return
    }

    Write-Host "Запуск утилиты: $finalExe" -ForegroundColor Cyan
    try {
        $utilityProcess = Start-Process -FilePath $finalExe -Wait -PassThru -WindowStyle Hidden -ErrorAction Stop
        Write-Host "Утилита завершила работу (ExitCode: $($utilityProcess.ExitCode))" -ForegroundColor Green
    }
    catch {
        Write-Host "ОШИБКА запуска утилиты: $($_.Exception.Message)" -ForegroundColor Red
    }
}

function Process-Program {
    param(
        [string]$folderName,
        [string]$programName,
        [string]$sourceFolder
    )

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

    # --- 1. Проверяем папку-источник ---
    if (-not (Test-Path $sourceFolder)) {
        Write-Host "Папка-источник не найдена: $sourceFolder" -ForegroundColor Red
        return $false
    }

    # --- 2. Ищем .rar в папке-источнике ---
    $rarFiles = Get-ChildItem -Path $sourceFolder -Filter "*.rar" -File -Recurse -ErrorAction SilentlyContinue
    if ($rarFiles.Count -eq 0) {
        Write-Host "В папке $folderName не найдено .rar архивов" -ForegroundColor Red
        return $false
    }

    $rarToUse = $rarFiles | Select-Object -First 1
    Write-Host "Найден архив: $($rarToUse.Name)" -ForegroundColor Green

    # --- 3. Готовим EKP ---
    if (Test-Path $ekpFolder) {
        Clear-FolderFull -FolderPath $ekpFolder
    } else {
        New-Item -ItemType Directory -Path $ekpFolder -Force | Out-Null
        Write-Host "Создана папка EKP: $ekpFolder" -ForegroundColor Gray
    }

    # --- 4. Копируем .rar в EKP ---
    $localRarPath = Join-Path $ekpFolder $rarToUse.Name
    Write-Host "Копирование архива: $($rarToUse.FullName) -> $localRarPath" -ForegroundColor Cyan
    try {
        Copy-Item -Path $rarToUse.FullName -Destination $localRarPath -Force -ErrorAction Stop
        Write-Host "Архив скопирован" -ForegroundColor Green
    }
    catch {
        Write-Host "ОШИБКА копирования архива: $($_.Exception.Message)" -ForegroundColor Red
        return $false
    }

    # --- 5. Распаковываем ---
    if (-not (Extract-RarArchive -archivePath $localRarPath -destinationPath $ekpFolder)) {
        Write-Host "Распаковка не удалась, пропускаем $programName" -ForegroundColor Red
        Clear-FolderFull -FolderPath $ekpFolder
        return $false
    }

    # --- 6. Ищем .msi в распакованном ---
    $msiFile = Get-ChildItem -Path $ekpFolder -Filter "*.msi" -File -Recurse -ErrorAction SilentlyContinue |
        Select-Object -First 1

    if (-not $msiFile) {
        Write-Host "В распакованном архиве не найден .msi" -ForegroundColor Red
        Clear-FolderFull -FolderPath $ekpFolder
        return $false
    }

    $msiPath = $msiFile.FullName
    Write-Host "Найден установщик: $($msiFile.Name)" -ForegroundColor Green
    Write-Host "Путь: $msiPath" -ForegroundColor Gray

    # --- 7. Удаляем старую версию ---
    $existingPackage = Find-InstalledProgram -exactName $programName
    if ($existingPackage) {
        Write-Host "Программа найдена. Удаляем..." -ForegroundColor Yellow
        Uninstall-Program -displayName $existingPackage.Name -package $existingPackage
    } else {
        Write-Host "Программа не найдена. Выполняется установка..." -ForegroundColor Green
    }

    # --- 8. Установка MSI ---
    Install-Msi -msiPath $msiPath

    Start-Sleep -Seconds 3

    # --- 9. Запуск утилиты индексации ---
    Invoke-IndexationUtility -msiPath $msiPath

    # --- 10. Чистим EKP ---
    Clear-FolderFull -FolderPath $ekpFolder

    # --- 11. Чистим папку-источник (кроме .txt) ---
    Clear-FolderExceptTxt -FolderPath $sourceFolder

    Write-Host "Программа '$programName' успешно установлена!" -ForegroundColor Green
    Write-Host "---------------------------------------------------" -ForegroundColor Gray
    return $true
}

Write-Host ""
Write-Host "=====================================================" -ForegroundColor Cyan
Write-Host "НАЧАЛО ОБРАБОТКИ ПАПОК" -ForegroundColor Cyan
Write-Host "=====================================================" -ForegroundColor Cyan

$successCount = 0
$totalCount   = $programs.Count

foreach ($program in $programs) {
    $folderName   = $program.Folder
    $programName  = $program.Name
    $sourceFolder = Join-Path -Path $rootFolder -ChildPath $folderName

    if (Process-Program -folderName $folderName -programName $programName -sourceFolder $sourceFolder) {
        $successCount++
    }
}

Write-Host ""
Write-Host "=====================================================" -ForegroundColor Green
Write-Host "ОБРАБОТКА ВСЕХ ПАПОК ЗАВЕРШЕНА" -ForegroundColor Green
Write-Host "Успешно: $successCount из $totalCount" -ForegroundColor Green
Write-Host "=====================================================" -ForegroundColor Green

Read-Host "123"
#Start-Sleep -Seconds 10
#exit 0

Конец downloader.ps1


watcher.ps1
if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Host "Требуются права администратора. Запуск от имени администратора..." -ForegroundColor Yellow
    Start-Process PowerShell -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`""
    exit
}

# Путь к папке, где создается success.flag (должен совпадать с путем в основном скрипте)
$watcherPath = "D:\win10\Share\App"  # или "Z:\App"

# Создаем наблюдатель
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $watcherPath
$watcher.Filter = "success.flag"
$watcher.EnableRaisingEvents = $true
$watcher.IncludeSubdirectories = $false

# Действие при создании файла
$action = {
    $path       = $Event.SourceEventArgs.FullPath
    $changeType = $Event.SourceEventArgs.ChangeType
    $name       = $Event.SourceEventArgs.Name

    Write-Host "========================================" -ForegroundColor Cyan
    Write-Host "СИГНАЛ ПОЛУЧЕН!" -ForegroundColor Green
    Write-Host "Файл: $name" -ForegroundColor Yellow
    Write-Host "Путь: $path" -ForegroundColor Yellow
    Write-Host "Тип события: $changeType" -ForegroundColor Yellow
    Write-Host "Время: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Yellow

    # Небольшая задержка, чтобы файл гарантированно дописался
    Start-Sleep -Milliseconds 500

    if (Test-Path $path) {
        # Показываем содержимое (если нужно)
        try {
            $content = Get-Content $path -Raw -ErrorAction Stop
            Write-Host "Содержимое файла:" -ForegroundColor Gray
            Write-Host $content -ForegroundColor Gray
        } catch {
            Write-Host "Не удалось прочитать содержимое: $($_.Exception.Message)" -ForegroundColor Red
        }

        $scriptPath = "D:\services\Рабочий стол\App2\123.ps1"

        if (Test-Path $scriptPath) {
            Write-Host "`nЗапуск скрипта: $scriptPath" -ForegroundColor Cyan

            $exitCode = -1
            try {
                $proc = Start-Process powershell.exe -ArgumentList @(
                    "-NoProfile",
                    "-ExecutionPolicy", "Bypass",
                    "-File", "`"$scriptPath`""
                ) -Wait -PassThru -ErrorAction Stop

                $exitCode = $proc.ExitCode
                Write-Host "Скрипт 123.ps1 завершён с кодом: $exitCode" -ForegroundColor $(if ($exitCode -eq 0) { "Green" } else { "Red" })
            }
            catch {
                Write-Host "ОШИБКА запуска 123.ps1: $($_.Exception.Message)" -ForegroundColor Red
                $exitCode = -1
            }

            if ($exitCode -eq 0) {
                Remove-Item $path -Force
                Write-Host "Обработка успешна, файл сигнала удалён" -ForegroundColor Green
            } else {
                Write-Host "Скрипт завершился с кодом $exitCode, файл оставлен" -ForegroundColor Red
            }
        } else {
            Write-Host "СКРИПТ НЕ НАЙДЕН: $scriptPath" -ForegroundColor Red
        }
    }
    else {
        Write-Host "ВНИМАНИЕ: Файл не найден!" -ForegroundColor Red
    }

    Write-Host "========================================" -ForegroundColor Cyan
    Write-Host ""
}

# Регистрируем событие
$eventJob = Register-ObjectEvent -InputObject $watcher -EventName "Created" -Action $action

Write-Host "Наблюдатель запущен!" -ForegroundColor Cyan
Write-Host "Отслеживается папка: $watcherPath" -ForegroundColor Yellow
Write-Host "Ожидание файла: success.flag" -ForegroundColor Yellow
Write-Host ""
Write-Host "Для остановки нажмите Ctrl+C" -ForegroundColor Red
Write-Host ""

# Держим скрипт запущенным
try {
    while ($true) {
        Start-Sleep -Seconds 1
    }
}
finally {
    # Очистка при завершении
    Unregister-Event -SubscriptionId $eventJob.Id
    $watcher.Dispose()
    Write-Host "`nНаблюдатель остановлен." -ForegroundColor Red
}

Конец watcher.ps1

Скрипт ftp.ps1
# --- Настройки FTP ---
$ftpHost     = "10.0.2.15"
$ftpPath     = "/AisNalog3"
$ftpUser     = "anonymous"
$ftpPassword = ""

# --- Временная локальная папка (только для скачивания) ---
$localBasePath = "C:\Users\vboxuser\Desktop\App"

# --- Сетевой путь (куда перекидываем и где маркеры) ---
$networkBasePath = "Z:\App"

# --- Файл-флаг успешного завершения ---
$flagFile = Join-Path $networkBasePath "success.flag"

# --- Соответствие удалённых папок локальным ---
$folderMapping = @{
    "Prom"     = "KPE"
    "PredProm" = "PPK"
    "OE"       = "KOE"
    "OEIZ"     = "KOEIZ"
}

# --- Шаблон имени файла-маркера версии ---
$versionPattern = '^\d+_\d+_\d+_\d+$'

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]$ftpPath)

    $folders = @{}
    try {
        $uri = "ftp://$ftpHost$ftpPath/"
        $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 {
                        try {
                            $dateTime = [DateTime]::ParseExact("$month $day $currentYear $time", "MMM dd yyyy HH:mm", [System.Globalization.CultureInfo]::InvariantCulture)
                        } catch { $dateTime = $null }
                    }
                }
            }
            elseif ($line -match "<DIR>\s+(\S+)$") {
                $folderName  = $matches[1]
                $isDirectory = $true
                if ($line -match "(\d{2}-\d{2}-\d{2})\s+(\d{2}:\d{2}[AP]M)") {
                    try {
                        $dateTime = [DateTime]::ParseExact("$($matches[1]) $($matches[2])", "MM-dd-yy hh:mmtt", [System.Globalization.CultureInfo]::InvariantCulture)
                    } catch { $dateTime = $null }
                }
            }

            if ($isDirectory -and $folderName -and $folderName -ne "." -and $folderName -ne "..") {
                if ($dateTime) { $folders[$folderName] = $dateTime }
                else           { $folders[$folderName] = [DateTime]::MinValue }
            }
        }
        return $folders
    }
    catch {
        Write-Host "  Ошибка при получении списка папок: $_" -ForegroundColor Red
        return @{}
    }
}

function Get-FtpFileList {
    param([string]$ftpPath)

    $items = @()
    try {
        $uri = "ftp://$ftpHost$ftpPath/"
        $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 {
        Write-Host "  Ошибка при получении списка файлов: $_" -ForegroundColor Red
        return @()
    }
}

function Get-FtpFileSize {
    param(
        [string]$remoteUri,
        [string]$ftpDirPath,
        [string]$fileName
    )

    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.EnableSsl   = $false
        $request.KeepAlive   = $false
        $request.Timeout     = 30000

        $response = $request.GetResponse()
        $size = $response.ContentLength
        $response.Close()

        if ($size -gt 0) { return [int64]$size }
    }
    catch {
    }

    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) {
            if ($line -match ("\s" + [regex]::Escape($fileName) + "\s*$")) {
                if ($line -match "^\d{2}-\d{2}-\d{2}\s+\d{2}:\d{2}[AP]M\s+(\d+)\s+") {
                    return [int64]$matches[1]
                }
                if ($line -match "^-.*\s+(\d+)\s+\w{3}\s+\d{1,2}\s+(\d{2}:\d{2}|\d{4})\s+") {
                    return [int64]$matches[1]
                }
            }
        }
    }
    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 Update-VersionFile {
    param(
        [string]$folderPath,
        [string]$latestFolder,
        [System.IO.FileInfo]$versionFile
    )

    try {
        if ($versionFile) {
            if ($versionFile.BaseName -eq $latestFolder) {
                return
            }

            $extension = $versionFile.Extension
            if ([string]::IsNullOrEmpty($extension)) { $extension = ".txt" }

            $newName = "$latestFolder$extension"
            Rename-Item -Path $versionFile.FullName -NewName $newName -Force
            Write-Host "  Файл версии переименован: $($versionFile.Name) -> $newName" -ForegroundColor Green
        }
        else {
            $newName = "$latestFolder.txt"
            $newPath = Join-Path $folderPath $newName
            New-Item -ItemType File -Path $newPath -Force | Out-Null
            Write-Host "  Создан файл версии: $newName" -ForegroundColor Green
        }
    }
    catch {
        Write-Host "  Ошибка при обновлении файла версии: $_" -ForegroundColor Red
    }
}

function Clear-NetworkFolder {
    param(
        [string]$folderPath,
        [System.IO.FileInfo]$versionFile
    )

    if (!(Test-Path $folderPath)) { return }

    $versionFileName = if ($versionFile) { $versionFile.Name } else { $null }

    $files = Get-ChildItem -Path $folderPath -File -Force -ErrorAction SilentlyContinue
    foreach ($f in $files) {
        if ($versionFileName -and $f.Name -eq $versionFileName) { continue }

        try {
            Remove-Item -Path $f.FullName -Force -ErrorAction Stop
            Write-Host "  Удалён старый файл: $($f.Name)" -ForegroundColor Gray
        }
        catch {
            Write-Host "  Не удалось удалить $($f.Name): $($_.Exception.Message)" -ForegroundColor Yellow
        }
    }
}

function Download-FtpFile {
    param(
        [string]$remoteUri,
        [string]$localPath,
        [string]$ftpDirPath = "",
        [string]$fileName   = ""
    )

    try {
        if (-not $fileName) { $fileName = Split-Path $localPath -Leaf }

        $knownSize = Get-FtpFileSize -remoteUri $remoteUri -ftpDirPath $ftpDirPath -fileName $fileName

        $resumeOffset = 0
        if (Test-Path $localPath) {
            $resumeOffset = (Get-Item $localPath).Length

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

            if ($resumeOffset -gt 0) {
                $mbHave = [math]::Round($resumeOffset / 1MB, 2)
                $mbAll  = if ($knownSize -gt 0) { [math]::Round($knownSize / 1MB, 2) } else { "?" }
                Write-Host "  Найден недокачанный файл: $mbHave / $mbAll 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.EnableSsl   = $false
        $request.Timeout     = 60000

        $resumeSupported = $false
        if ($resumeOffset -gt 0) {
            try {
                $request.ContentOffset = $resumeOffset
                $resumeSupported = $true
            }
            catch {
                Write-Host "  Сервер не поддерживает докачку (REST): $($_.Exception.Message)" -ForegroundColor Yellow
                $resumeSupported = $false
            }
        }

        $response = $null
        try {
            $response = $request.GetResponse()
        }
        catch {
            if ($resumeSupported) {
                Write-Host "  Докачка не удалась, начинаем с нуля..." -ForegroundColor Yellow
                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.EnableSsl   = $false
                $request.Timeout     = 60000

                $response = $request.GetResponse()
            }
            else {
                throw
            }
        }

        $remaining = $response.ContentLength
        if ($remaining -le 0) { $remaining = $knownSize - $resumeOffset }

        $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
        }

        $bufferSize  = 4MB
        $buffer      = New-Object Byte[] $bufferSize
        $bytesRead   = $ftpStream.Read($buffer, 0, $bufferSize)
        $downloaded  = $resumeOffset

        $lastRender = [DateTime]::MinValue
        $lastMb     = -1
        $width      = 30

        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 200) { continue }
            $lastRender = $now

            if ($totalBytes -gt 0) {
                Show-ProgressLine -FileName $fileName -Downloaded $downloaded -Total $totalBytes -Width $width
            }
            else {
                $mbNow = [math]::Floor($downloaded / 1MB)
                if ($mbNow -eq $lastMb) { continue }
                $lastMb = $mbNow
                Show-ProgressLine -FileName $fileName -Downloaded $downloaded -Total 0 -Width $width
            }
        }

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

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

        $fileSize = [math]::Round($downloaded / 1MB, 2)
        if ($resumeOffset -gt 0) {
            Write-Host ("  Файл докачан: {0} (Размер: {1} MB)" -f $localPath, $fileSize) -ForegroundColor Green
        } else {
            Write-Host ("  Файл скачан: {0} (Размер: {1} MB)" -f $localPath, $fileSize) -ForegroundColor Green
        }
        return $true
    }
    catch {
        Write-Host ""
        Write-Host "  Ошибка при скачивании: $_" -ForegroundColor Red
        return $false
    }
}

function Copy-ToNetwork {
    param(
        [string]$sourceFile,
        [string]$networkFolder
    )

    try {
        if (!(Test-Path $networkFolder)) {
            New-Item -ItemType Directory -Path $networkFolder -Force | Out-Null
            Write-Host "  Создана сетевая папка: $networkFolder" -ForegroundColor Gray
        }

        $fileName = Split-Path $sourceFile -Leaf
        $destPath = Join-Path $networkFolder $fileName

        Write-Host "  Копирование на сеть: $sourceFile -> $destPath" -ForegroundColor Cyan

        $sourceStream = [System.IO.File]::OpenRead($sourceFile)
        $destStream   = [System.IO.File]::Create($destPath)

        $bufferSize  = 8192
        $buffer      = New-Object Byte[] $bufferSize
        $totalBytes  = $sourceStream.Length
        $copied      = 0
        $lastRender  = [DateTime]::MinValue
        $width       = 30

        try {
            while (($bytesRead = $sourceStream.Read($buffer, 0, $bufferSize)) -gt 0) {
                $destStream.Write($buffer, 0, $bytesRead)
                $copied += $bytesRead

                $now = Get-Date
                if (($now - $lastRender).TotalMilliseconds -lt 200) { continue }
                $lastRender = $now

                if ($totalBytes -gt 0) {
                    Show-ProgressLine -FileName $fileName -Downloaded $copied -Total $totalBytes -Width $width
                }
            }
        }
        finally {
            $sourceStream.Close()
            $destStream.Close()
        }

        if ($totalBytes -gt 0) {
            Show-ProgressLine -FileName $fileName -Downloaded $totalBytes -Total $totalBytes -Width $width
        }
        Write-Host ""
        Write-Host "  Файл скопирован на сетевой диск" -ForegroundColor Green
        return $true
    }
    catch {
        Write-Host ""
        Write-Host "  Ошибка копирования на сеть: $($_.Exception.Message)" -ForegroundColor Red
        return $false
    }
}

function Download-ArchivesFromFTP {
    param(
        [string]$remoteFolder,
        [string]$localFolder
    )

    Write-Host "Обработка папки: $remoteFolder" -ForegroundColor Yellow

    $remoteBasePath = "$ftpPath/$remoteFolder"
    $networkFolder  = Join-Path $networkBasePath $localFolder

    if (!(Test-Path $localBasePath)) {
        New-Item -ItemType Directory -Path $localBasePath -Force | Out-Null
        Write-Host "  Создана временная папка: $localBasePath" -ForegroundColor Gray
    }

    if (!(Test-Path $networkFolder)) {
        New-Item -ItemType Directory -Path $networkFolder -Force | Out-Null
        Write-Host "  Создана сетевая папка: $networkFolder" -ForegroundColor Gray
    }

    Write-Host "  Получение списка папок из: ftp://$ftpHost$remoteBasePath/" -ForegroundColor Gray
    $foldersWithDates = Get-FtpFoldersWithDates -ftpPath $remoteBasePath

    if ($foldersWithDates.Count -eq 0) {
        Write-Host "  В папке $remoteFolder нет подпапок" -ForegroundColor Red
        return $false
    }

	Write-Host "  Найдено папок: $($foldersWithDates.Count)" -ForegroundColor Gray

    $latestFolder = $null
    $latestDate   = [DateTime]::MinValue
    foreach ($folder in $foldersWithDates.Keys) {
        $date = $foldersWithDates[$folder]
        if ($date -gt $latestDate) {
            $latestDate   = $date
            $latestFolder = $folder
        }
    }

    if (-not $latestFolder) {
        Write-Host "  Не удалось определить самую новую папку" -ForegroundColor Red
        return $false
    }

    Write-Host "  Самая новая папка: $latestFolder ($($latestDate.ToString('dd.MM.yyyy HH:mm')))" -ForegroundColor Green

    # === Проверка версии ===
    $versionFile = Get-VersionFile -folderPath $networkFolder
    $versionChanged = $false
    if ($versionFile) {
        Write-Host "  Файл версии на сети: $($versionFile.Name)" -ForegroundColor Gray
        if ($versionFile.BaseName -eq $latestFolder) {
            Write-Host "  Версия актуальна ($($versionFile.BaseName) = $latestFolder). Пропускаем." -ForegroundColor Green
            return $true
        } else {
            Write-Host "  Версия неактуальна ($($versionFile.BaseName) <> $latestFolder). Забираем архив." -ForegroundColor Yellow
            $versionChanged = $true
        }
    } else {
        Write-Host "  Файл версии на сети не найден. Забираем архив." -ForegroundColor Yellow
    }

    $ekpPath = "$remoteBasePath/$latestFolder/EKP"
    Write-Host "  Проверка EKP: ftp://$ftpHost$ekpPath/" -ForegroundColor Gray

    $ekpFiles = Get-FtpFileList -ftpPath $ekpPath
    if ($ekpFiles.Count -eq 0) {
        Write-Host "  Папка EKP пуста или не существует" -ForegroundColor Red
        return $false
    }

    Write-Host "  Найдено файлов в EKP: $($ekpFiles.Count)" -ForegroundColor Gray
    Write-Host "  Файлы: $($ekpFiles -join ', ')" -ForegroundColor Gray

    $archives = @()
    foreach ($file in $ekpFiles) {
        if ($file -match "\.rar$") { $archives += $file }
    }

    if ($archives.Count -eq 0) {
        Write-Host "  В EKP не найдено RAR архивов" -ForegroundColor Red
        Write-Host "  Доступные файлы: $($ekpFiles -join ', ')" -ForegroundColor Yellow
        return $false
    }

    Write-Host "  Найдены RAR архивы: $($archives -join ', ')" -ForegroundColor Gray
    $archiveToDownload = $archives[0]
    if ($archives.Count -gt 1) {
        Write-Host "  Найдено несколько архивов, выбран: $archiveToDownload" -ForegroundColor Yellow
    }

    $localFilePath   = Join-Path $localBasePath $archiveToDownload
    $networkFilePath = Join-Path $networkFolder $archiveToDownload

    # --- Если сменилась версия — локальный обрубок невалиден, удаляем ---
    if ($versionChanged -and (Test-Path $localFilePath)) {
        Write-Host "  Сменилась версия — удаляем локальный недокачанный файл: $localFilePath" -ForegroundColor Yellow
        try {
            Remove-Item -Path $localFilePath -Force -ErrorAction Stop
        }
        catch {
            Write-Host "  Не удалось удалить локальный файл: $($_.Exception.Message)" -ForegroundColor Yellow
        }
    }

    # --- Проверяем файл на сети: возможно, он уже есть, но недокачан ---
    if (Test-Path $networkFilePath) {
        $networkSize = (Get-Item $networkFilePath).Length
        $networkMb   = [math]::Round($networkSize / 1MB, 2)

        $ftpUriForCheck = "ftp://$ftpHost$ekpPath/$archiveToDownload"
        $ftpSizeCheck   = Get-FtpFileSize -remoteUri $ftpUriForCheck -ftpDirPath $ekpPath -fileName $archiveToDownload

        if ($ftpSizeCheck -gt 0 -and $networkSize -eq $ftpSizeCheck) {
            Write-Host "  Файл уже полностью скачан на сеть: $networkFilePath ($networkMb MB)" -ForegroundColor Green
            Update-VersionFile -folderPath $networkFolder -latestFolder $latestFolder -versionFile $versionFile
            return $true
        }
        elseif ($ftpSizeCheck -gt 0) {
            $ftpMb = [math]::Round($ftpSizeCheck / 1MB, 2)
            Write-Host "  Файл на сети неполный: $networkMb / $ftpMb MB. Перекачиваем." -ForegroundColor Yellow
            try {
                Remove-Item -Path $networkFilePath -Force -ErrorAction Stop
                Write-Host "  Неполный файл удалён: $networkFilePath" -ForegroundColor Gray
            }
            catch {
                Write-Host "  Не удалось удалить неполный файл: $($_.Exception.Message)" -ForegroundColor Yellow
            }
        }
        else {
            Write-Host "  Файл уже существует на сети: $networkFilePath ($networkMb MB). Размер на FTP неизвестен — пропускаем." -ForegroundColor Yellow
            Update-VersionFile -folderPath $networkFolder -latestFolder $latestFolder -versionFile $versionFile
            return $true
        }
    }

    # --- Если локальный файл старше 1 дня — считаем мусором ---
    if (Test-Path $localFilePath) {
        $localInfo = Get-Item $localFilePath
        if ($localInfo.LastWriteTime -lt (Get-Date).AddDays(-1)) {
            Write-Host "  Удаляем устаревший локальный файл: $localFilePath" -ForegroundColor Gray
            Remove-Item $localFilePath -Force -ErrorAction SilentlyContinue
        }
    }

    $downloadUri = "ftp://$ftpHost$ekpPath/$archiveToDownload"
    $remoteSize  = Get-FtpFileSize -remoteUri $downloadUri -ftpDirPath $ekpPath -fileName $archiveToDownload
    if ($remoteSize -gt 0) {
        $mb = [math]::Round($remoteSize / 1MB, 2)
        Write-Host "  Размер архива на FTP: $mb MB" -ForegroundColor Gray
    } else {
        Write-Host "  Размер архива на FTP: неизвестен" -ForegroundColor Gray
    }

    Write-Host "  Скачивание: $archiveToDownload" -ForegroundColor Cyan
    Write-Host "  Во временную папку: $localBasePath" -ForegroundColor Cyan

    if (-not (Download-FtpFile -remoteUri $downloadUri -localPath $localFilePath -ftpDirPath $ekpPath -fileName $archiveToDownload)) {
        return $false
    }

    Clear-NetworkFolder -folderPath $networkFolder -versionFile $versionFile

    if (-not (Copy-ToNetwork -sourceFile $localFilePath -networkFolder $networkFolder)) {
        Write-Host "  Не удалось скопировать на сеть" -ForegroundColor Red
        return $false
    }

    try {
        Remove-Item -Path $localFilePath -Force -ErrorAction Stop
        Write-Host "  Локальный файл удалён: $localFilePath" -ForegroundColor Gray
    }
    catch {
        Write-Host "  Не удалось удалить локальный файл: $($_.Exception.Message)" -ForegroundColor Yellow
    }

    Update-VersionFile -folderPath $networkFolder -latestFolder $latestFolder -versionFile $versionFile
    return $true
}

Write-Host "========================================" -ForegroundColor Cyan
Write-Host "FTP-хост:       $ftpHost" -ForegroundColor Yellow
Write-Host "FTP-путь:       $ftpPath" -ForegroundColor Yellow
Write-Host "Временная:      $localBasePath" -ForegroundColor Yellow
Write-Host "Сетевая:        $networkBasePath" -ForegroundColor Yellow
Write-Host "Флаг:           $flagFile" -ForegroundColor Yellow
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""

if (!(Test-Path $networkBasePath)) {
    Write-Host "ВНИМАНИЕ: сетевой путь недоступен: $networkBasePath" -ForegroundColor Red
    Write-Host "Проверьте, что Z: примонтирован и доступен." -ForegroundColor Red
    Write-Host "Продолжаем — папки будут созданы, если это возможно." -ForegroundColor Yellow
    Write-Host ""
}

$successCount    = 0
$totalCount      = $folderMapping.Count
$downloadResults = @{}

foreach ($remoteFolder in $folderMapping.Keys) {
    $localFolder = $folderMapping[$remoteFolder]
    Write-Host ""
    Write-Host (">> Обработка: {0} -> {1}" -f $remoteFolder, $localFolder) -ForegroundColor Magenta

    if (Download-ArchivesFromFTP -remoteFolder $remoteFolder -localFolder $localFolder) {
        $successCount++
        $downloadResults[$remoteFolder] = $true
    } else {
        $downloadResults[$remoteFolder] = $false
    }
    Write-Host "----------------------------------------" -ForegroundColor Gray
}

Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "РЕЗУЛЬТАТЫ:" -ForegroundColor Cyan
foreach ($remoteFolder in $folderMapping.Keys) {
    $localFolder = $folderMapping[$remoteFolder]
    if ($downloadResults[$remoteFolder]) {
        Write-Host ("  {0} -> {1} : УСПЕШНО" -f $remoteFolder, $localFolder) -ForegroundColor Green
    } else {
        Write-Host ("  {0} -> {1} : ОШИБКА" -f $remoteFolder, $localFolder) -ForegroundColor Red
    }
}
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ("Успешно: {0} из {1}" -f $successCount, $totalCount) -ForegroundColor Yellow

if ($successCount -eq $totalCount) {
    try {
        if (!(Test-Path $networkBasePath)) {
            New-Item -ItemType Directory -Path $networkBasePath -Force | Out-Null
        }

        $flagContent  = "Скачивание архивов завершено: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`r`n"
        $flagContent += "Обработано папок: $successCount из $totalCount`r`n`r`n"
        $flagContent += "Скачанные и скопированные архивы:`r`n"

        foreach ($remoteFolder in $folderMapping.Keys) {
            $localFolder   = $folderMapping[$remoteFolder]
            $networkFolder = Join-Path $networkBasePath $localFolder

            if (Test-Path $networkFolder) {
                $files = Get-ChildItem -Path $networkFolder -File -Filter "*.rar"
                if ($files.Count -gt 0) {
                    foreach ($file in $files) {
                        $size = [math]::Round($file.Length / 1MB, 2)
                        $flagContent += "  $localFolder : $($file.Name) ($size MB)`r`n"
                    }
                } else {
                    $flagContent += "  $localFolder : (нет .rar)`r`n"
                }
            } else {
                $flagContent += "  $localFolder : (папка не создана)`r`n"
            }
        }

        $flagContent | Out-File -FilePath $flagFile -Encoding UTF8
        Write-Host ""
        Write-Host "Файл success.flag создан: $flagFile" -ForegroundColor Green
        Write-Host "Содержимое success.flag:" -ForegroundColor Gray
        Write-Host $flagContent -ForegroundColor Gray
    }
    catch {
        Write-Host "Ошибка при создании success.flag: $_" -ForegroundColor Red
    }
} else {
    Write-Host ""
    Write-Host "Не все папки обработаны успешно. success.flag не создан." -ForegroundColor Red
}

Write-Host ""
Write-Host "Скрипт завершён." -ForegroundColor Cyan
Read-Host "123"
#Start-Sleep -Seconds 10
#exit 0

Конец ftp.ps1