Public/Restore-PWSHPUKMGT-Data.ps1

<#
.SYNOPSIS
    Restores PWSHPUKMGT records from a CMS-encrypted backup file.
.DESCRIPTION
    Decrypts a backup file produced by Backup-PWSHPUKMGT-Data and writes its records into the
    currently configured hosting backend. Restore only targets the same hosting model the
    backup was taken from -- restoring a flat-file backup into an Active Directory-configured
    instance (or vice versa) is not supported and fails fast.
.PARAMETER Path
    Path to the encrypted backup file.
.PARAMETER Force
    Overwrite existing records for identities already present in the target backend.
.PARAMETER Server
    The domain controller to run the underlying LDAP requests against. Defaults to the value
    configured in hosting.activeDirectory.server, or, if that is not set either, to the primary
    domain controller (PDC emulator) of the current domain.
.EXAMPLE
    Restore-PWSHPUKMGT-Data -Path 'D:\Backups\pukdata-2026-07-07.json.cms'
#>

function Restore-PWSHPUKMGT-Data {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Path,

        [switch]$Force,

        [string]$Server
    )

    if (-not (Test-Path -LiteralPath $Path)) {
        throw "Backup file not found at '$Path'."
    }

    $config = Get-PukModuleConfig
    $certificate = Test-PukCertificate -Thumbprint $config.certificate.thumbprint -StoreLocation $config.certificate.storeLocation -RequiredEkuOids $config.certificate.requiredEkuOids
    $effectiveServer = Resolve-PukServer -Server $Server -Config $config

    $encrypted = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
    $backup = Unprotect-PukFlatFileData -Content $encrypted -Certificate $certificate

    if ($backup.hostingModel -ne $config.hosting.model) {
        throw "Backup file was taken from hosting model '$($backup.hostingModel)', but the current configuration is '$($config.hosting.model)'. Cross-backend restore is not supported."
    }

    $records = @($backup.records)

    if (-not $PSCmdlet.ShouldProcess($Path, "Restore $($records.Count) PWSHPUKMGT record(s) into the '$($config.hosting.model)' backend")) {
        return
    }

    $restoredCount = 0
    foreach ($record in $records) {
        try {
            $recordObject = [PSCustomObject]@{
                DistinguishedName = $record.DistinguishedName
                SerialNumber      = $record.SerialNumber
                Puk               = $record.Puk
            }
            Set-PukRecord -Config $config -Certificate $certificate -Record $recordObject -Server $effectiveServer -Force:$Force -Confirm:$false
            $restoredCount++
        }
        catch {
            Write-Error "Failed to restore record for '$($record.DistinguishedName)': $($_.Exception.Message)"
            Write-PukLog -Level Warning -Config $config -Message "Restore-PWSHPUKMGT-Data: failed to restore record for '$($record.DistinguishedName)' from '$Path': $($_.Exception.Message)"
        }
    }

    Write-PukLog -Level Info -Config $config -Message "Restore-PWSHPUKMGT-Data: restored $restoredCount of $($records.Count) record(s) from '$Path' into the '$($config.hosting.model)' backend."
}