Для одного из проектов нам понадобился PowerShell скрипт, который бы находил дубликаты файлов в сетевых папках сервера. Для Windows есть большое количество утилит для поиска и удаления дубликатов файлов, однако большинство из них платные либо слабо подходят для автоматизации.
Так как у файлов могут быть разные имена, но идентичное содержимое, некорректно будет сравнивать файлы просто по именам. Лучше получить хэши всех файлов и найти среди них одинаковые.
Следующий однострочный PowerShell скрипт позволяет рекурсивно просканировать указанную папку (включая вложенные) и найти дубликаты файлов. В данном примере нашлись два файла одинаковых файла с идентичными хэшами:
Get-ChildItem –path C:\Share\ -Recurse | Get-FileHash | Group-Object -property hash | Where-Object { $_.count -gt 1 } | ForEach-Object { $_.group | Select-Object Path, Hash }
Такой однострочную команду PowerShell удобно использовать для поиска дубликатов, но производительность ее оставляет желать лучшего. Если файлов в каталоге много и для каждого считать хэш, это займет довольно много времени. Проще сначала сравнить файлы по размеру (это готовый атрибут файла, который не надо вычислять). Хэш будем получать только для файлов с одинаковым размером:
$file_dublicates = Get-ChildItem –path C:\Share\ -Recurse| Group-Object -property Length| Where-Object { $_.count -gt 1 }| Select-Object –Expand Group| Get-FileHash | Group-Object -property hash | Where-Object { $_.count -gt 1 }| ForEach-Object { $_.group | Select-Object Path, Hash }
Можете сравнить производительность обеих команда на тестовой папке с помощью Measure-Command:
Measure-Command {your_powershell_command}
На небольшом каталоге с 2000 файлов разница в скорости выполнения второй команды будет на несколько порядков быстрее (10 минут vs 3 секунды).
Можно предложить пользователю выбрать файлы, которые можно удалить. Для этого список дубликатов файлов нужно передать конвейером в командлет Out-GridView:
$file_dublicates | Out-GridView -Title "Выберите файлы для удаления" -OutputMode Multiple –PassThru|Remove-Item –Verbose –WhatIf
Пользователь в таблице может выбрать файлы для удаления (чтобы выбрать несколько файлов, нужно зажать ctrl) и нажать Ok.
Move-Item
.Вадим Стеркин в своем блоге www.outsidethebox.ms предлагает заменять дубликаты файлов на жесткие ссылки. Такой подход позволит сохранить файлы на месте и существенно сэкономить место на диске.
Приведу полный код скрипта с его сайта здесь (https://www.outsidethebox.ms/20953/):
param(
[Parameter(Mandatory=$True)]
[ValidateScript({Test-Path -Path $_ -PathType Container})]
[string]$dir1,
[Parameter(Mandatory=$True)]
[ValidateScript({(Test-Path -Path $_ -PathType Container) -and $_ -ne $dir1})]
[string]$dir2
)
Get-ChildItem -Recurse $dir1, $dir2 |
Group-Object Length | Where-Object {$_.Count -ge 2} |
Select-Object -Expand Group | Get-FileHash |
Group-Object hash | Where-Object {$_.Count -ge 2} |
Foreach-Object {
$f1 = $_.Group[0].Path
Remove-Item $f1
New-Item -ItemType HardLink -Path $f1 -Target $_.Group[1].Path | Out-Null
#fsutil hardlink create $f1 $_.Group[1].Path
}
Для запуска файла используйте такой формат команды:
.\hardlinks.ps1 -dir1 d:\fldr1 -dir2 d:\fldr2
Этот скрипт можно использовать для поиска и замены дубликатов статических файлов (которые не изменяются!) на символические жёсткие ссылки.
Также для замены дубликатов файлов жесткими ссылками можно использовать консольную утилиту dupemerge.
Команда со сравнением файлов по размеру не работает, ничего не происходит, работает только долгая команда. Тире на дефисы заменил, ничего.
вроде работает быстрая. переменную смотрите или удалите ее из скрипта «$file_dublicates =»
В сетевой папке зачастую файлы бывают заняты/открыты пользователями, куча темп файлов. Скрипт когда натыкается на это сразу обрушается. Как пофиксить?
Как я понимаю проблема с
Remove-Item $f1
? Используте в этой команде параметры-ErrorAction Ignore
или-ErrorAction SilentlyContinue
.А как в конечный вывод добавить еще размер файлов?
Get-ChildItem *.* -Recurse |
Group-Object -property Length| Where-Object { $_.count -gt 1 }|
Select-Object –Expand Group| Get-FileHash |
Group-Object -property hash | Where-Object { $_.count -gt 1 }|
ForEach-Object { $_.group | Select-Object Path, Hash -skip 1} | del
В примере от Vadim Sterkin всё сложнее и сложнее. мой способ из 2015 года + новый синтаксис + компактность
И тут есть проблема. Сменившая NTFS REFS не поддерживает жёсткие ссылки, а дедубликация не распространяется на медиафайлы. В.С. Предлагает сравнивать два каталога, но их может быть больше, Поэтому надо оставить как в оригинале, поиск по размеру, потом поиск по хэшу но добавить Measure.
И тут есть проблема. Скрипт зависает видимо при большом количестве MD5 хэшей.
Подскажите, как мне добавить в скрипт условие, для обработки файлов определенного размера?
А то получается, что вплоть до dll’лек обрабатывает всё что лежит в директории.
Добавляем проверку на минимальный размер файла в 1000 Мб:
Get-ChildItem –path C:\ -Recurse |where {$_.Length -gt 1000mb}| Get-FileHash | Group-Object -property hash | Where-Object { $_.count -gt 1 } | ForEach-Object { $_.group | Select-Object Path, Hash }