Почему DFSR не реплицирует некоторые файлы и как с этим бороться

Как многим известно, в свойствах реплицируемых папок можно настроить исключения в виде масок файлов — и тогда служба не будет реплицировать файлы, соответствующие заданным маскам. Но не все знают, что у файлов есть атрибут «временный», и DFSR не обрабатывает такие файлы by design. И если это не учесть, то может случиться так, что содержимое ваших DFSR-папок станет рассинхронизированным, хотя в логах службы всё будет чисто и красиво, и всплыть это может в самый неподходящий момент. Сама проблема и ее решение уже не раз разбирались в интернете, цель же этой статьи — доработать созданное ранее решение, добавив ему гибкости и удобства. Для кого актуально — прошу под кат.

Тема давно и не раз освещалась в интернете, например в официальном блоге, тут и на самом Хабре. Поэтому не буду повторяться, а перейду сразу к делу. Исходное решение представляет собой скрипт на PowerShell, который сбрасывает атрибут «временный» для всех файлов в заданной папке. Взяв его за основу, я написал свой вариант, который находит реплицируемые папки на целевом сервере и пробегается по каждой из них, сбрасывая проблемный атрибут, после чего отправляет отчет о найденных файлах на email. Также добавлена поддержка длинных путей (необходима 5-я версия PowerShell).
Для работы скрипту нужны инструменты управления службами DFS (по умолчанию устанавливаются вместе с добавлением роли DFS Replication). Если же на целевом сервере они отсутствуют, запустите этот командлет:
Install-WindowsFeature RSAT-DFS-Mgmt-Con

А вот сам скрипт:
TempAttrFixer.ps1Param(
#Куда сохраняем отчеты
[parameter(Mandatory=$false)][String]$OutDir
)

$SMTPServer = «mail.mydomain.com»
$MailFrom = «sender@mydomain.com»
$MailTo = «recipient@mydomain.com»

Function ConvertTo-LiteralPath {
#Преобразуем формат пути из обычного в LiteralPath
Param(
[parameter(Mandatory=$true)][String]$Path
)
#Если путь в формате UNC
If ($Path.Substring(0,2) -eq «») {
Return («?UNC» + $Path.Remove(0,1))
}
Else {
Return «?$Path»
}
}

  Когда начинается рождественский пост в 2018 году у православных

$StartTime = Get-Date

$Error.Clear()

If (!$OutDir) {
$OutDir = (Get-WmiObject Win32_OperatingSystem).SystemDrive +»TempAttrFixer_Report»
}

#Получаем список реплицируемых папок, расположенных на сервере
$FoldersToScan = @(Get-DfsrMembership -ComputerName $env:COMPUTERNAME | Sort-Object GroupName, FolderName).ContentPath

$LogFileName = «$env:COMPUTERNAME» + «_TempFiles_» + (Get-Date -Format «yyyy-MM-dd-HH-mm-ss») + «.csv»
$LogFilePath = «$OutDir$LogFileName»
$Delimiter = «`t»
$FilesCount = 0
If (!(Test-Path $OutDir -PathType Container)) {
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
}

ForEach ($Folder in $FoldersToScan) {
#Нужен PowerShell 5.1, иначе командлет не поймет аргумент LireralPath
Write-Output «Scanning `»$Folder`»…»
Get-ChildItem -LiteralPath (ConvertTo-LiteralPath $Folder) -Recurse | ForEach-Object -Process {
if (($_.Attributes -band 0x100) -eq 0x100) {
$FilesCount += 1
$Entry = $_.FullName + $Delimiter + $_.GetAccessControl().Owner + «`r`n»
$Entry
$Entry | Out-File -FilePath $LogFilePath -Encoding unicode -Append -NoNewline
$_.Attributes = ($_.Attributes -band 0xFEFF)
}
}
}

$FinishTime = Get-Date
$TimeSpan = $FinishTime — $StartTime
Write-Output («Done, errors: » + $Error.Count)

$Encoding = [System.Text.Encoding]::Unicode

If ($FilesCount -gt 0) {
$MessageBody = $null
$Subject = «Обнаружены временные файлы в реплицируемых папках»
$MessageBody += «Имя компьютера: $env:COMPUTERNAME`r`n»
$MessageBody += «Общее время сканирования: » + $TimeSpan.ToString() + «`r`n»
$MessageBody += «Подробности в файле `»$LogFilePath`»`r`n»
Send-MailMessage -SmtpServer $SMTPServer -From $MailFrom -Subject $Subject -Encoding $Encoding -To $MailTo -Body $MessageBody -Attachments $LogFilePath
}
Else {
$MessageBody = $null
$Subject = «Временные файлы в реплицируемых папках не обнаружены»
$MessageBody += «Имя компьютера: $env:COMPUTERNAME`r`n»
$MessageBody += «Общее время сканирования: » + $TimeSpan.ToString() + «`r`n»
Send-MailMessage -SmtpServer $SMTPServer -From $MailFrom -Subject $Subject -Encoding $Encoding -To $MailTo -Body $MessageBody
}

#Уведомляем, если что-то пошло не так
If ($Error.Count -gt 0) {
$MessageBody = $null
$Subject = «Обработка временных файлов завершена с ошибкой»
$MessageBody += «Имя компьютера: $env:COMPUTERNAME`r`n»
$MessageBody += «Учетная запись: $env:UserName`r`n»
$MessageBody += «Последняя ошибка:`r`n»
$MessageBody += $Error[0]
Send-MailMessage -SmtpServer $SMTPServer -From $MailFrom -Subject $Subject -Encoding $Encoding -To $MailTo -Body $MessageBody
}

Правим значения переменных, отвечающих за email-оповещения, и добавляем скрипт в планировщик:
powershell.exe -NoLogo -ExecutionPolicy Bypass -NoProfile -File «<папка со скриптом>TempAttrFixer.ps1»

  Меган Маркл родила мальчика

Запускать задание проще всего из-под SYSTEM, но если у вас повышенный уровень паранойи практикуется серьезный подход к информационной безопасности, можно использовать отдельную учетную запись, выдав ей привилегию на вход в качестве пакетного задания и права на изменение файлов в реплицируемых папках.
Вместо отчетов на email (либо в дополнение к ним) можно настроить взаимодействие скрипта с заббиксом или другой системой мониторинга. Также важно понимать, что процедура достаточно ресурсоёмкая, поэтому не надо запускать ее слишком часто. У нас на обработку 10ТБ данных уходит примерно час, а скрипт запускается раз в сутки по ночам.
Еще нас с коллегами интересовал вопрос, откуда берутся файлы с temporary-атрибутом. Поэтому в каждом отчете вместе с полным именем файла фигурирует его NTFS-владелец. На основании собранных данных удалось выяснить, что в нашей ситуации атрибут иногда добавлялся к файлам во время копирования данных с локальных дисков, проброшенных по RDP (у нас активно используется технология удаленных рабочих столов). Но не исключено, что файлы изначально были «дефектными». Пока выяснить более подробно не удалось.
Буду благодарен, если вы пройдете краткий опрос и поделитесь своим опытом в комментариях.