Данный сценарий находит в указанных папках файлы с одинаковым размером и на основании MD5-хэша отбирает из них файлы с одинаковым содержимым


function Find-EqualFile
{
    <#
    .SYNOPSIS
    Командлет предназначен для поиска одинаковых файлов
    .DESCRIPTION
    Командлет находит в указанных папках файлы с одинаковым 
    размером и на основании MD5-хэша отбирает из них файлы с 
    одинаковым содержимым
    .PARAMETER Folders
    Папки, в которых необходимо найти одинаковые файлы
    .PARAMETER OutputCSV
    Путь к CSV-файлу со списком одинаковых файлов
    .INPUTS
    -
    .OUTPUTS
    -    
    .NOTES
    Версия 1.3 
    (c) 2019 Александр Галков, [email protected]
    .EXAMPLE
    Find-EqualFile -Folders "c:\folder1","c:\folder2" -OutputCSV "c:\output.csv"
    .LINK
    www.galkov.pro/powershell_script_for_finding_equal_files
    #>

    [CmdletBinding()]
    
    Param
    (
        [Parameter(Mandatory=$true)][string[]]$Folders,
        [Parameter(Mandatory=$true)][string]$OutputCSV
    )

    $format = "yyyy-MM-dd HH:mm:ss"

    #составляем список всех файлов
    
    Write-Host ("{0} Составляем список всех файлов..." -f ([DateTime]::Now).ToString($format))
    $all_files = @()
    foreach ($folder in $Folders)
    {
        $all_files += Get-ChildItem -LiteralPath $folder -File -Recurse -Force
    }
    Write-Host ("{0} Обнаружено файлов: {1}" -f ([DateTime]::Now).ToString($format), $all_files.Length)

    #составляем список файлов с одинаковым размером

    Write-Host ("{0} Составляем список файлов с одинаковым размером..." -f ([DateTime]::Now).ToString($format))
    $eq_size_groups = @{}
    for ($i=0; $i -lt $all_files.Length; $i++)
    {
        $size = $all_files[$i].Length
        if (!$eq_size_groups.ContainsKey($size))
        {
            $eq_size_groups.Add($size,@())
        }
        $eq_size_groups[$size] += $all_files[$i]
    }
    $eq_size_file_count = 0
    $eq_size_group_count = 0
    foreach ($eq_size_files in $eq_size_groups.Values)
    {
        if ($eq_size_files.Length -gt 1)
        {
            $eq_size_file_count += $eq_size_files.Length
            $eq_size_group_count++
        }
    }
    Write-Host ("{0} Обнаружено групп файлов с  одинаковым размером: {1}, в них файлов: {2}" -f 
        ([DateTime]::Now).ToString($format), $eq_size_group_count, $eq_size_file_count)

    #вычисляем хэш

    Write-Host ("{0} Вычисляем хэш..." -f ([DateTime]::Now).ToString($format))
    $eq_hash_groups = @{}
    $proc_file_count = 0
    foreach ($eq_size_files in $eq_size_groups.Values)
    {
        if ($eq_size_files.Length -gt 1)
        {
            for ($i=0; $i -lt $eq_size_files.Length; $i++)
            {
                $hash = Get-FileHash -LiteralPath $eq_size_files[$i].FullName -Algorithm MD5
                if (!$eq_hash_groups.ContainsKey($hash.Hash))
                {
                    $eq_hash_groups.Add($hash.Hash,@())
                }
                $eq_hash_groups[$hash.Hash] += $eq_size_files[$i]
                if ($proc_file_count%[Math]::Ceiling($eq_size_file_count/10) -eq 0 -or $proc_file_count -eq $eq_size_file_count-1)
                {
                    write-host ("{0} Обработано: {1} %" -f ([DateTime]::Now).ToString($format),
                        [Math]::Round(($proc_file_count+1)/$eq_size_file_count*100))
                }
                $proc_file_count++
            }
        }
    }
    $eq_hash_file_count = 0
    $eq_hash_group_count = 0
    foreach ($eq_hash_files in $eq_hash_groups.Values)
    {
        if ($eq_hash_files.Length -gt 1)
        {
            $eq_hash_file_count += $eq_hash_files.Length
            $eq_hash_group_count++
        }
    }
    Write-Host ("{0} Обнаружено групп файлов с одинаковым хэшем: {1}, в них файлов: {2}" -f 
        ([DateTime]::Now).ToString($format), $eq_hash_group_count, $eq_hash_file_count)

    #сохраняем результат
    
    Write-Host ("{0} Сохраняем результат в файл {1}" -f ([DateTime]::Now).ToString($format), $OutputCSV)
    $file_instances = @()
    foreach ($hash in $eq_hash_groups.Keys)
    {
        $eq_hash_files = $eq_hash_groups[$hash]
        if ($eq_hash_files.Length -gt 1)
        {
            $eq_hash_files = $eq_hash_files | Sort -Property Fullname
            for ($i=0; $i -lt $eq_hash_files.Length; $i++)
            {
                $file_instance = New-Object -TypeName PSObject
                Add-Member -InputObject $file_instance -MemberType NoteProperty -Name ID -Value 0
                Add-Member -InputObject $file_instance -MemberType NoteProperty -Name Fullname -Value $eq_hash_files[$i].Fullname
                Add-Member -InputObject $file_instance -MemberType NoteProperty -Name Instance -Value ($i+1)
                Add-Member -InputObject $file_instance -MemberType NoteProperty -Name Hash -Value $hash
                $file_instances += $file_instance
            }
        }        
    }
    $file_instances = $file_instances | Sort -Property Instance,Fullname
    for ($i=0; $i -lt $file_instances.Length; $i++)
    {
        $file_instances[$i].ID = $i
    }
    $file_instances | Export-Csv -LiteralPath $OutputCSV -Encoding UTF8 -NoTypeInformation
}

