четверг, 9 февраля 2012 г.

PowerShell: Синхронизация целевого каталога с каталогом образца

Пара функций, которые я буду использовать в одном из своих скриптов.


# Функция синхронизации целевого каталога с каталогом, содержимое которого принято за образец.
# ПАРАМЕТРЫ ФУНКЦИИ:
# $sourceDirPath - путь к каталогу-источнику, т.е. к каталогу, с которым должен быть синхронизирован 
# целевой каталог.
# $destDirPath - путь к целевому каталогу, подлежащему синхронизации.
# $recursive - метка рекурсивной обработки содержимого ($True - с рекурсивной обработкой; $False - 
# без рекурсивной обработки).
# $excludSrvFoldNames - Перечень имён подкаталогов, которые не должны участвовать в 
# синхронизации (т.е. пропускать их). Содержимое этих подкаталогов можно синхронизировать по иному 
# алгоритму (учитывая специфику содержимого). 
# В качестве параметра $excludSrvFoldNames можно передавать значение $NULL - это означает, 
# что обрабатывать следует все подкаталоги.
# ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ:
# Функция не имеет возвращаемых значений.
# 
# ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:
# 
# sync -sourceDirPath "c:\000" -destDirPath "c:\111\222" -recursive "Новая папка", "Новая папка (2)"
#
# Первый пример синхронизирует содержимое каталога "c:\111\222" с содержимым принятого за эталон 
# каталога "c:\000". При этом обработка будет рекурсивной, и в обоих каталогах из обработки будут 
# исключены все подкаталоги, имеющие имена "Новая папка" и "Новая папка (2)".
# 
# sync -sourceDirPath "c:\000" -destDirPath "c:\111\222" -recursive $NULL
# 
# Второй пример синхронизирует содержимое каталога "c:\111\222" с содержимым принятого за эталог 
# каталога "c:\000". При этом обработка будет рекурсивной, и обработке будут подвергаться все 
# подкаталоги, т.к. аргументу '-excludSrvFoldNames' в качестве значения передан $NULL.
# 
# sync -sourceDirPath "c:\000" -destDirPath "c:\111\222" $NULL
# 
# Третий пример синхронизирует содержимое каталога "c:\111\222" с содержимым принятого за эталог 
# каталога "c:\000". При этом обработка не будет рекурсивной, т.к. не указан переключатель '-recursive'.
# 
function sync ([string] $sourceDirPath, [string] $destDirPath, [Switch] $recursive, `
[string[]] $excludSrvFoldNames) {
    # Если указанный клиентский каталог отсутствует - создаём новый одноимённый каталог
    if (![System.IO.Directory]::Exists($destDirPath)) {
        New-Item -Path $destDirPath -ItemType "directory" | Out-Null
    }
 
    # Если в качестве фильтра передан $NULL - заменяем его массивом, состоящим из одного значения 
    # "" (каталогов с таким именем не может существовать, а следовательно это гарантирует выборку 
    # всех подкаталогов.
    if ($excludSrvFoldNames -eq $NULL) {
        [string[]] $excludSrvFoldNames = ""
    }    
 
    # Получаем имена эталонных файлов
    $sourceFileNames = $NULL
    $sourceDirPath | Get-Item | Get-ChildItem | Where-Object {!$_.PSIsContainer} | ForEach-Object `
    {[string[]] $sourceFileNames += $_.PSChildName}    
 
    # Удаляем лишние целевые файлы    
    $destDirPath | Get-Item | Get-ChildItem | Where-Object {!$_.PSIsContainer -and `
    !($sourceFileNames -contains $_.PSChildName)} | Remove-Item   
 
    # Заменяем модифицированные целевые файлы их эталонными оригиналами
    $destDirPath | Get-Item | Get-ChildItem | Where-Object {!$_.PSIsContainer -and `
    !(IsEqualContents "$destDirPath\$($_.PSChildName)" "$sourceDirPath\$($_.PSChildName)")} | 
    ForEach-Object {Copy-Item -Path "$sourceDirPath\$($_.PSChildName)" -Destination $destDirPath `
    -Recurse}  
 
    # Получаем имена целевых файлов
    $destFileNames = $NULL
    $destDirPath | Get-Item | Get-ChildItem | Where-Object {!$_.PSIsContainer} | 
    ForEach-Object {[string[]] $destFileNames += $_.PSChildName}
 
    # Добавляем недостающие файлы в целевой каталог
    $sourceDirPath | Get-Item | Get-ChildItem | Where-Object {!$_.PSIsContainer -and `
    !($destFileNames -contains $_.PSChildName)} | Copy-Item -Destination $destDirPath
 
    # Если указана рекурсивная обработка, то синхронизируем вложенные подкаталоги.
    if ($recursive) {
        # Получаем имена эталонных подкаталогов
        $sourceSubDirNames = $NULL
        $sourceDirPath | Get-Item | Get-ChildItem | Where-Object {$_.PSIsContainer -and `
        !($excludSrvFoldNames -contains $_.PSChildName)} | ForEach-Object `
        { [string[]] $sourceSubDirNames += $_.PSChildName}  
 
        # Удаляем лишние целевые каталоги
        $destDirPath | Get-Item | Get-ChildItem | Where-Object {$_.PSIsContainer -and `
        !($excludSrvFoldNames -contains $_.PSChildName) -and !($sourceSubDirNames -contains `
        $_.PSChildName)} | Remove-Item -Recurse -Force
 
        # Синхронизируем вложенные подкаталоги.
        $sourceDirPath | Get-Item | Get-ChildItem | Where-Object {$_.PSIsContainer -and `
        !($excludSrvFoldNames -contains $_.PSChildName)} | ForEach-Object {sync $_.FullName `
        "$destDirPath\$($_.PSChildName)" $recursive $excludSrvFoldNames}   
    } 
}
 
# Функция, с помощью которой будем определять идентичность содержимого файлов. Выносим проверку 
# идентичности в отдельную функцию, чтобы можно было заменить логику проверки. Например сейчас 
# проверяется размер файла и дата модификации, но можно вместо этого организовать проверку хеша по 
# некоторому алгоритму (MD5, SHA2 или др.).
# ПАРАМЕТРЫ ФУНКЦИИ:
# $first - имя первого файла, подлежащего сравнению
# $second - имя второго файла, подлежащего сравнению
# ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ:
# Функция возвращает логическое значение, являющееся результатом сравнения: 
# $True - файлы одинаковые; $False - файлы разные.
function IsEqualContents ([string] $first, [string] $second) {
    $f1 = New-Object -TypeName System.IO.FileInfo -ArgumentList $first
    $f2 = New-Object -TypeName System.IO.FileInfo -ArgumentList $second
 
    ($f1.Length -eq $f2.Length) -and ($f1.LastWriteTimeUtc -eq $f2.LastWriteTimeUtc)
}


Код подробно комментирован, поэтому дополнительных пояснений не делаю.

2 комментария:

Александр комментирует...

Добрый день!
Очень полезный и интересный скрипт.Можно ли его настроить так, чтобы он не удалял файлы которые отсутствуют в эталонной папке?

Andrey Bushman комментирует...

Можно. Закомментируйте строку кода, которая удаляет лишние целевые файлы (помечена комментарием в коде).