none
[POWERSHELL] Ricerca File Duplicati; Esistono metodi più veloci? RRS feed

Risposte

  • function Take-Double($Source){
        $SourceDir = Get-Item -Path $Source -ErrorAction Stop
        $ChildFiles = $SourceDir.GetFiles('*', [System.IO.SearchOption]::AllDirectories)
    
        $PossibleDuplicates = $ChildFiles| Group-Object -Property Length| Where-Object { $_.Count -gt 1 }
    
        foreach ($GroupInfo in $PossibleDuplicates){
            $Hashes = foreach ($File in $GroupInfo.Group){
                New-Object psobject -Property @{
                    Path = $File.FullName
                    HashCode = Get-FileHash $File.fullname -Algorithm MD5
                }
            }
    
            $Hashes| Group-Object -Property HashCode| Where-Object { $_.Count -gt 1 }|
            ForEach-Object {
                $Paths = ($_.Group | Select-Object -ExpandProperty Path) -join ', '
                Write-Host "Duplicate files detected: $Paths" -BackgroundColor Green
            }
        }
    }
    
    $Where = 'C:\temp'
    if(Test-Path $Where){
        Take-Double -Source $Where -Verbose
    }
    else{
        Write-Error 'Invalid Path' -Category ResourceUnavailable
    }

    Così dovrebbe funzionare molto meglio...

    Grazie!

    • Contrassegnato come risposta Albiz lunedì 13 gennaio 2014 10:00
    lunedì 13 gennaio 2014 10:00

