Providers/FreeIPA/Public/New-FreeIPADnsZone.ps1

function New-FreeIPADnsZone {
    <#
    .EXTERNALHELP TestEnvironment-Help.xml
    .SYNOPSIS
        Creates the seed's own DNS zones and the records in them from Data\FreeIPADnsRecords.csv
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [switch]$SkipRecords,

        [Parameter()]
        [switch]$PassThru
    )

    $connection = Get-FreeIPAConnection
    $marker = Get-FreeIPASeedMarker -Connection $connection
    $zone = Get-FreeIPASeedZone -Marker $marker -Connection $connection

    $csvPath = Join-Path -Path (Get-FreeIPADataPath) -ChildPath 'FreeIPADnsRecords.csv'
    $rows = @(Import-Csv -Path $csvPath -Encoding UTF8)

    $result = [PSCustomObject]@{
        DnsEnabled     = $true
        ZonesCreated   = 0
        ZonesExisting  = 0
        RecordsCreated = 0
        RecordsUpdated = 0
        Zones          = @()
        Errors         = @()
    }

    $enabled = Invoke-FreeIPARequest -Method 'dns_is_enabled' -Connection $connection
    if (-not ($enabled -and $enabled.result -eq $true)) {
        $result.DnsEnabled = $false
        Write-Warning 'The realm has no DNS server, so no zone is created; the hosts will be records without addresses.'
        if ($PassThru) { return $result }
        return
    }

    $zones = [System.Collections.Generic.List[object]]::new()
    $ready = @{}
    foreach ($entry in @(
            @{ Name = $zone.Forward; Kind = 'Forward'; Options = @{ idnssoarname = $zone.Contact; idnsallowdynupdate = $false } }
            @{ Name = $zone.Reverse; Kind = 'Reverse'; Options = @{ idnssoarname = $zone.Contact; name_from_ip = $zone.Subnet } })) {
        if (-not $PSCmdlet.ShouldProcess($entry.Name, "Create FreeIPA $($entry.Kind.ToLowerInvariant()) DNS zone")) { continue }
        try {
            $shown = Invoke-FreeIPARequest -Method 'dnszone_show' -Arguments $entry.Name -Connection $connection -IgnoreError 'NotFound'
            if ($shown -and $shown.result) {
                $contact = ConvertFrom-FreeIPADnsName -Value $shown.result.idnssoarname
                if ($contact -ne $zone.Contact) {
                    throw "Zone $($entry.Name) already exists with the contact '$contact', which is not the seed's; it is left alone."
                }
                $result.ZonesExisting++
            }
            else {
                # The reverse zone is named from the subnet by the server, so its name is
                # not an argument; the forward zone's is.
                $arguments = @()
                if ($entry.Kind -eq 'Forward') { $arguments = @($entry.Name) }
                $null = Invoke-FreeIPARequest -Method 'dnszone_add' -Arguments $arguments -Options $entry.Options -Connection $connection
                $result.ZonesCreated++
                Write-Verbose "Created DNS zone $($entry.Name)"
            }
            $ready[$entry.Kind] = $entry.Name
            $zones.Add([PSCustomObject]@{ Name = $entry.Name; Kind = $entry.Kind })
        }
        catch {
            $message = "Failed to create DNS zone '$($entry.Name)': $($_.Exception.Message)"
            $result.Errors += $message
            Write-Error $message
        }
    }

    if (-not $SkipRecords) {
        # {prefix} and {zone} in the data become the session's prefix and the forward zone,
        # so an alias target is a seeded host's real name.
        $substitute = { param($text) ([string]$text).Replace('{prefix}', $marker.NamePrefix).Replace('{zone}', $zone.Forward) }
        $approved = [System.Collections.Generic.List[object]]::new()
        foreach ($row in $rows) {
            if (-not $ready.ContainsKey($row.Zone)) { continue }
            $zoneName = $ready[$row.Zone]
            $attribute = '{0}record' -f $row.Type.ToLowerInvariant()
            $values = [object[]]@(($row.Data -split ';') | Where-Object { $_ } | ForEach-Object { & $substitute $_ })
            if (-not $PSCmdlet.ShouldProcess("$($row.Name) $($row.Type) in $zoneName", 'Create FreeIPA DNS record')) { continue }
            $options = @{}
            $options[$attribute] = $values
            $approved.Add([PSCustomObject]@{ Index = $approved.Count; Row = $row; Zone = $zoneName; Options = $options; Label = "'$($row.Name)' ($($row.Type)) in $zoneName" })
        }
        if ($approved.Count -gt 0) {
            # Looked up in one batch and then added or modified in another, fifty to a request:
            # 836 records one lookup and one write each was 1,672 round trips.
            $state = @{}
            $lookups = @($approved | ForEach-Object { @{ Method = 'dnsrecord_show'; Arguments = @($_.Zone, $_.Row.Name); Options = @{}; IgnoreError = @('NotFound'); Tag = $_.Index } })
            foreach ($answer in @(Invoke-FreeIPABatch -Command $lookups -Connection $connection)) {
                $record = $approved[$answer.Command.Tag]
                if (-not $answer.Success) {
                    $state[$record.Index] = 'failed'
                    $message = "Failed to create DNS record $($record.Label): $($answer.ErrorMessage)"
                    $result.Errors += $message
                    Write-Error $message
                    continue
                }
                if (-not $answer.Ignored -and $answer.Result -and $answer.Result.result) { $state[$record.Index] = 'present' }
            }
            $writes = @(foreach ($record in $approved) {
                    if ($state.ContainsKey($record.Index) -and $state[$record.Index] -eq 'failed') { continue }
                    if ($state.ContainsKey($record.Index)) {
                        @{ Method = 'dnsrecord_mod'; Arguments = @($record.Zone, $record.Row.Name); Options = $record.Options; IgnoreError = @('EmptyModlist'); Tag = $record.Index; Updates = $true }
                    }
                    else {
                        @{ Method = 'dnsrecord_add'; Arguments = @($record.Zone, $record.Row.Name); Options = $record.Options; Tag = $record.Index; Updates = $false }
                    }
                })
            foreach ($answer in @(Invoke-FreeIPABatch -Command $writes -Connection $connection)) {
                $record = $approved[$answer.Command.Tag]
                if (-not $answer.Success) {
                    $message = "Failed to create DNS record $($record.Label): $($answer.ErrorMessage)"
                    $result.Errors += $message
                    Write-Error $message
                    continue
                }
                if ($answer.Command.Updates) { $result.RecordsUpdated++ }
                else {
                    $result.RecordsCreated++
                    Write-Verbose "Created DNS record $($record.Row.Name) $($record.Row.Type) in $($record.Zone)"
                }
            }
        }
    }

    $result.Zones = $zones.ToArray()
    Write-Verbose "DNS: $($result.ZonesCreated) zones created, $($result.RecordsCreated) records created, $($result.RecordsUpdated) updated, $($result.Errors.Count) problems"
    if ($PassThru) { return $result }
}