<# .SYNOPSIS Backup Exchange certificates to PFX files .DESCRIPTION Exports all Exchange certificates to password-protected PFX files. Alerts on certificates expiring within specified days. .PARAMETER OutputFolder Destination folder for PFX exports. Default: .\CertBackup- .PARAMETER Password Password for PFX files (required) .PARAMETER ExpiryWarningDays Alert on certificates expiring within this many days (default: 90) .PARAMETER IncludePrivateKey Export with private key (required for restoration, default: $true) .NOTES ⚠️ AI-GENERATED SCRIPT - UNTESTED This script was generated by Claude AI and has not been tested in production. Review and test thoroughly in a non-production environment before use. - Run in Exchange Management Shell with appropriate RBAC permissions - Requires password for PFX protection - Store PFX files securely - they contain private keys - Tested compatibility: Exchange 2013/2016/2019 (not validated) .EXAMPLE $pwd = Read-Host "Enter PFX password" -AsSecureString .\Backup-ExchangeCertificates.ps1 -Password $pwd .EXAMPLE $pwd = ConvertTo-SecureString "P@ssw0rd!" -AsPlainText -Force .\Backup-ExchangeCertificates.ps1 -Password $pwd -ExpiryWarningDays 60 #> [CmdletBinding()] param( [string]$OutputFolder = (Join-Path -Path (Get-Location) -ChildPath ("CertBackup-" + (Get-Date -Format "yyyyMMdd-HHmm"))), [Parameter(Mandatory = $true)] [System.Security.SecureString]$Password, [int]$ExpiryWarningDays = 90, [bool]$IncludePrivateKey = $true ) function NowTag { (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") } Write-Host "[$(NowTag)] ⚠️ AI-GENERATED SCRIPT - UNTESTED" -ForegroundColor Yellow Write-Host "[$(NowTag)] Starting Exchange certificate backup..." -ForegroundColor Green # Create output folder New-Item -ItemType Directory -Path $OutputFolder -Force | Out-Null Write-Host "[$(NowTag)] Output folder: $OutputFolder" # Get all Exchange certificates Write-Host "[$(NowTag)] Retrieving Exchange certificates..." $certs = Get-ExchangeCertificate -ErrorAction SilentlyContinue if (-not $certs) { Write-Host "[$(NowTag)] No Exchange certificates found" -ForegroundColor Yellow exit 0 } $certCount = ($certs | Measure-Object).Count Write-Host "[$(NowTag)] Found $certCount certificate(s)" # Process each certificate $exported = @() $warnings = @() foreach ($cert in $certs) { $thumbprint = $cert.Thumbprint $subject = $cert.Subject $notAfter = $cert.NotAfter $daysUntilExpiry = [int](($notAfter - (Get-Date)).TotalDays) Write-Host "[$(NowTag)] Processing: $subject (Thumbprint: $thumbprint)" # Check expiry $status = "OK" if ($daysUntilExpiry -lt 0) { $status = "EXPIRED" $warnings += [PSCustomObject]@{ Thumbprint = $thumbprint Subject = $subject NotAfter = $notAfter DaysRemaining = $daysUntilExpiry Status = $status } Write-Host " WARNING: Certificate EXPIRED on $($notAfter.ToString('yyyy-MM-dd'))" -ForegroundColor Red } elseif ($daysUntilExpiry -lt $ExpiryWarningDays) { $status = "EXPIRING SOON" $warnings += [PSCustomObject]@{ Thumbprint = $thumbprint Subject = $subject NotAfter = $notAfter DaysRemaining = $daysUntilExpiry Status = $status } Write-Host " WARNING: Certificate expires in $daysUntilExpiry days ($($notAfter.ToString('yyyy-MM-dd')))" -ForegroundColor Yellow } else { Write-Host " Status: OK - Expires in $daysUntilExpiry days" } # Export to PFX $fileName = "$($thumbprint).pfx" $filePath = Join-Path $OutputFolder $fileName try { if ($IncludePrivateKey) { # Export with private key Export-ExchangeCertificate -Thumbprint $thumbprint -FileName $filePath -Password $Password -ErrorAction Stop | Out-Null Write-Host " Exported: $fileName (with private key)" -ForegroundColor Green } else { # Export public key only (using .NET method) $certObj = Get-Item -Path "Cert:\LocalMachine\My\$thumbprint" -ErrorAction SilentlyContinue if ($certObj) { $certBytes = $certObj.Export('Cert') [System.IO.File]::WriteAllBytes($filePath, $certBytes) Write-Host " Exported: $fileName (public key only)" -ForegroundColor Green } else { throw "Certificate not found in LocalMachine\My" } } $exported += [PSCustomObject]@{ Thumbprint = $thumbprint Subject = $subject Services = ($cert.Services -join ", ") NotBefore = $cert.NotBefore NotAfter = $notAfter DaysRemaining = $daysUntilExpiry Status = $status FileName = $fileName FilePath = $filePath FileSize = (Get-Item $filePath).Length } } catch { Write-Host " ERROR exporting certificate: $($_.Exception.Message)" -ForegroundColor Red } } # Export inventory $inventoryFile = Join-Path $OutputFolder "Certificate-Inventory.csv" $exported | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $inventoryFile Write-Host "`n[$(NowTag)] Inventory exported: $inventoryFile" -ForegroundColor Green # Export warnings if any if ($warnings.Count -gt 0) { $warningsFile = Join-Path $OutputFolder "Certificate-Warnings.csv" $warnings | Sort-Object DaysRemaining | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $warningsFile Write-Host "[$(NowTag)] Warnings exported: $warningsFile" -ForegroundColor Yellow } # Summary Write-Host "`nBACKUP SUMMARY:" -ForegroundColor Cyan Write-Host " Total Certificates: $certCount" Write-Host " Successfully Exported: $($exported.Count)" -ForegroundColor Green Write-Host " Failed: $($certCount - $exported.Count)" -ForegroundColor $(if ($certCount - $exported.Count -gt 0) { "Red" } else { "Green" }) Write-Host " Expired: $(($warnings | Where-Object Status -eq 'EXPIRED').Count)" -ForegroundColor Red Write-Host " Expiring Soon: $(($warnings | Where-Object Status -eq 'EXPIRING SOON').Count)" -ForegroundColor Yellow if ($warnings.Count -gt 0) { Write-Host "`nCERTIFICATE WARNINGS:" -ForegroundColor Yellow $warnings | Sort-Object DaysRemaining | ForEach-Object { $color = if ($_.Status -eq "EXPIRED") { "Red" } else { "Yellow" } Write-Host " $($_.Status): $($_.Subject) - $($_.DaysRemaining) days" -ForegroundColor $color } } Write-Host "`n[$(NowTag)] Backup complete! Output folder: $OutputFolder" Write-Host "`nIMPORTANT: Store PFX files securely - they contain private keys!" -ForegroundColor Cyan