После этого можно удалить лишние экземпляры файлов, используя следующий сценарий

function Remove-ExcessFile
{
    <#
    .SYNOPSIS
    Командлет предназначен для удаления лишних экземпляров файлов 
    .DESCRIPTION
    -
    .PARAMETER InputCSV
    CSV-файл, полученный в результате выполнения командлета Find-EqualFile
    .PARAMETER IDs
    ID файлов, которые нужно удалить
    .INPUTS
    -
    .OUTPUTS
    -    
    .NOTES
    Версия 1.0 
    (c) 2019 Александр Галков, [email protected]
    .EXAMPLE
    Remove-ExcessFile -InputCSV "с:\input.csv" -IDs "5-25,30,50-75,100"
    .LINK
    www.galkov.pro/powershell_script_for_finding_equal_files
    #>

    [CmdletBinding()]
    
    Param
    (
        [Parameter(Mandatory=$true)][string]$InputCSV,
        [Parameter(Mandatory=$true)][string]$IDs
    )

    $all_files = Import-Csv -LiteralPath $InputCSV -Encoding UTF8

    $intervals = @()
    while ($IDs -ne "")
    {
        if ($IDs -match "(.*),(.*)")
        {
            $IDs = $Matches[1]
            $id = $Matches[2]
        }
        else
        {
            $id = $IDs
            $IDs = ""
        }

        $interval = New-Object -TypeName PSObject
        Add-Member -InputObject $interval -MemberType NoteProperty -Name Left -Value 0
        Add-Member -InputObject $interval -MemberType NoteProperty -Name Right -Value 0
        if ($id -match "(.*)-(.*)")
        {
            $interval.Left = [Convert]::ToInt32($Matches[1])
            $interval.Right = [Convert]::ToInt32($Matches[2])
        }
        else
        {
            $interval.Left = $interval.Right = [Convert]::ToInt32($id)
        }
        $intervals += $interval
    }

    $removed_files = @()
    foreach ($file in $all_files)
    {
        foreach ($interval in $intervals)
        {
            if ([Convert]::ToInt32($file.ID) -ge $interval.Left -and [Convert]::ToInt32($file.ID) -le $interval.Right)
            {
	            $removed_files += $file
                break
            }
        }
    }
    $removed_files | Format-Table -AutoSize

    $answer = ""
    while ($answer.ToLower() -ne "y" -and $answer.ToLower() -ne "n")
    {
        $answer = Read-Host -Prompt $("Удалить указанные файлы в количестве {0} шт (y/n)?" -f $removed_files.Length)
    }
    if ($answer.ToLower() -eq "y")
    {
        foreach ($removed_file in $removed_files)
        {
            Remove-Item -LiteralPath $removed_file.Fullname -Force
        }
    }
}

Файлы данных сценариев можно скачать, соответственно, отсюда и отсюда

Добавить комментарий