Libera spazio sul tuo disco rigido! Con PowerShell
I file duplicati possono occupare spazio prezioso sul tuo disco rigido e rallentare il tuo computer. In questo articolo, ti mostrerò come utilizzare PowerShell per trovare ed eliminare i file duplicati in modo facile e veloce.
L’dea di base è quella di andare ad indicizzare ogni file presente nella/e cartella/e che vogliamo andare ad ripulire e per farlo sfrutteremo il concetto di hash.
Ma cos’è un hash?
Per semplificare, un algoritmo di hash è una funzione matematica che “comprime” un’informazione di qualsiasi lunghezza (come una parola, una frase o un file) in una stringa di caratteri di lunghezza fissa, chiamata, appunto, “hash”.
Gli hash, quindi, possono essere considerati come delle impronte digitali che identificano, in maniera univoca e sicura, un file ed il suo il contenuto e, essendo nient’altro che una stringa di caratteri, sono facili da confrontare per determinare se due file siano identici anche se con nomi e/o path differenti.
Cosa ti serve
- Un computer con PowerShell installato (su windows è installato di default)
- Un editor di testo come notepad o qualcosa di più avanzato (opzionale) come Notepad++, PowerShell ISE o Visual Studio Code
Come funziona
Lo script scansiona le cartelle che selezioni e calcola l’hash SHA256 di ogni file. I file con lo stesso hash sono considerati duplicati. Lo script ti mostrerà un elenco di tutti i file duplicati trovati, indicando la loro dimensione e la cartella in cui si trovano.
Lo Script
Per prima cosa occorre andare a creare un dizionario per permetterci di associare ai singoli hash gli eventuali path dei file duplicati.
$hashtable = New-Object System.Collections.Hashtable
Fatto ciò, andiamo a scorrere ogni singolo file e ne calcoliamo l’hash relativo e lo aggiungiamo al dizionario creato nel precedente step.
$hash = (Get-FileHash -Path $item.FullName -Algorithm SHA256).Hash
Questo ci permetterà di indentificare, man mano che scansioniamo, i file duplicati che all’occorrenza andremo a marcare come tali.
# Check if hash exists in the hash table if($hashtable.ContainsKey($hash)){ # Found duplicate! Increment counters $duplicates++; $bytes += $item.Length $toRemove = $true }
Al termine della scansione, lo script produrrà un file CSV (comma separated values) contenente l’elenco di tutti i file scansionati, con il relativo hash, con allegata l’informazione se è da rimuovere oppure no. Di base lo script marca come “da rimuovere” tutti i file successivi al primo identificati come duplicati.
Ecco un esempio in azione.
. 'D:\Scripts\Find-DuplicateFiles.ps1' -Paths D:\foto\ -OutputPath D:\
5 items to analyze
Duplicates found: 2 (1,00 Mb)
Result file: D:\duplicated_files_20240422_0959.csv created
Il file prodotto
"Hash","File","CreationTime","LastWriteTime","Size","ToRemove"
"F954AC8B009F965C052519C4E1E395A9F15328596A2B1EAF373D74FE7E169A5F","D:\foto\f1.jpg","22/04/2024 09:57:46","22/04/2024 09:59:10","1048576","False"
"18B423094A1956209D0C777BBAE92A8B7CF74F17739DF7DB4B72BF932DF7CD51","D:\foto\f2.jpg","22/04/2024 09:57:46","22/04/2024 09:59:10","2048","False"
"F954AC8B009F965C052519C4E1E395A9F15328596A2B1EAF373D74FE7E169A5F","D:\foto\f3.jpg","22/04/2024 09:57:46","22/04/2024 09:59:10","1048576","True"
"67FA8B7E479417053708E46F2B3669C2F1C2857DF57ACBFF83AC6A06EA0232E9","D:\foto\foto.jpg","22/04/2024 09:57:46","22/04/2024 09:59:10","1024","False"
"67FA8B7E479417053708E46F2B3669C2F1C2857DF57ACBFF83AC6A06EA0232E9","D:\foto\fotografia.jpg","22/04/2024 09:57:46","22/04/2024 09:59:10","1024","True"
Possiamo notare che i due file duplicati trovati sono stati marcati come “ToRemove:True” e corrispondono a due file differenti e lo capiamo tramite l’hash.
Quando i record da analizzare diventano tanti, è utile utilizzare un programma come Microsoft Excel per andare a filtrare ed ordinare le colonne.
Conclusioni
Ora che abbiamo il nostro file di output possiamo andare visionare i file marcati come da rimuovere ed eventualmente modificare la scelta fatta in automatico dallo script (es. potremmo voler che vengano cancellati i file con data di ultima scrittura meno recente). Una volta terminata la nostra revisione possiamo andare ad eliminare tutti i file marcati come da rimuovere con il seguente comando:
(Import-Csv -Delimiter "," -Path D:\duplicated_files_20240422_1011.csv | ?{$_.ToRemove -eq $true}).FullName | Remove-Item
Dove stiamo essenzialmente reimportando i dati generati dallo script andando poi a filtrarli per la colonna ToRemove prendendo solo quelli marcati come “da rimuovere e per questi ultimi ne richiediamo la rimozione.
Di seguito lo script completo:
[Cmdletbinding()] param( # Array of paths to search for duplicates [Parameter(Mandatory=$true)] [string[]] $Paths, # Switch parameter to enable searching subfolders [switch] $Recurse, [Parameter(Mandatory=$true)] [string] $OutputPath, [switch]$PassThru ) # Initialize variables $hashtable = New-Object System.Collections.Hashtable # Hash table to store hash and file paths $files = @() $bytes = 0 # Total size of duplicate files (bytes) $duplicates = 0 # Number of duplicate files found if(!(Test-Path $OutputPath)) { Write-Warning "Path: $OutputPath not exists" exit } # Loop through each provided path foreach ($path in $Paths) { # Check if path exists if(!(Test-Path $path)) { Write-Host "Path $path not found" continue; } # Get all files from the path (including subfolders if Recurse is set) $items = Get-ChildItem -Path $path -Recurse:$Recurse -File Write-Host "$($items.Count) items to analyze" # Progress bar initialization $i = 1 Write-Progress -Activity "Evaluating $($items.Count) files [path: $path]" -Status ([string]::Format("Duplicates: {0} ({1:N2} Mb)", $duplicates, $bytes/1Mb)) -PercentComplete 0 # Loop through each file in the path foreach ($item in $items) { Write-Progress -Activity "Evaluating $i of $($items.Count) files [path: $path]" -Status ([string]::Format("Duplicates: {0} ({1:N2} Mb)", $duplicates, $bytes/1Mb)) -PercentComplete ($i++/$items.Count * 100) # Calculate SHA256 hash of the file $hash = (Get-FileHash -Path $item.FullName -Algorithm SHA256).Hash $toRemove = $false # Check if hash exists in the hash table if($hashtable.ContainsKey($hash)){ # Found duplicate! Increment counters $duplicates++; $bytes += $item.Length $toRemove = $true } # Add file path to the hash table entry for the current hash $props = [Ordered]@{Hash = $hash; FullName=$item.FullName; CreationTime=$item.CreationTime; LastWriteTime=$item.LastWriteTime; Size=$item.Length ;ToRemove=$toRemove} $obj = New-Object -Property $props -TypeName psobject $hashtable[$hash] += @($item.FullName) $files += $obj } } # Display summary of duplicate files found Write-Host ([string]::Format("Duplicates found: {0} ({1:N2} Mb)", $duplicates, $bytes/1Mb)) # Output the hash table containing duplicate file information $export = Join-Path $OutputPath ("duplicated_files_" + [datetime]::Now.ToString("yyyyMMdd_HHmm") + ".csv") $files| Export-Csv -Delimiter "," -Path $export -Force -NoTypeInformation Write-Host "Result file: $export created" if($PassThru) { Write-Output $files }