Public/disk/Get-RecycleBinSize.ps1

#Requires -Version 5.1
function Get-RecycleBinSize {
    <#
    .SYNOPSIS
        Reports Recycle Bin size and item count per fixed volume
 
    .DESCRIPTION
        Enumerates fixed volumes via CIM and measures the contents of each
        volume's hidden $Recycle.Bin folder, returning one object per volume
        with the exact byte size, rounded MB/GB, item count, and the size as a
        percentage of the volume. Local calls run in-process (no WinRM needed);
        remote calls dispatch over WinRM through Invoke-RemoteOrLocal, and only
        the per-volume summary rows cross the wire.
 
    .PARAMETER ComputerName
        One or more computer names to query. Defaults to the local computer.
        Accepts pipeline input by value and by property name.
 
    .PARAMETER Credential
        Optional PSCredential for authenticating to remote computers.
        Not used for local queries.
 
    .EXAMPLE
        Get-RecycleBinSize
 
        Reports the Recycle Bin size for every fixed volume on the local computer.
 
    .EXAMPLE
        Get-RecycleBinSize -ComputerName 'SRV01'
 
        Reports the Recycle Bin size for every fixed volume on SRV01.
 
    .EXAMPLE
        'SRV01', 'SRV02' | Get-RecycleBinSize
 
        Reports Recycle Bin sizes for multiple servers via the pipeline.
 
    .OUTPUTS
        PSWinOps.RecycleBinSize
        Returns one object per fixed volume with the byte size, rounded MB/GB,
        item count, and the size as a percentage of the volume.
 
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-09-18
        Requires: PowerShell 5.1+ / Windows only
        Requires: Administrator rights to read other users' Recycle Bins. A
        non-elevated caller can read only its own SID folder under $Recycle.Bin,
        so the reported size can be understated. This is not gated on elevation.
 
    .LINK
        https://github.com/k9fr4n/PSWinOps
 
    .LINK
        https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-logicaldisk
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.RecycleBinSize')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'Name', 'DNSHostName')]
        [string[]]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential
    )

    begin {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting"

        $scriptBlock = {
            $volumes = @(Get-CimInstance -ClassName 'Win32_LogicalDisk' -Filter 'DriveType = 3' -ErrorAction Stop)

            foreach ($volume in $volumes) {
                # $Recycle.Bin is hidden + system, so -Force is required. A volume
                # with no (or an unreadable) Recycle Bin is a normal state: the
                # SilentlyContinue swallows access-denied and missing-path errors,
                # leaving a zero size rather than skipping the volume.
                $recyclePath = "$($volume.DeviceID)\`$Recycle.Bin"
                $measure = Get-ChildItem -LiteralPath $recyclePath -Force -File -Recurse -ErrorAction SilentlyContinue |
                    Measure-Object -Property Length -Sum

                [PSCustomObject]@{
                    DriveLetter = $volume.DeviceID
                    SizeBytes   = [long]$measure.Sum
                    ItemCount   = [long]$measure.Count
                    VolumeSize  = [long]$volume.Size
                }
            }
        }
    }

    process {
        foreach ($machine in $ComputerName) {
            try {
                Write-Verbose -Message "[$($MyInvocation.MyCommand)] Measuring Recycle Bins on '$machine'"
                $summaries = @(Invoke-RemoteOrLocal -ComputerName $machine -ScriptBlock $scriptBlock -Credential $Credential)

                foreach ($summary in $summaries) {
                    $sizeBytes = [long]$summary.SizeBytes
                    $volumeSize = [long]$summary.VolumeSize

                    $percentOfVolume = if ($volumeSize -gt 0) {
                        [math]::Round(($sizeBytes / $volumeSize) * 100, 2)
                    }
                    else { 0 }

                    [PSCustomObject]@{
                        PSTypeName      = 'PSWinOps.RecycleBinSize'
                        ComputerName    = $machine
                        DriveLetter     = $summary.DriveLetter
                        SizeBytes       = $sizeBytes
                        SizeMB          = [math]::Round($sizeBytes / 1MB, 2)
                        SizeGB          = [math]::Round($sizeBytes / 1GB, 2)
                        ItemCount       = [long]$summary.ItemCount
                        PercentOfVolume = $percentOfVolume
                        Timestamp       = Get-Date -Format 'o'
                    }
                }
            }
            catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed on '${machine}': $_"
                continue
            }
        }
    }

    end {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed"
    }
}