Public/Restore-DailyBackup.ps1

function Restore-DailyBackup
{
    <#
    .SYNOPSIS
        Restores files and directories from daily backup archives.

    .DESCRIPTION
        Restores backed up files and directories from compressed ZIP archives created
        by New-DailyBackup. Supports restoring specific backups by date, individual
        files by name pattern, or entire backup sets. Can restore to original locations
        using metadata or to custom destinations.

    .PARAMETER BackupRoot
        The root directory containing daily backup folders (yyyy-MM-dd format).
        This should be the same directory used as -Destination in New-DailyBackup.

    .PARAMETER DestinationPath
        The destination directory where restored files will be placed.
        If not specified and -UseOriginalPaths is enabled, attempts to restore
        to original source locations using metadata.

    .PARAMETER Date
        Specific backup date to restore from (yyyy-MM-dd format).
        If not specified, uses the most recent backup date available.

    .PARAMETER BackupName
        Optional pattern to match specific backup files by name.
        Supports wildcards (e.g., "*Documents*", "*.pdf*").
        If not specified, restores all backups from the specified date.

    .PARAMETER UseOriginalPaths
        When enabled, attempts to restore files to their original source locations
        using metadata information. Requires metadata files to be present.
        When disabled, restores all files to the specified DestinationPath.

    .PARAMETER PreservePaths
        Controls whether directory structure within backups is preserved during
        restoration. When enabled, maintains folder hierarchy from the backup.

    .PARAMETER Force
        Overwrites existing files during restoration without prompting.
        Use with caution as this can replace current files.

    .INPUTS
        None. This function does not accept pipeline input.

    .OUTPUTS
        [PSCustomObject[]]
        Returns an array of restore operation results, including success status,
        paths processed, and any errors encountered for each backup file.

    .NOTES
        - Requires backup files created by New-DailyBackup with version 2.0+ metadata
        - Supports ShouldProcess for WhatIf and Confirm scenarios
        - Automatically handles file timestamp and attribute restoration when possible
        - Creates destination directories as needed
        - Provides detailed progress reporting for multiple files

    .EXAMPLE
        PS > Restore-DailyBackup -BackupRoot 'D:\Backups' -DestinationPath 'C:\Restored'

        Restores the most recent backup set to C:\Restored

    .EXAMPLE
        PS > Restore-DailyBackup -BackupRoot 'D:\Backups' -Date '2025-09-15' -UseOriginalPaths

        Restores backups from September 15, 2025 to their original source locations

    .EXAMPLE
        PS > Restore-DailyBackup -BackupRoot 'D:\Backups' -BackupName '*Documents*' -DestinationPath 'C:\Restored'

        Restores only backup files matching "*Documents*" pattern

    .EXAMPLE
        PS > Restore-DailyBackup -BackupRoot 'D:\Backups' -Date '2025-09-10' -WhatIf

        Shows what would be restored without actually performing the restoration

    .EXAMPLE
        PS > Restore-DailyBackup -BackupRoot '\\server\backups' -DestinationPath 'C:\Emergency' -Force

        Restores from network backup location, overwriting existing files

    .LINK
        New-DailyBackup
        Get-BackupInfo
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    param(
        [Parameter(
            Mandatory = $true,
            HelpMessage = 'The root directory containing daily backup folders.'
        )]
        [ValidateNotNullOrEmpty()]
        [string] $BackupRoot,

        [Parameter(
            HelpMessage = 'The destination directory where restored files will be placed.'
        )]
        [string] $DestinationPath,

        [Parameter(
            HelpMessage = 'Specific backup date to restore from (yyyy-MM-dd format).'
        )]
        [ValidatePattern('^\d{4}-\d{2}-\d{2}$')]
        [string] $Date,

        [Parameter(
            HelpMessage = 'Pattern to match specific backup files by name (supports wildcards).'
        )]
        [string] $BackupName,

        [Parameter(
            HelpMessage = 'Restore files to their original source locations using metadata.'
        )]
        [switch] $UseOriginalPaths,

        [Parameter(
            HelpMessage = 'Preserve directory structure within backups during restoration.'
        )]
        [switch] $PreservePaths,

        [Parameter(
            HelpMessage = 'Overwrite existing files during restoration without prompting.'
        )]
        [switch] $Force
    )

    begin
    {
        $verboseEnabled = ($VerbosePreference -eq 'Continue')

        if (-not $UseOriginalPaths -and -not $DestinationPath)
        {
            throw 'Either DestinationPath must be specified or UseOriginalPaths must be enabled'
        }

        if (-not (Test-Path $BackupRoot -PathType Container))
        {
            throw "Backup root directory not found: $BackupRoot"
        }

        Write-Verbose "Restore-DailyBackup:Begin> Starting restore operation from $BackupRoot"
    }

    process
    {
        $backupInfo = Get-BackupInfo -BackupRoot $BackupRoot
        if ($Date)
        {
            $backupInfo = $backupInfo | Where-Object { $_.Date -eq $Date }
        }

        if ($backupInfo.Count -eq 0)
        {
            Write-Warning "No backups found in $BackupRoot$(if ($Date) { " for date $Date" })"
            return @()
        }

        # Use most recent backup if no date specified
        if (-not $Date)
        {
            $selectedBackup = $backupInfo | Sort-Object Date -Descending | Select-Object -First 1
            Write-Verbose "Restore-DailyBackup> Using most recent backup from $($selectedBackup.Date)"
        }
        else
        {
            $selectedBackup = $backupInfo | Where-Object { $_.Date -eq $Date } | Select-Object -First 1
            if (-not $selectedBackup)
            {
                throw "No backup found for date: $Date"
            }
        }

        # Filter backups by name pattern if specified
        $backupsToRestore = @(if ($BackupName)
            {
                $selectedBackup.Backups | Where-Object { $_.Name -like $BackupName }
            }
            else
            {
                $selectedBackup.Backups
            })

        if ($backupsToRestore.Count -eq 0)
        {
            Write-Warning 'No backup files match the specified criteria'
            return @()
        }

        Write-Host "Found $($backupsToRestore.Count) backup file(s) to restore from $($selectedBackup.Date)" -ForegroundColor Green

        # Process each backup file
        $results = @()
        $totalBackups = $backupsToRestore.Count
        $currentBackup = 0

        foreach ($backup in $backupsToRestore)
        {
            $currentBackup++
            Write-Progress -Activity 'Restoring Daily Backups' -Status "Restoring backup $currentBackup of $totalBackups" -PercentComplete (($currentBackup / $totalBackups) * 100)

            try
            {
                $restoreParams = @{
                    BackupFilePath = $backup.Path
                    UseOriginalPath = $UseOriginalPaths
                    PreservePaths = $PreservePaths
                    VerboseEnabled = $verboseEnabled
                }

                if ($DestinationPath)
                {
                    $restoreParams.DestinationPath = $DestinationPath
                }

                if ($Force)
                {
                    $restoreParams.Force = $true
                }

                $result = Restore-BackupFile @restoreParams
                $results += $result

                if ($result.Success)
                {
                    Write-Host "[SUCCESS] $($result.Message)" -ForegroundColor Green
                }
                else
                {
                    Write-Warning "[FAILED] $($result.Message)"
                }
            }
            catch
            {
                $errorResult = [PSCustomObject]@{
                    Success = $false
                    SourcePath = $backup.Path
                    DestinationPath = $DestinationPath
                    Metadata = $backup.Metadata
                    Message = "Failed to restore $($backup.Name): $_"
                }
                $results += $errorResult
                Write-Error $errorResult.Message -ErrorAction Continue
            }
        }

        Write-Progress -Activity 'Restoring Daily Backups' -Completed
    }

    end
    {
        $successfulResults = @($results | Where-Object { $_.Success })
        $successCount = $successfulResults.Count
        $totalCount = $results.Count

        Write-Host "`nRestore Summary:" -ForegroundColor Cyan
        Write-Host " Successful: $successCount" -ForegroundColor Green
        Write-Host " Failed: $($totalCount - $successCount)" -ForegroundColor Red
        Write-Host " Total: $totalCount" -ForegroundColor Blue

        Write-Verbose 'Restore-DailyBackup:End> Restore operation completed'
        return $results
    }
}