Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Managing the Recycle Bin with PowerShell

Posted on December 3, 2019December 2, 2019

A while ago, I posted an Iron Scripter challenge asking you to write some PowerShell code for working with items in the recycle bin. You were asked to calculate how much space the recycle bin is using and then restore a file. If you'd prefer, stop reading this post, check out the challenge and see what you can come up with.  If you get stuck, this article might get you back on track. Although, I'm sure there are several ways to meet the challenge. My solution is far from the only solution.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Parsing the Recycle Bin

I decided to use the COM object approach and used a reference to Shell.Application. The Windows Recycle Bin can be accessed as Namespace 10.

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

This namespace will include the recycle bin on all fixed disks. Even though this object type may not be well documented, you can still use PowerShell to discover what to do. Get-Member is still your friend.

The Recycle Bin COM object

The Items property looks like it will give me deleted items.

Exploring the Recycle Bin with PowerShell

And sure enough it does. To make this easier, I'll add the items to an array.

Recycle Bin items

This is where it gets tricky.

Even though it looks like there are only 156 items in my recycle bin, there are many more. I only see the top level 156 items.  Items that are folders have their own Items property.

Getting Recycle Bin child items

As you can see, there is good information for each deleted file. In exploring objects with Get-Member, I put together a function to turn each deleted item into a usable PowerShell object.

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
}

My function has a non-standard name because I am using it as an internal, helper function inside of a control script. I can use this function to transform each recycle bin item into something easier to work with.

My parsed Recycle Bin object

This means I can get all the items like this:

$bin = $rb.items() | ParseItem

Which makes it very easy to calculate how much space my recycle bin is using.

Measuring the Recycle Bin with PowerShell

With this code as my foundation, it wouldn't be that much more work to get Recycle Bin information for different fixed drives.

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

Recycle Bin usage by drive

Restoring a Recycle Bin Item

Because I did all the work up front to write an object to the pipeline with original location information, it was pretty easy to write a function to restore the deleted item by moving it from the recycle bin location to the original location.

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

Now it is very simple to restore a deleted item.

Restoring a deleted item

I should probably revise the restore function to let me specify an alternate location. Or add some logic not to overwrite an existing file. But I can leave those scripting challenges to you.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d