Recherche de site Web

Gérer la corbeille avec PowerShell


Il y a quelque temps, j'ai publié un défi Iron Scripter vous demandant d'écrire du code PowerShell pour travailler avec des éléments dans la corbeille. Il vous a été demandé de calculer la quantité d'espace utilisée par la corbeille, puis de restaurer un fichier. Si vous préférez, arrêtez de lire cet article, jetez un œil au défi et voyez ce que vous pouvez proposer. Si vous êtes bloqué, cet article pourrait vous remettre sur la bonne voie. Cependant, je suis sûr qu’il existe plusieurs façons de relever le défi. Ma solution est loin d'être la seule.

Analyser la corbeille

J'ai décidé d'utiliser l'approche objet COM et j'ai utilisé une référence à Shell.Application. La corbeille Windows est accessible en tant qu'espace de noms 10.

$shell = New-Object -com shell.application
$rb = $shell.Namespace(10)

Cet espace de noms inclura la corbeille sur tous les disques fixes. Même si ce type d'objet n'est pas bien documenté, vous pouvez toujours utiliser PowerShell pour découvrir quoi faire. Get-Member est toujours votre ami.

La propriété Items semble me donner les éléments supprimés.

Et bien sûr, c’est le cas. Pour faciliter cela, je vais ajouter les éléments à un tableau.

C'est là que ça devient délicat.

Même s’il semble qu’il n’y ait que 156 éléments dans ma corbeille, il y en a bien d’autres. Je ne vois que les éléments du niveau supérieur 156. Les éléments qui sont des dossiers ont leur propre propriété Items.

Comme vous pouvez le constater, il existe de bonnes informations pour chaque fichier supprimé. En explorant des objets avec Get-Member, j'ai mis en place une fonction pour transformer chaque élément supprimé en un objet PowerShell utilisable.

Function ParseItem {
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [object]$Item
    )
    #this function relies variables set in a parent scope
    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $($item.path)"
        
        # uncomment for troubleshooting
        # $global:raw += $item
        if ($item.IsFolder -AND ($item.type -notmatch "ZIP")) {
            Write-Verbose "Enumerating $($item.name)"
            Try {
                #track the path name through each child object
                if ($fldpath) {
                    $fldpath = Join-Path -Path $fldPath -ChildPath $item.GetFolder.Title
                }
                else {
                    $fldPath = $item.GetFolder.Title
                }
                #recurse through child items
                $item.GetFolder().Items() | ParseItem
                Remove-Variable -Name fldpath
            }
            Catch {
               # Uncomment for troubleshooting
               # $global:rbwarn += $item
                Write-Warning ($item | Out-String)
                Write-Warning $_.exception.message
            }
        }
        else {
            #sometimes the original location is stored in an extended property
            $data = $item.ExtendedProperty("infotip").split("`n") | Where-Object { $_ -match "Original location" }
            if ($data) {
                $origPath = $data.split(":", 2)[1].trim()
                $full = Join-Path -path $origPath -ChildPath $item.name -ErrorAction stop
                Remove-Variable -Name data
            }
            else {
                #no extended property so use this code to attemp to rebuild the original location
                if ($item.parent.title -match "^[C-Zc-z]:\\") {
                    $origPath = $item.parent.title
                }
                elseif ($fldpath) {
                    $origPath = $fldPath
                }
                else {
                    $test = $item.parent
                    Write-Host "searching for parent on $($test.self.path)" -ForegroundColor cyan
                    do { $test = $test.parentfolder; $save = $test.title } until ($test.title -match "^[C-Zc-z]:\\" -OR $test.title -eq $save)
                    $origPath = $test.title
                }

                $full = Join-Path -path $origPath -ChildPath $item.name -ErrorAction stop
            }

            [pscustomobject]@{
                PSTypename       = "DeletedItem"
                Name             = $item.name
                Path             = $item.Path
                Modified         = $item.ModifyDate
                OriginalPath     = $origPath
                OriginalFullName = $full
                Size             = $item.Size
                IsFolder         = $item.IsFolder
                Type             = $item.Type
            }
        }
    } #process
}

Ma fonction a un nom non standard car je l'utilise comme fonction d'assistance interne à l'intérieur d'un script de contrôle. Je peux utiliser cette fonction pour transformer chaque élément de la corbeille en quelque chose de plus facile à utiliser.

Cela signifie que je peux obtenir tous les éléments comme ceci :

$bin = $rb.items() | ParseItem

Ce qui permet de calculer très facilement l’espace utilisé par ma corbeille.

Avec ce code comme base, il ne serait pas beaucoup plus de travail d'obtenir des informations sur la corbeille pour différents lecteurs fixes.

$bin | group-object -Property {$_.path.substring(0,2)} |
Select-Object -Property Name,Count,
@{Name="SizeMB";Expression = {($_.group | measure-object -Property size -sum).sum/1MB}}

Restauration d'un élément de la corbeille

Étant donné que j'ai effectué tout le travail dès le départ pour écrire un objet dans le pipeline avec les informations d'emplacement d'origine, il était assez facile d'écrire une fonction pour restaurer l'élément supprimé en le déplaçant de l'emplacement de la corbeille vers l'emplacement d'origine.

Function Restore-RecycleBinItem {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [object]$Item
    )
    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"

    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] $($Item.Path) "
        if (-Not (Test-Path $Item.originalPath)) {
            New-Item $Item.originalpath -force -itemtype directory
        }
        Move-Item -path $Item.Path -Destination $Item.OriginalFullName -PassThru -Force

    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end

} #close Restore-RecyleBinItem

Il est désormais très simple de restaurer un élément supprimé.

Je devrais probablement réviser la fonction de restauration pour me permettre de spécifier un autre emplacement. Ou ajoutez une logique pour ne pas écraser un fichier existant. Mais je peux vous laisser ces défis de script.

Articles connexes