Public/Backup-GamConfig.ps1

function Backup-GamConfig {
<#
.SYNOPSIS
    Backs up the entire GAM7 config folder as a single AES-encrypted artifact.
.DESCRIPTION
    Compresses the GAM config directory into a zip archive, then encrypts
    the archive bytes using AES-256 with the provided key.
    Produces a single .encrypted file.
.EXAMPLE
    Backup-GamConfig -KeyFile ./gam-encryption.key
.EXAMPLE
    Backup-GamConfig -GamConfigDir ~/.gam -OutputPath ~/backups/gam-backup.encrypted
.OUTPUTS
    File: gam-config-backup.encrypted (or custom name via -OutputPath)
#>

  [CmdletBinding()]
  param(
    [Parameter()]
    [string]$GamConfigDir,

    [Parameter()]
    [string]$KeyFile = (Join-Path (Get-Location) 'gam-encryption.key'),

    [Parameter()]
    [string]$OutputPath = (Join-Path (Get-Location) 'gam-config-backup.encrypted'),

    [Parameter()]
    [switch]$Force
  )

  $activity = 'Backup-GamConfig'
    
  if (-not $GamConfigDir) {
    $GamConfigDir = $env:GAMCFGDIR
    if (-not $GamConfigDir) {
      $GamConfigDir = Join-Path $HOME '.gam'
    }
  }

  Write-Verbose "$activity : $GamConfigDir"

  if (-not (Test-Path $GamConfigDir)) {
    Write-Warning "GAM config directory not found: $GamConfigDir"
    return
  }

  if (-not (Test-Path $KeyFile)) {
    Write-Warning "Encryption key not found: $KeyFile. Run New-GamEncryptionKey first."
    return
  }

  Write-Progress -Activity $activity -Status 'Loading encryption key...' -PercentComplete 10
    
  $keyBase64 = [System.IO.File]::ReadAllText($KeyFile).Trim()
  [byte[]]$key = [Convert]::FromBase64String($keyBase64)

  if ($key.Length -notin @(16, 24, 32)) {
    Write-Warning "Invalid key length: $($key.Length) bytes. Must be 16, 24, or 32."
    return
  }

  if ((Test-Path $OutputPath) -and -not $Force) {
    Write-Warning "Output file already exists: $OutputPath. Use -Force to overwrite."
    return
  }

  $tempZip = Join-Path ([System.IO.Path]::GetTempPath()) "gam-backup-$(Get-Date -Format 'yyyyMMddHHmmss').zip"

  try {
    Write-Progress -Activity $activity -Status 'Compressing GAM config folder...' -PercentComplete 30
    Compress-Archive -Path (Join-Path $GamConfigDir '*') -DestinationPath $tempZip -CompressionLevel Optimal -Force

    Write-Progress -Activity $activity -Status 'Encrypting backup...' -PercentComplete 60
    $zipBytes = [System.IO.File]::ReadAllBytes($tempZip)
    $encryptedBytes = Protect-GamData -PlainBytes $zipBytes -AesKey $key

    Write-Progress -Activity $activity -Status 'Writing encrypted backup...' -PercentComplete 85
    $outputDir = Split-Path $OutputPath -Parent
    if ($outputDir -and -not (Test-Path $outputDir)) {
      New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
    }
    [System.IO.File]::WriteAllBytes($OutputPath, $encryptedBytes)

    Write-Progress -Activity $activity -Completed

    $sizeMB = [math]::Round((Get-Item $OutputPath).Length / 1MB, 2)

    [PSCustomObject]@{
      SourceDir  = $GamConfigDir
      OutputPath = $OutputPath
      SizeMB     = $sizeMB
      Encrypted  = 'Yes'
    } | Format-List
  }
  finally {
    if (Test-Path $tempZip) {
      Remove-Item $tempZip -Force
    }
  }
}