Tutte le risposte

  • Ho dato un occhio rapido al codice  e ci sono notevoli margini di miglioramento ho ottimizzato un po' le cose

    ottenendo un miglioramento quasi del 30% 

    Get-Double originale------------------------------------

    Days              : 0
    Hours             : 0
    Minutes           : 3
    Seconds           : 49
    Milliseconds      : 460
    Ticks             : 2294601264
    TotalDays         : 0,0026557885
    TotalHours        : 0,063738924
    TotalMinutes      : 3,82433544
    TotalSeconds      : 229,4601264
    TotalMilliseconds : 229460,1264

    Get-D modificata ------------------------------------
     
    Days              : 0
    Hours             : 0
    Minutes           : 2
    Seconds           : 57
    Milliseconds      : 721
    Ticks             : 1777216812
    TotalDays         : 0,00205696390277778
    TotalHours        : 0,0493671336666667
    TotalMinutes      : 2,96202802
    TotalSeconds      : 177,7216812
    TotalMilliseconds : 177721,6812

    function Get-Double {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory=$true,
                       Position=0,
                       ValueFromPipeline=$true,
                       ValueFromPipelineByPropertyName=$true)]
            [System.String]
            [Alias('PSPath')]
            $Path
        )
    
        if ((Test-Path -Path $Path) -eq $false) {
            throw New-Object System.IO.FileNotFoundException($Path)
        }
    
        try {
            $item = Get-Item -Path $Path -Force -ErrorAction Stop
        } 
        catch {
            throw
        }
    
        if ($item -isnot [System.IO.FileInfo]) {
            throw New-Object System.ArgumentException('Specified path must refer to a File object.','Path')
        }
    
        try {
            $bytes = [System.IO.File]::ReadAllBytes($item.FullName)
        } catch {
            throw
        }
    
        $md5Hash = [System.Security.Cryptography.MD5]::Create()
        $hashBytes = $md5Hash.ComputeHash($bytes)
    
        $sb = New-Object System.Text.StringBuilder
    
        foreach ($byte in $hashBytes) {
            $sb.Append($byte.ToString("x2")) | Out-Null
        }
    
        Write-Output $sb.ToString()
    }
    
    function Get-D {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory=$true,
                       Position=0,
                       ValueFromPipeline=$true,
                       ValueFromPipelineByPropertyName=$true)]
            [System.String]
            [Alias('PSPath')]
            $Path
        )
    
    
        $item = Get-Item -Path $Path -Force 
        $md5Hash = [System.Security.Cryptography.MD5]::Create()
        $hashBytes = $md5Hash.ComputeHash([System.IO.File]::ReadAllBytes($item.FullName))
    
        $sb = New-Object System.Text.StringBuilder
    
        foreach ($byte in $hashBytes) {
            $sb.Append($byte.ToString("x2")) | Out-Null
        }
    
        $sb.ToString()
    }
    cls
    $Kind= "Double"
    
    $Files = @{}
    $Search = "C:\Program Files\"
    "Get-Double originale------------------------------------"
    Measure-Command { Get-ChildItem $Search -Force -Recurse | 
    Where-Object { $_ -is [System.IO.FileInfo] } |
    ForEach-Object {
        try {
            $hash = $_ | Get-Double
        } catch {
            Write-Error -ErrorRecord $_
            return
        }
    
        if ($Files.ContainsKey($hash) -eq $false) {
            $Files[$hash] = New-Object 'System.Collections.Generic.List[System.String]'
        } else {
            # I'm just identifying duplicates by adding all their names to a list.
            # Keep in mind that if two files have identical hashes, they could have
            # different contents; it's possible for two different inputs to produce the same
            # hash code.
    		 "doppio"
    		 $_
        }
    
        $Files[$hash].Add($_.FullName)
    }
    
    }
    "Get-D modificata ------------------------------------"
    Measure-Command { Get-ChildItem $Search -Force -Recurse | 
    Where-Object { !$_.PSIsContainer } |
    ForEach-Object {
        try {
            $hash = $_ | Get-D
        } catch {
            Write-Error -ErrorRecord $_
            return
        }
    
        if ($Files.ContainsKey($hash) -eq $false) {
            $Files[$hash] = New-Object 'System.Collections.Generic.List[System.String]'
        } else {
            # I'm just identifying duplicates by adding all their names to a list.
            # Keep in mind that if two files have identical hashes, they could have
            # different contents; it's possible for two different inputs to produce the same
            # hash code.
    		 "doppio"
    		 $_
        }
    
        $Files[$hash].Add($_.FullName)
    }
    
    }

    Ti coniglio di cambiare approccio, il tuo script rischia di far collassare il pc/server dove lo andrai ad eseguire, allocare l' hashing di tutti i file in una variabile non è cosa sana (per soli 10000 files lo script ha allocato 1,6GB di RAM !!!)   Prova a ridurre il numero di Hash solo per i probabili doppi es i file che hanno la stessa dimensione...

    Cerca su internet una utility nata per questo specifico scopo...

    Ho anche letto il tuo post precedente e ho visto che ti veniva consigliato:

    1) Ok per individuare file pesanti in tutto il disco ma è possibile farlo con una ricerca avanzata.

    2) Stessa inutilità per gli installer facilmente individuabili con una ricerca.

    per i punti 1 e 2 powershell ti da garanzie migliori, con un risultato molto più accurato, dato che  "la ricerca avanzata di windows" purtroppo non cerca in tutti i folder e alcune directory  vengono escluse!!! Concludendo se non trovi un file con il cerca di windows, non è detto che non ci sia, usa ps/dos/vbs o qualche utility ad hoc e se c'è lo troverai.


    Gastone Canali >http://www.armadillo.it

    Se alcuni post rispondono al tuo quesito(non necessariamente i miei), ricorda di contrassegnarli come risposta e non dimenticare di contrassegnare anche i post utili. GRAZIE! Ricorda di dare un occhio ai link Click Here andHere



    venerdì 10 gennaio 2014 17:28
  • Buongiorno!

    Effettivamente ho appurato che trovare gli hash per tutti i file è alquanto pesante...

    Sto in primis raggruppando i file in base alla dimensione, successivamente eseguo un controllo di hash sui singoli sottogruppi.

    Spero di postare il codice a breve!

    Grazie!

    lunedì 13 gennaio 2014 08:53
  • function Take-Double($Source){
        $SourceDir = Get-Item -Path $Source -ErrorAction Stop
        $ChildFiles = $SourceDir.GetFiles('*', [System.IO.SearchOption]::AllDirectories)
    
        $PossibleDuplicates = $ChildFiles| Group-Object -Property Length| Where-Object { $_.Count -gt 1 }
    
        foreach ($GroupInfo in $PossibleDuplicates){
            $Hashes = foreach ($File in $GroupInfo.Group){
                New-Object psobject -Property @{
                    Path = $File.FullName
                    HashCode = Get-FileHash $File.fullname -Algorithm MD5
                }
            }
    
            $Hashes| Group-Object -Property HashCode| Where-Object { $_.Count -gt 1 }|
            ForEach-Object {
                $Paths = ($_.Group | Select-Object -ExpandProperty Path) -join ', '
                Write-Host "Duplicate files detected: $Paths" -BackgroundColor Green
            }
        }
    }
    
    $Where = 'C:\temp'
    if(Test-Path $Where){
        Take-Double -Source $Where -Verbose
    }
    else{
        Write-Error 'Invalid Path' -Category ResourceUnavailable
    }

    Così dovrebbe funzionare molto meglio...

    Grazie!

    • Contrassegnato come risposta Albiz lunedì 13 gennaio 2014 10:00
    lunedì 13 gennaio 2014 10:00
  • Prova a cimentarti scrivendo il codice di tuo pugno, inutile postare risposte di altri e marcartele come soluzioni! Metti il riferimento  da dove hai tratto il "tuo" codice, la spiegzione di Wyatt è importante quanto il codice stesso, inutile porre la domanda su due forum fa perder tempo ...

    Richiedere script, accrocchiarli fra loro, non implica sapere scrivere buon codice ne conoscere come funziona powershell... Prima di pubblicare codice come in "basta un po' di logica" riguarda tutto il tuo script e cerca di migliorarlo, es. nel mio post ho modificato il tuo codice togliendo una verifica "duplicata" di esistenza file otenendo 30% di performance in più.

    Il "tuo" codice che hai postato è molto migliore del precedente, ma non abbastanza affidabile per essere utilizzato in un ambiente di produzione il punto debole è la seguente riga :

    $ChildFiles = $SourceDir.GetFiles('*', [System.IO.SearchOption]::AllDirectories)

    Funziona se si ha l'accesso a tutte le directories e al posto di $ChildFiles sarebbe meglio usare una pipe... altri problemi anche nel calcolo dell'hash con file di grandi dimensioni dovuti alla ReadAllBytes($item.FullName) ...

    ciao


    Gastone Canali >http://www.armadillo.it

    Se alcuni post rispondono al tuo quesito(non necessariamente i miei), ricorda di contrassegnarli come risposta e non dimenticare di contrassegnare anche i post utili. GRAZIE! Ricorda di dare un occhio ai link Click Here andHere


    lunedì 13 gennaio 2014 11:59
  • Ciao! Come sai sto cercando di imparare, dopo aver letto i libri consigliati e anche altri, mi accorgo ogni giorno che quanto fatto è migliorabile e, spulciando online, trovo spunti, idee che cerco di testare, tuttavia, mi sento ancora un utente basic. Fatto sta che il mio codice comunque lo avevo scritto(l'ho salvato, se vuoi sto un attimo a postarlo) ma ho intuito che era migliorabile (Un esempio: al posto di Group-Object usavo una serie di cicli for), quindi ho fatto le domande; David gentilmente mi ha risposto con un codice per intero che ho testato e ho visto funzionante, a questo punto, come fatto in precedenza, l'ho postato e segnato come risposta, poiché al momento credo sia il miglioramento cercato (Magari un domani, alla luce di nuove conoscenze, posso pensare di migliorarlo ancora). Non è che non intenda riconoscere i meriti a David o in generale a coloro che mi hanno aiutato in tutto questo tempo, e non intendo nemmeno far passare per mio ciò che altri hanno fatto, ho semplicemente postato il codice poiché al momento è la risposta alla domanda fatta. Spero di non aver offeso nessuno con questo mio comportamento ma se fosse chiedo venia. A
    martedì 14 gennaio 2014 08:15
  • Prova a cimentarti scrivendo il codice di tuo pugno, inutile postare risposte di altri e marcartele come soluzioni!


    Buongiorno!

    A riprova di quanto detto allego il mio nuovo script fatto tutto da solo, si è sempre la solita solfa in merito alle cartelle vuote etc....

    function Move-EmptyDir{
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)][string]$Source,
            [Parameter(Mandatory = $true)][string]$Destination,
            [ValidateNotNullOrEmpty()][regex]$NoMatch
        )
    
        $EmptyFolders=@()
        $AllFolders= (Dir $Source -Force -Recurse) | Where-Object{$_.Attributes -match "Directory"}
        $ExistFolders= $AllFolders | Group-Object -Property Exists | Where-Object{$_.Name -match "True"} #verify
        $Inexisting= $AllFolders | Group-Object -Property Exists | Where-Object{$_.Name -match "False"}
        if($Inexisting.Count -gt 0){
            ForEach($Not in $Inexisting){
                Write-Warning "The Folder $Not is not existing"
            }
        }
        Foreach($Item in $ExistFolders.Group){
            $MeltinCount= $Item.GetFileSystemInfos().count #0= no element(s)
            $SimCount= (($ExistFolders.Group | Group-Object -Property Parent) | Where-Object{$_.Name -eq $Item.Name}).Count #0=no subfolders
            if($MeltinCount -eq $SimCount){
                $EmptyFolders+= $Item
            }
            else{
                Write-Warning "The Folder $Item is have file(s)"
            }
        }
    
        $Moved= @()
        $Moving= $EmptyFolders.Length -1
        
        While($Moving -ne -1){
            $OneFolder= $EmptyFolders[$Moving] #<---------Maybe the problem is near here
            Write-Host $OneFolder.name -BackgroundColor Red
            if($OneFolder.GetFileSystemInfos().count -gt 0){ #Parent have some directory(but 0 files)
                Write-Warning "This Folder $OneFolder is not empty"
                $Moving--
            }
            elseif($OneFolder.Name -match $NoMatch){
                Write-Warning "The Folder $OneFolder Match the Exceptions"
                $Moving--
            }
            elseif($OneFolder.parent.Name -match $NoMatch){
                Write-Host "The Parent Folder of $OneFolder Match the Exceptions" -BackgroundColor Green
                $Moved += $OneFolder
                $Place = $OneFolder.FullName -replace "^$([regex]::Escape($Source))", $Destination
                if((Test-Path $Place) -eq $false){
                    New-Item -Path $Place -ItemType Directory
                    Remove-Item -Path $OneFolder.fullname -Force -ErrorAction SilentlyContinue
                    Write-Verbose ('Moved folder "{0}" to "{1}"' -f $OneFolder.FullName, $Place)
                    $Moving --
                }
                else{
                    Remove-Item -Path $OneFolder.FullName -Force -ErrorAction SilentlyContinue
                    $Moving --
                }
            }
            else{
                $Moved += $OneFolder
                $Place = $OneFolder.FullName -replace "^$([regex]::Escape($Source))", $Destination
                if((Test-Path $Place) -eq $false){
                    New-Item -Path $Place -ItemType Directory
                    Remove-Item -Path $OneFolder.fullname -Force -ErrorAction SilentlyContinue
                    Write-Verbose ('Moved folder "{0}" to "{1}"' -f $OneFolder.FullName, $Place)
                    $Moving --
                }
                else{
                    Remove-Item -Path $OneFolder.fullname -Force -ErrorAction SilentlyContinue
                }
                $Moving --
            }
        }
    }
    
    $Where= 'C:\temp'
    $ToGo= 'C:\estinto'
    $MatchList= '0','1','8','9','Scansion'
    $Kind= 'Empty'
    if(Test-Path $Where){
        #$TheMatch = Select-Match -FullList $MatchList
        $Folders = Move-EmptyDir -Source $Where -Destination $ToGo -NoMatch 'F' -Verbose
        $Drives = ForEach($Item in $Folders){
            Select-Object -Property @(
                @{ Name = 'Name'; Expression = { $Item.Name } }
                @{ Name = 'Percorso'; Expression = { $Item.Parent.FullName } }
            )
        }
        #$Drives
        #Get-Report $Drives $Kind
    }
    else{
        Write-Error 'Invalid Path' -Category ResourceUnavailable
    }

    Però aver fatto la prima parte con Group-object e aver ridotto un controllo mi sembra già qualcosa di significativo...

    mercoledì 22 gennaio 2014 12:22