all repos — fast-scripts @ 91ad877b609c1970114b901276fa63385f9c37cc

scripts que podem ser uteis para meu dia a dia

Organizar/achar-duplicados.cmd (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
<# :
@echo off
powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression ([System.IO.File]::ReadAllText('%~f0',[System.Text.Encoding]::UTF8))"
exit /b %errorlevel%
#>

# ============================================================
#  Achar duplicados - encontra arquivos iguais por conteúdo (hash)
#  Desenvolvido por Pablo Murad - 2026
# ============================================================

try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
try { $Host.UI.RawUI.WindowTitle = 'Achar duplicados' } catch {}

function Pausar { Write-Host ''; Write-Host 'Pressione Enter para fechar...' -ForegroundColor DarkGray; [void][System.Console]::ReadLine() }
function Titulo($t) {
    Write-Host ''
    Write-Host '============================================================' -ForegroundColor DarkCyan
    Write-Host "  $t" -ForegroundColor Cyan
    Write-Host '============================================================' -ForegroundColor DarkCyan
}
function Tam($bytes) { if ($bytes -ge 1GB) { '{0:N2} GB' -f ($bytes/1GB) } elseif ($bytes -ge 1MB) { '{0:N1} MB' -f ($bytes/1MB) } else { '{0:N0} KB' -f ($bytes/1KB) } }

Titulo 'Achar duplicados'
Write-Host '  Desenvolvido por Pablo Murad - 2026' -ForegroundColor DarkGray

$pasta = $null
while (-not $pasta) {
    Write-Host ''
    $p = (Read-Host 'Cole o caminho da pasta (verifica também as subpastas)').Trim().Trim('"')
    if (Test-Path -LiteralPath $p -PathType Container) { $pasta = (Resolve-Path -LiteralPath $p).Path } else { Write-Host '[!] Pasta não encontrada.' -ForegroundColor Yellow }
}

Write-Host ''
Write-Host 'Analisando... (agrupa por tamanho e depois compara o conteúdo)' -ForegroundColor Gray
$todos = @(Get-ChildItem -LiteralPath $pasta -Recurse -File -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt 0 })

# 1) agrupa por tamanho (rápido); 2) só calcula hash de quem tem tamanho repetido
function Hash-SHA256([string]$caminho) {
    $sha = [System.Security.Cryptography.SHA256]::Create()
    $fs  = [System.IO.File]::OpenRead($caminho)
    try { return ([BitConverter]::ToString($sha.ComputeHash($fs)) -replace '-', '') }
    finally { $fs.Close(); $sha.Dispose() }
}
# Pre-filtro barato: hash so das PONTAS do arquivo (primeiros/ultimos 64 KB).
# So quem colide nas pontas paga o SHA256 do arquivo inteiro.
function Hash-Parcial([string]$caminho, [long]$tam) {
    $n = 65536
    try { $fs = [System.IO.File]::OpenRead($caminho) } catch { return "ERRO$caminho" }
    try {
        $md5 = [System.Security.Cryptography.MD5]::Create()
        $buf = New-Object byte[] $n
        $lido = $fs.Read($buf, 0, $n)
        [void]$md5.TransformBlock($buf, 0, $lido, $buf, 0)
        if ($tam -gt ($n * 2)) {
            [void]$fs.Seek(-$n, [System.IO.SeekOrigin]::End)
            $lido2 = $fs.Read($buf, 0, $n)
            [void]$md5.TransformBlock($buf, 0, $lido2, $buf, 0)
        }
        [void]$md5.TransformFinalBlock((New-Object byte[] 0), 0, 0)
        return ([BitConverter]::ToString($md5.Hash) + "_$tam")
    } catch { return "ERRO$caminho" } finally { $fs.Close() }
}

$porTamanho = @($todos | Group-Object Length | Where-Object { $_.Count -gt 1 })
$grupos = @()
$gi = 0
foreach ($g in $porTamanho) {
    $gi++
    Write-Host ("`r  Comparando... {0}/{1} grupos   " -f $gi, $porTamanho.Count) -NoNewline -ForegroundColor Cyan
    $porParcial = $g.Group | Group-Object { Hash-Parcial $_.FullName $_.Length }
    foreach ($pp in ($porParcial | Where-Object { $_.Count -gt 1 })) {
        $porHash = $pp.Group | Group-Object { Hash-SHA256 $_.FullName }
        foreach ($h in ($porHash | Where-Object { $_.Count -gt 1 })) {
            $grupos += ,@($h.Group | Sort-Object FullName)
        }
    }
}
if ($porTamanho.Count -gt 0) { Write-Host '' }
if ($grupos.Count -eq 0) { Titulo 'Resultado'; Write-Host 'Nenhum arquivo duplicado encontrado.' -ForegroundColor Green; Pausar; exit 0 }

