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.