Private/GpoBackupHelpers.ps1

<#
    GPO backup helpers (from PoSHRepo Windows\GPO\ReplaceImportGPO.ps1 + ActiveDirectory\GPO\ExportGPO.ps1).
    A "portable" backup has its source domain replaced by placeholders, in this order:
        DC.contoso.com <- the domain controller FQDN the backup was taken on (PDC emulator)
        contoso.com <- the DNS domain
        contoso <- the NetBIOS domain
    Order matters both ways: the DC FQDN contains the DNS name, and the DNS name contains the
    NetBIOS name in most domains.
#>


$script:NSPADGpoPlaceholder = [ordered]@{ Pdc = 'DC.contoso.com'; Dns = 'contoso.com'; NetBios = 'contoso' }
# The files Backup-GPO writes that carry domain names; manifest.xml sits beside the {GUID} folder.
$script:NSPADGpoBackupFiles = @('gpreport.xml', 'Backup.xml', 'bkupInfo.xml')

function Get-NSPADGpoBackupInfo {
    # One backup ({GUID} folder holding bkupInfo.xml): display name, backup ID, GPO GUID, time, domain.
    param([Parameter(Mandatory)][string]$BackupFolder)
    $info = Get-ChildItem -LiteralPath $BackupFolder -Filter 'bkupInfo.xml' -Force -File -ErrorAction SilentlyContinue | Select-Object -First 1
    if (-not $info) { return $null }
    try { $x = [xml](Get-Content -LiteralPath $info.FullName -Raw) } catch { return $null }
    $bi = $x.DocumentElement
    $text = { param($n) $node = $bi.ChildNodes | Where-Object { $_.LocalName -eq $n } | Select-Object -First 1; if ($node) { "$($node.InnerText)".Trim() } }
    [pscustomobject]@{
        DisplayName  = & $text 'GPODisplayName'
        BackupId     = & $text 'ID'
        GpoGuid      = & $text 'GPOGuid'
        BackupTime   = & $text 'BackupTime'
        SourceDomain = & $text 'GPODomain'
        Comment      = & $text 'Comment'
        BackupFolder = (Get-Item -LiteralPath $BackupFolder).FullName
        BackupRoot   = (Split-Path -Parent (Get-Item -LiteralPath $BackupFolder).FullName)
    }
}

function Get-NSPADGpoDomainNames {
    # This domain's names in placeholder order: Pdc, Dns, NetBios.
    $d = Get-ADDomain -ErrorAction Stop
    [ordered]@{ Pdc = "$($d.PDCEmulator)"; Dns = "$($d.DNSRoot)"; NetBios = "$($d.NetBIOSName)" }
}

function Convert-NSPADGpoBackupText {
    <#
        PURE. Replaces each -From value with the matching -To value (keys Pdc, Dns, NetBios, in that
        order) in -Text. Case-insensitive, so a backup taken on CONTOSO.COM still matches contoso.com.
    #>

    param([AllowEmptyString()][string]$Text, [Parameter(Mandatory)][Collections.IDictionary]$From, [Parameter(Mandatory)][Collections.IDictionary]$To)
    foreach ($key in 'Pdc', 'Dns', 'NetBios') {
        if ([string]::IsNullOrEmpty($From[$key]) -or $null -eq $To[$key]) { continue }
        $Text = [regex]::Replace($Text, [regex]::Escape([string]$From[$key]), ([string]$To[$key]).Replace('$', '$$'), 'IgnoreCase')
    }
    return $Text
}

function Convert-NSPADGpoBackupFolder {
    # Rewrites the domain names in one backup root (manifest.xml + the {GUID} folder's files). Keeps
    # each file's encoding (Backup-GPO writes UTF-16 for some of them).
    param([Parameter(Mandatory)][string]$BackupRoot, [Parameter(Mandatory)][Collections.IDictionary]$From, [Parameter(Mandatory)][Collections.IDictionary]$To)
    $files = @(Get-ChildItem -LiteralPath $BackupRoot -Filter 'manifest.xml' -Force -File -ErrorAction SilentlyContinue)
    foreach ($guidDir in @(Get-ChildItem -LiteralPath $BackupRoot -Directory -Force -Filter '{*}')) {
        $files += @(Get-ChildItem -LiteralPath $guidDir.FullName -Force -File | Where-Object { $script:NSPADGpoBackupFiles -contains $_.Name })
    }
    foreach ($f in $files) {
        $bytes = [IO.File]::ReadAllBytes($f.FullName)
        $enc = if ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) { [Text.Encoding]::Unicode }
               elseif ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { New-Object Text.UTF8Encoding($true) }
               else { New-Object Text.UTF8Encoding($false) }
        $reader = New-Object IO.StreamReader((New-Object IO.MemoryStream(, $bytes)), $enc, $true)
        try { $text = $reader.ReadToEnd() } finally { $reader.Dispose() }
        $new = Convert-NSPADGpoBackupText -Text $text -From $From -To $To
        if ($new -cne $text) {
            $attrs = $f.Attributes
            if ($attrs -band [IO.FileAttributes]::Hidden) { $f.Attributes = $attrs -band (-bnot [IO.FileAttributes]::Hidden) }
            try { [IO.File]::WriteAllText($f.FullName, $new, $enc) } finally { $f.Attributes = $attrs }
        }
    }
}