$totalDup = 0; $espaco = 0
foreach ($grp in $grupos) { $totalDup += ($grp.Count - 1); $espaco += ($grp.Count - 1) * $grp[0].Length }

Titulo 'Duplicados encontrados'
Write-Host ("Grupos de iguais: {0}   |   Cópias extras: {1}   |   Espaço recuperável: {2}" -f $grupos.Count, $totalDup, (Tam $espaco)) -ForegroundColor Cyan
Write-Host ''
$mostra = [Math]::Min($grupos.Count, 15)
for ($i=0; $i -lt $mostra; $i++) {
    $grp = $grupos[$i]
    Write-Host ("Grupo {0} ({1} cópias, {2} cada):" -f ($i+1), $grp.Count, (Tam $grp[0].Length)) -ForegroundColor White
    for ($j=0; $j -lt $grp.Count; $j++) {
        $marca = if ($j -eq 0) { '[manter] ' } else { '[extra]  ' }
        $cor   = if ($j -eq 0) { 'Green' } else { 'DarkGray' }
        Write-Host ("   $marca" + $grp[$j].FullName) -ForegroundColor $cor
    }
}
if ($grupos.Count -gt $mostra) { Write-Host ("... e mais {0} grupo(s)." -f ($grupos.Count - $mostra)) -ForegroundColor DarkGray }

Titulo 'O que fazer com as cópias extras?'
Write-Host '  [1] Só listar (nada é alterado) - padrão' -ForegroundColor White
Write-Host '  [2] Mover as cópias extras para uma subpasta "_Duplicados"' -ForegroundColor White
Write-Host '  [3] Apagar as cópias extras (mantém sempre 1 original)' -ForegroundColor White
$acao = Read-Host 'Opção (Enter = 1)'
if ([string]::IsNullOrWhiteSpace($acao)) { $acao = '1' }

if ($acao -eq '1') { Write-Host ''; Write-Host 'Nada foi alterado. Relatório acima.' -ForegroundColor Gray; Pausar; exit 0 }

if ($acao -eq '3') {
    Write-Host ''
    Write-Host 'ATENÇÃO: isto APAGA as cópias extras permanentemente.' -ForegroundColor Red
    $conf = Read-Host 'Digite APAGAR para confirmar'
    if ($conf -cne 'APAGAR') { Write-Host 'Cancelado.' -ForegroundColor Yellow; Pausar; exit 0 }
}

$feitos=0;$err=0
$destDup = Join-Path $pasta '_Duplicados'
foreach ($grp in $grupos) {
    for ($j=1; $j -lt $grp.Count; $j++) {
        $f = $grp[$j]
        try {
            if ($acao -eq '2') {
                New-Item -ItemType Directory -Path $destDup -Force | Out-Null
                $base=[System.IO.Path]::GetFileNameWithoutExtension($f.Name); $ext=$f.Extension
                $dest=Join-Path $destDup $f.Name; $n=1
                while (Test-Path -LiteralPath $dest) { $dest=Join-Path $destDup ("$base ($n)$ext"); $n++ }
                Move-Item -LiteralPath $f.FullName -Destination $dest -ErrorAction Stop
            } else {
                Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop
            }
            $feitos++
        } catch { Write-Host "   [ERRO] $($f.Name): $($_.Exception.Message)" -ForegroundColor Red; $err++ }
    }
}
Titulo 'Concluído'
$verbo = if ($acao -eq '2') { 'Movidas' } else { 'Apagadas' }
Write-Host "$verbo`: $feitos cópia(s) extra(s)" -ForegroundColor Green
Write-Host "Erros:   $err" -ForegroundColor $(if ($err -gt 0){'Red'}else{'Gray'})
if ($acao -eq '2') { Write-Host "Movidos para: $destDup" }
Pausar