Скрипт PowerShell для копирования папок частями

admin Автор admin 24.11.2025
Скрипт PS пригодится когда будет необходимость копировать/синхронизировать большой объем папок и файлов между серверами.

Под копотом будет запускаться Robocopy с параметрами /E /B /R:2 /W:1 /MT:8 /COPYALL /NFL /NDL /NP — можете запенить на нужные в самом теле скрипта.

В данном скрипте копирование будет идит батчами по 10 папок в 8 поток. Эти все параметры можете заменить сами.

Включено визуальное отображение процесса.

Выглядит так:
# Параметры
$sourcePath = "\\server\d$\Shares\"#Указываем путь откуда копировать данные
$destinationPath = "D:\Shares\"#Путь куда данные скопировать. Обычно это локальное хранилище на сервера
$batchSize = 10 #Количество папок в батче
$threadCount = 6

# Проверяем доступность исходного пути
if (-not (Test-Path $sourcePath)) {
    Write-Host "Ошибка: Исходный путь недоступен: $sourcePath" -ForegroundColor Red
    exit 1
}

# Получаем все папки верхнего уровня
Write-Host "Сканирование папок в $sourcePath ..."
$allFolders = Get-ChildItem -Path $sourcePath -Directory | Select-Object -ExpandProperty FullName

Write-Host "Всего папок для копирования: $($allFolders.Count)"
$totalBatches = [Math]::Ceiling($allFolders.Count / $batchSize)
Write-Host "Будет обработано батчей: $totalBatches (по $batchSize папок в каждом)" -ForegroundColor Yellow

# Функция для многопоточного копирования
function Copy-FoldersBatch {
    param(
        [string[]]$Folders,
        [string]$SourceRoot,
        [string]$DestinationRoot,
        [int]$ThreadCount = 6
    )

    $jobs = @()
    $foldersPerThread = [Math]::Ceiling($Folders.Count / $ThreadCount)

    for ($i = 0; $i -lt $ThreadCount; $i++) {
        $startIndex = $i * $foldersPerThread
        $endIndex = [Math]::Min(($startIndex + $foldersPerThread - 1), ($Folders.Count - 1))
        if ($startIndex -gt $endIndex) { continue }

        $threadFolders = $Folders[$startIndex..$endIndex]

        Write-Host "Запущен поток $i для копирования $($threadFolders.Count) папок"

        # Запуск фонового задания
        $job = Start-Job -ScriptBlock {
            param($folders, $sourceRoot, $destinationRoot, $threadId)

            foreach ($folder in $folders) {
                try {
                    $folderName = Split-Path $folder -Leaf
                    $relativePath = $folder.Substring($sourceRoot.Length).TrimStart('\')
                    $destPath = Join-Path $destinationRoot $relativePath

                    # Создаем целевую директорию, если не существует
                    if (-not (Test-Path $destPath)) {
                        New-Item -ItemType Directory -Path $destPath -Force | Out-Null
                    }

                    # Всегда запускаем Robocopy — он сам определит, что обновлять
                    Write-Host "[Thread $threadId] Копирование (синхронизация): $folderName"

                    # Формируем команду для cmd
                    $cmd = "robocopy `"$folder`" `"$destPath`" /E /B /R:2 /W:1 /MT:8 /COPYALL /NFL /NDL /NP"
                    cmd.exe /c $cmd | Out-Null
                    $exitCode = $LASTEXITCODE

                    if ($exitCode -le 7) {
                        Write-Host "[Thread $threadId] Успешно: $folderName" -ForegroundColor Green
                    } else {
                        Write-Host "[Thread $threadId] Ошибка копирования: $folderName (Код: $exitCode)" -ForegroundColor Red
                    }
                }
                catch {
                    Write-Host "[Thread $threadId] EXCEPTION: $folder -> $($_.Exception.Message)" -ForegroundColor Red
                }
            }
        } -ArgumentList $threadFolders, $SourceRoot, $DestinationRoot, $i

        $jobs += $job
    }

    Write-Host "Ожидание завершения потоков..."
    $jobs | Wait-Job | Out-Null
    $jobs | Receive-Job | Out-Null
    $jobs | Remove-Job | Out-Null
}

# Основной цикл по батчам
for ($batchNumber = 0; $batchNumber -lt $totalBatches; $batchNumber++) {
    $startIndex = $batchNumber * $batchSize
    $endIndex = [Math]::Min(($startIndex + $batchSize - 1), ($allFolders.Count - 1))
    $currentBatch = $allFolders[$startIndex..$endIndex]

    Write-Host "`n=== Батч $($batchNumber + 1) из $totalBatches ===" -ForegroundColor Yellow
    Write-Host "Папки в батче: $($currentBatch.Count)" -ForegroundColor Cyan
    $currentBatch | ForEach-Object { Write-Host "  - $(Split-Path $_ -Leaf)" }

    Copy-FoldersBatch -Folders $currentBatch -SourceRoot $sourcePath -DestinationRoot $destinationPath -ThreadCount $threadCount

    Write-Host "Батч $($batchNumber + 1) завершен" -ForegroundColor Green

    if ($batchNumber -lt $totalBatches - 1) {
        Write-Host "Пауза 3 секунд перед следующим батчем..." -ForegroundColor Gray
        Start-Sleep -Seconds 3
    }
}

Write-Host "`n=== ВСЕ ПАПКИ СИНХРОНИЗИРОВАНЫ ===" -ForegroundColor Green
Write-Host "Источник: $sourcePath"
Write-Host "Назначение: $destinationPath"
Write-Host "Всего обработано папок: $($allFolders.Count)"