mit Powershells Group-Object doppelte Dateien löschen
Zum Finden und Löschen von doppelten Dateien gibt es jede Menge Tools, aber es geht auch von der Kommandozeile mit einem Powershell-Einzeiler. Dafür ist ein Cmdlet nützlich, dass ich normalerweise nur selten einsetze, dass hier aber seine Mächtigkeit ausspielt: Group-Object.
Mit Group-Object kann man Objekte in ein Array zusammenfassen (also gruppieren), die in einer Eigenschaft gleich sind, also z.B. nach dem Namen, dem Erstelldatum usw. Die ausgegebenen Gruppen beinhalten dann nicht nur die gruppierten Objekte, sondern auch die Menge der Objekte in einer Gruppe sowie die Eigenschaft, die gruppiert wurde. Wenn man das auf die Dateien des Windows\System32-Ordners anwendet, sieht das ungefähr so aus:
Die Eigenschaft Count gibt an, wie viele Elemente eine Gruppe enthält. Sortiert man nach der Anzahl und filtert alle Gruppen aus, die weniger als 2 Elemente beinhalten, erhält man alle Dateien gleicher Größe in einer Gruppe. Als Beispiel verwende ich hier den Ordner Windows\System32, damit man anhand realer Dateien die Ausgabe nachvollziehen kann.
get-Childitem -Path c:\windows\System32 -File | Group-Object -Property length | Where-Object { $_.Count -gt 1 }
Anschließend muss man die Gruppen mit einem Foreach-Object wieder auflösen und mit dem Parameter -Skip des Select-Object das jeweils erste Element - das beibehalten werden soll - entfernen. Die Ausgabe kann man dann innerhalb des Skriptblocks des Foreach-Object in den Remove-Item pipen, was im Code ausgelassen wurde, um bösen Unfällen vorzubeugen.
get-Childitem -Path c:\windows\System32 -File | Group-Object -Property length | Where-Object { $_.Count -gt 1 } | ForEach-Object { $_.group | Select-Object -skip 1 # remove-item }
Die Gruppierung der Dateien nach Größe geht sehr schnell, ist aber kein eindeutiger Indikator für die Eindeutigkeit von Dateien, speziell, wenn es sich um sehr kleine Dateien handelt. Diese Variante funktioniert sehr gut, wenn es sich um sehr große Dateien handelt, bei der der Verlust einer einzelnen Datei keine katastrophalen Auswirkungen hat, da sie wahnsinnig schnell ist.
Um die Eindeutigkeit der Dateien sicherzustellen, muss man zuerst eine Prüfsumme in Form eines Hashes über die Dateien erstellen, was mit dem Cmdlet Get-Filehash sehr einfach zu bewerkstelligen ist. Dafür piped man die Ausgabe von Get-Childitem einfach zuerst in Get-Filehash und gruppiert dann nach dem erstellten Hash. Der modifizierte Code sieht dann so aus:
get-Childitem -Path c:\windows\System32 -File | Get-FileHash -Alogrithm SHA1 | Group-Object -Property Hash -Algorithm SHA1 | Where-Object { $_.Count -gt 1 } | ForEach-Object { $_.Hash | Select-Object -skip 1 # | remove-item }
Im Beispielcode wurde SHA1 als als Hash-Algorithmus angegeben. Lässt man den Parameter aus, verwendet Get-Filehash den SHA256-Algorithmus, der längere Hashes erzeugt, aber auch ein wenig langsamer ist. Die Eindeutigkeit ist bei beiden Algorithmen sicher gestellt.
Ein paar Hinweise zur Performance: Es hat in meinen Tests keinen merklichen Unterschied gemacht, ob ich Powershell 5.1 oder Powershell 7 eingesetzt habe. Die Hash-Algorithmen sind aber so schnell, dass es einen Unterschied macht, ob die Dateien im Netzwerk oder lokal liegen! Im Zweifel sollte man die Duplikatssuche immer lokal ausführen oder eine schnelle Netzwerkanbindung bevorzugen. Zum Erstellen der Hashes müssen die Dateien aber immer erst lokal über das Netzwerk gezogen werden, man generiert bei großen Datenmengen also jede Menge Netzwerklast! Wenn es schnell gehen soll, ist es also eine gute Idee, die Suche die Dateigröße einzuschränken und nur auf die Dateien gleicher Größe eine Hash-Prüfung zu machen, was den Suchprozess massiv beschleunigt. Das fertige Skript sieht dann also so aus - wieder ohne Remove-Item:
get-Childitem -Path c:\windows\System32 -File | Group-Object -Property length | Where-Object { $_.Count -gt 1 } | Select-Object -ExpandProperty group | Get-FileHash -Algorithm SHA1 | Group-Object -Property hash | ForEach-Object { $_.group.Path | Select-Object -skip 1 # | remove-item }
Ich habe das ganze noch einmal in eine Funktion gegossen, die einen Ordner auch rekursive nach Duplikaten untersuchen kann, aber keinen Löschvorgang ausführt, sondern nur die gruppierten Duplikate ausgibt.
Function Get-FileDuplicates { <# .SYNOPSIS Finds and groups duplicate files in a specified directory by content. .DESCRIPTION Get-FileDuplicates scans a directory for duplicate files by grouping files with equal size (length) and then comparing file hashes (SHA1) to confirm content-based duplication. The function supports recursive search using the -Recurse switch and returns file groups sharing identical content. Only files are included; directories are ignored. .PARAMETER Path The root directory to scan for duplicate files. Accepts pipeline input of type [System.IO.DirectoryInfo]. .PARAMETER Recurse If specified, searches subfolders recursively for duplicate files. .INPUTS System.IO.DirectoryInfo Allows pipeline input for the -Path parameter. .OUTPUTS System.Object Groups of duplicate files (file objects sharing same hash). .EXAMPLE Get-FileDuplicates -Path "C:\Pictures" Finds and groups duplicate files in C:\Pictures. .EXAMPLE Get-ChildItem "C:\Data" | Get-FileDuplicates -Recurse Finds duplicates across all subdirectories within C:\Data. .NOTES Uses a two-step approach (size then hash) for efficiency. Only files with matching sizes are hashed and compared. #> Param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [System.IO.DirectoryInfo]$Path, [Switch]$Recurse ) Process { Get-ChildItem -Path $Path -File -Recurse:$Recurse -ErrorAction SilentlyContinue | Group-Object -Property Length | Where-Object -FilterScript { $_.Count -gt 1 } | Select-Object -ExpandProperty group | Get-FileHash -Algorithm SHA1 | Group-Object -Property hash } }
When you subscribe to the blog, we will send you an e-mail when there are new updates on the site so you wouldn't miss them.
Comments