Thursday, July 11, 2013

Empty Recycle Bin with Retention Time (Logoff Script)

In an environment where Roaming Profiles are used and/or where User Profile folders (like My Documents, Desktop, Application Data, etc.) are redirected to a network share the Recycle Bin for those locations also resides on the network. Meaning that if users delete a lot in these locations and never empty their Recycle Bin valueable network storage is wasted.

I didn't want to disable the Recycle Bin feature in it's whole but rather delete stuff that has been deleted longer than x days ago.

In order to do so you will have to communicate with the Recycle Bin application by creating an instance to the Shell.application object and connect to the Recycle Bin Special Folder by using the NameSpace method.

$Shell = New-Object -ComObject Shell.Application
$Global:Recycler = $Shell.NameSpace(0xa)

You can then retrieve the content of this special folder by using the Items method.

$Recycler.Items()

I wrote a little function to Get the Recycle Bin's content older than x days (7 by default) and, if the DeleteItems parameter is provided, deletes this content. (I can't get the formatting right in both Iexplore and Chrome... Internet Explorer displays colors but alligns left and Chrome doesn't display colors on editing this page in Blogger :-/ )

function Get-recyclebin
{
[CmdletBinding()]
Param($RetentionTime = "7",
[Switch]$DeleteItems
)
$User = $env:USERNAME
$Computer = $env:COMPUTERNAME
$DateRun = Get-Date

foreach($item in $Recycler.Items())
{
$DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e","" #Invisible Unicode Characters
$DeletedDate_datetime = get-date $DeletedDate
[Int]$DeletedDays = (New-TimeSpan -Start $DeletedDate_datetime -End $(Get-Date)).Days

If($DeletedDays -ge $RetentionTime)
{
$Size = $Recycler.GetDetailsOf($item,3)
$SizeArray = $Size -split " "
$Decimal = $SizeArray[0] -replace ",","."

If ($SizeArray[1] -contains "bytes") { $Size = [int]$Decimal /1024 }
If ($SizeArray[1] -contains "KB") { $Size = [int]$Decimal }
If ($SizeArray[1] -contains "MB") { $Size = [int]$Decimal * 1024 }
If ($SizeArray[1] -contains "GB") { $Size = [int]$Decimal *1024 *1024 }

$Object = New-Object Psobject -Property @{
Computer= $computer
User = $User
DateRun = $DateRun
Name = $item.Name
Type = $item.Type
SizeKb = $size
Path = $item.path
"Deleted Date" = $DeletedDate_datetime
"Deleted Days" = $DeletedDays }

$Object

If ($DeleteItems)
{
Remove-Item -Path $item.Path -Confirm:$false -Force -Recurse

if ($?)
{
$Global:Collection += @($object)
}
else
{Add-Content -Path $LogFailed -Value $error[0]
}
}
#EndIf $DeleteItems
}#EndIf($DeletedDays -ge $RetentionTime)
}#EndForeach item
}#EndFunction

A few things to notice...

$Recycler.GetDetailsOf($item,2) retrieves the "Date Deleted" in a string format but apparantly there are invisible Unicode characters in the string so you will have to remove them... otherwise the conversion to [DateTime] fails.

$DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e",""

$Recycler.GetDetailsOf($item,3) retrieves the size... also in a string format. e.g. "12.8 Kb". So if you want to do calculation later on you will have to convert them to integers. Doing so rounds the numbers... 12.8 becomes 12

In the script, when the DeleteItems parameter is used with the function reporting is done also.

if (@($collection).count -gt "0")
{
$Collection = $Collection | Select-Object "Computer","User","DateRun","Name","Type","Path","SizeKb","Deleted Days","Deleted Date"
$CsvData = $Collection | ConvertTo-Csv -NoTypeInformation
$Null, $Data = $CsvData
Add-Content -Path $csvfile -Value $Data
}

One thing to notice here is that in Powershell V2 no -Append parameter is available for the Export-Csv parameter. I have V3 installed but user workstations haven't. So I had to use a workaround... use ConverTo-Csv, assign the first line to $Null and use Add-Content to append to the Csv file.

Complete script:

# -----------------------------------------------------------------------
#
#       Author    :   Baldwin D.
#       Description : Empty Recycle Bin with Retention (Logoff Script)
#    
# -----------------------------------------------------------------------

$Global:Collection = @()

$Shell = New-Object -ComObject Shell.Application
$Global:Recycler = $Shell.NameSpace(0xa)

$csvfile = "\\YourNetworkShare\RecycleBin.txt"
$LogFailed = "\\YourNetworkShare\RecycleBinFailed.txt"


function Get-recyclebin
{
    [CmdletBinding()]
    Param
    (
        $RetentionTime = "7",
        [Switch]$DeleteItems
    )

    $User = $env:USERNAME
    $Computer = $env:COMPUTERNAME
    $DateRun = Get-Date

    foreach($item in $Recycler.Items())
        {
        $DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e","" #Invisible Unicode Characters
        $DeletedDate_datetime = get-date $DeletedDate  
        [Int]$DeletedDays = (New-TimeSpan -Start $DeletedDate_datetime -End $(Get-Date)).Days
     
        If($DeletedDays -ge $RetentionTime)
            {
            $Size = $Recycler.GetDetailsOf($item,3)
         
            $SizeArray = $Size -split " "
            $Decimal = $SizeArray[0] -replace ",","."
            If ($SizeArray[1] -contains "bytes") { $Size = [int]$Decimal /1024 }
            If ($SizeArray[1] -contains "KB") { $Size = [int]$Decimal }
            If ($SizeArray[1] -contains "MB") { $Size = [int]$Decimal * 1024 }
            If ($SizeArray[1] -contains "GB") { $Size = [int]$Decimal *1024 *1024 }
           
       $Object = New-Object Psobject -Property @{
                Computer = $computer
                User = $User
                DateRun = $DateRun
                Name = $item.Name
                Type = $item.Type
                SizeKb = $Size
                Path = $item.path
                "Deleted Date" = $DeletedDate_datetime
                "Deleted Days" = $DeletedDays }
           
            $Object

                If ($DeleteItems)
                {
                    Remove-Item -Path $item.Path -Confirm:$false -Force -Recurse
             
                    if ($?)
                    {
                        $Global:Collection += @($object)
                    }
                    else
                    {
                        Add-Content -Path $LogFailed -Value $error[0]
                    }
                }#EndIf $DeleteItems
            }#EndIf($DeletedDays -ge $RetentionTime)
}#EndForeach item
}#EndFunction

Get-recyclebin -RetentionTime 7 #-DeleteItems #Remove the comment if you wish to actually delete the content


if (@($collection).count -gt "0")
{
$Collection = $Collection | Select-Object "Computer","User","DateRun","Name","Type","Path","SizeKb","Deleted Days","Deleted Date"
$CsvData = $Collection | ConvertTo-Csv -NoTypeInformation
$Null, $Data = $CsvData

Add-Content -Path $csvfile -Value $Data
}

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell)

#ScriptEnd

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell) releases the COM object.

You will have to create the 2 log files defined in $Csvfile and $LogFailed on a network share that is accessible to all your users and add the Logoff script in a GPO (User Configuration)

You can then easily parse the logfile to find out what has been deleted from the network and what has been deleted on local drives.

Grts!

My Blog List