Recycle.psm1

#Set-StrictMode -Version Latest

function Remove-ItemSafely {

    [CmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess = $true, ConfirmImpact = 'Medium', SupportsTransactions = $true, HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113373')]
    param(
        [Parameter(ParameterSetName = 'Path', Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]
        ${Path},

        [Parameter(ParameterSetName = 'LiteralPath', Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('PSPath')]
        [string[]]
        ${LiteralPath},

        [string]
        ${Filter},

        [string[]]
        ${Include},

        [string[]]
        ${Exclude},

        [switch]
        ${Recurse},

        [switch]
        ${Force},

        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${Credential},

        [switch]
        $DeletePermanently)


    begin {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                $PSBoundParameters['OutBuffer'] = 1
            }
            if ($DeletePermanently -or @($PSBoundParameters.Keys | Where-Object { @('Filter', 'Include', 'Exclude', 'Recurse', 'Force', 'Credential') -contains $_ }).Count -ge 1) {
                if ($PSBoundParameters['DeletePermanently']) {
                    $PSBoundParameters.Remove('DeletePermanently') | Out-Null
                }

                $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Remove-Item', [System.Management.Automation.CommandTypes]::Cmdlet)
                $scriptCmd = { & $wrappedCmd @PSBoundParameters }
            }
            else {
                $scriptCmd = { & recycleItem @PSBoundParameters }
            }

            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        }
        catch {
            throw
        }
    }

    process {
        try {
            $steppablePipeline.Process($_)
        }
        catch {
            throw
        }
    }

    end {
        try {
            $steppablePipeline.End()
        }
        catch {
            throw
        }
    }
    <#

.ForwardHelpTargetName Microsoft.PowerShell.Management\Remove-Item
.ForwardHelpCategory Cmdlet

#>


}

function recycleItem {
    [CmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess = $true, ConfirmImpact = 'Medium', SupportsTransactions = $true, HelpUri = 'http://go.microsoft.com/fwlink/?LinkID=113373')]
    param(
        [Parameter(ParameterSetName = 'Path', Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]
        ${Path},

        [Parameter(ParameterSetName = 'LiteralPath', Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('PSPath')]
        [string[]]
        ${LiteralPath})

    process {
        try {
            if ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
                $items = @(Get-Item -LiteralPath:$PSBoundParameters['LiteralPath'])
            }
            else {
                $items = @(Get-Item -Path:$PSBoundParameters['Path'])
            }

            foreach ($item in $items) {
                if ($PSCmdlet.ShouldProcess($item)) {
                    $directoryPath = Split-Path $item -Parent

                    $shell = New-Object -ComObject "Shell.Application"
                    $shellFolder = $shell.Namespace($directoryPath)
                    $shellItem = $shellFolder.ParseName($item.Name)
                    $shellItem.InvokeVerb("delete")
                }
            }
        }
        catch {
            throw
        }
    }
}

function Restore-RecycledItem {
    [CmdletBinding(DefaultParameterSetName = 'ManualSelection', SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Position = 0, ParameterSetName = 'ComObject', Mandatory, ValueFromPipeline)]
        [System.__ComObject]
        $ComObject,

        [Parameter(Position = 0, ParameterSetName = 'ManualSelection', Mandatory)]
        [Parameter(Position = 0, ParameterSetName = 'Selector', Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]
        $OriginalPath,

        [Parameter(Position = 1, ParameterSetName = 'ManualSelection')]
        [ValidateSet('Application', 'GetFolder', 'GetLink', 'IsBrowsable', 'IsFileSystem', 'IsFolder', 'IsLink', 'ModifyDate', 'Name', 'Parent', 'Path', 'Size', 'Type')]
        [Alias('Criteria', 'Property')]
        [String]
        $SortingCriteria = 'ModifyDate',

        [Parameter(Position = 2, ParameterSetName = 'ManuelSelection')]
        [Alias('Desc')]
        [Switch]
        $Descending,

        [Parameter(Position = 1, ParameterSetName = 'Selector')]
        [Alias('Selector', 'Script', 'Lambda', 'Filter')]
        [ValidateNotNull()]
        [ScriptBlock]
        $SelectorScript,

        [Parameter(ParameterSetName = 'ManualSelection')]
        [Parameter(ParameterSetName = 'Selector')]
        [Parameter(ParameterSetName = 'ComObject')]
        [Switch]
        $Overwrite
    )

    process {
        if ($ComObject) {
            $FoundItem = $ComObject
            $OriginalPath = $ComObject.GetFolder.Title
        }

        if ((Test-Path $OriginalPath) -and -not $Overwrite) {
            if ((Get-Item $OriginalPath) -is [System.IO.DirectoryInfo]) {
                Write-Error "Directory already exists and -Overwrite is not specified"
            }
            else {
                Write-Error "File already exists and -Overwrite is not specified"
            }
        }
        else {
            if ($PSCmdlet.ParameterSetName -eq "ManualSelection" -or $PSCmdlet.ParameterSetName -eq "Selector") {
                $BoundParametersLessOverwrite = $PSBoundParameters
                if ($BoundParametersLessOverwrite.ContainsKey("Overwrite")) {
                    $BoundParametersLessOverwrite.Remove("Overwrite") | Out-Null
                }
                $FoundItem = Get-RecycledItem @PSBoundParameters -Top 1
            }

            if ($FoundItem) {
                # This does not seem to work, so I am doing it manually
                # Maybe someone can get this to work (although I don't see an advantage over the current method)
                #(New-Object -ComObject "Shell.Application").Namespace($BinItems[0].Path).Self().InvokeVerb("Restore")
                if ($Overwrite -or $PSBoundParameters['Force']) {
                    Remove-ItemSafely $OriginalPath
                }
                Move-Item $FoundItem.Path $OriginalPath
            }
            else {
                Write-Error "No item in recycle bin with the specified path found"
            }
        }

        return Get-Item $OriginalPath -ErrorAction SilentlyContinue
    }

    <#
.SYNOPSIS
    Restores a file from the Recycle Bin.
.DESCRIPTION
    Finds the item(s) in the Recycle Bin with the given path, selects one based on the given selector (default is newest), and restores it to the original location.
.PARAMETER OriginalPath
    The original path to the file to restore.
.PARAMETER Overwrite
    Whether to overwrite the file at the path if it exists.
.PARAMETER SortingCriteria
    How to sort the items to find which to restore.
.PARAMETER Descending
    Whether the SortingCriteria sort should be descending or ascending.
.PARAMETER SelectorScript
    A script block which determines which item to restore.
.INPUTS
    System.__ComObject The result of calling Get-RecycledItem
.OUTPUTS
    System.Object Return the item that was restored.
.EXAMPLE
    Restore-Item "C:\TestFolder\TestFile.txt"
.EXAMPLE
    Restore-Item "C:\TestFolder\TestFile.txt" -SortingCriteria "Size" -Descending
.EXAMPLE
    Restore-Item "C:\TestFolder\TestFile.txt" -SelectorScript { $_.ModifyDate -eq '01.01.1970' }
.NOTES
    Credit for this approach: https://jdhitsolutions.com/blog/powershell/7024/managing-the-recycle-bin-with-powershell/
.NOTES
    Author: Kevin Holtkamp, kevinholtkamp26@gmail.com
    LastEdit: 09.07.2022
#>

}

function Get-RecycledItem {
    [CmdletBinding(DefaultParameterSetName = 'OriginalPath')]
    param(
        [Parameter(Position = 0, ValueFromPipeline, ParameterSetName = 'OriginalPath')]
        [String]
        $OriginalPath,

        [Parameter(Position = 1, ParameterSetName = 'OriginalPathRegex')]
        [String]
        $OriginalPathRegex,

        [Parameter(Position = 2)]
        [ValidateSet('Application', 'GetFolder', 'GetLink', 'IsBrowsable', 'IsFileSystem', 'IsFolder', 'IsLink', 'ModifyDate', 'Name', 'Parent', 'Path', 'Size', 'Type')]
        [Alias('Criteria', 'Property')]
        [String]
        $SortingCriteria = 'ModifyDate',

        [Parameter(Position = 3)]
        [Alias('Desc')]
        [Switch]
        $Descending,

        [Parameter(Position = 4)]
        [ValidateScript({ $_ -gt 0 })]
        [Int16]
        $Top,

        [Parameter(Position = 1)]
        [Alias('Selector', 'Script', 'Lambda', 'Filter')]
        [ValidateNotNull()]
        [ScriptBlock]
        $SelectorScript
    )

    process {
        $SelectedItems = @() + (New-Object -com shell.application).Namespace(10).Items()

        if ($OriginalPath) {
            $SelectedItems = $SelectedItems | Where-Object { $_.GetFolder.Title -eq $OriginalPath }
        }

        if ($OriginalPathRegex) {
            $SelectedItems = $SelectedItems | Where-Object { $_.GetFolder.Title -match $OriginalPathRegex }
        }

        if ($SelectorScript) {
            $SelectedItems = $SelectedItems | Where-Object { Invoke-Command $SelectorScript -ArgumentList $_ }
        }

        if ($SortingCriteria) {
            $SelectedItems = $SelectedItems | Sort-Object $SortingCriteria -Descending:$Descending
        }

        if ($Top) {
            $SelectedItems = $SelectedItems | Select-Object -First $Top
        }

        return $SelectedItems
    }
    <#
.SYNOPSIS
    Get all items from the recycle bin, optionally filtered by the parameters
.DESCRIPTION
    Get all items from the recycle bin, optionally filtered by the parameters
.PARAMETER OriginalPath
    Filters recycle bin items by their original path
.PARAMETER OriginalPathRegex
    Filters recycle bin items by their original path with a regex
.PARAMETER SortingCriteria
    Sort output by the specified criteria
.PARAMETER Descending
    Sort output descending instead of ascending
.PARAMETER Top
    Only get top n results
.PARAMETER SelectorScript
    Custom script to filter the results
.INPUTS
    System.String The OriginalPath to search for
.OUTPUTS
    System.__ComObject The recycle bin items
.EXAMPLE
    Get-RecycledItems -OriginalPath "C:\Users\Kevin\Testfile"
.EXAMPLE
    Get-RecycledItems -SortingCriteria "Size" -Descending -Top 5
.EXAMPLE
    Get-RecycledItems -SelectorScript { $_.IsFolder -eq $true }
.NOTES
    Author: Kevin Holtkamp, kevinholtkamp26@gmail.com
    LastEdit: 09.07.2022
#>

}

Export-ModuleMember -Function Get-RecycledItem
Export-ModuleMember -Function Remove-ItemSafely
Export-ModuleMember -Function Restore-RecycledItem