Providers/AD/Public/New-ADTestDnsZone.ps1
|
function New-ADTestDnsZone { <# .EXTERNALHELP TestEnvironment-Help.xml .SYNOPSIS Creates the seed's own DNS zones and the records in them, so the seeded computers resolve #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Colour-coded console progress is intentional; results are returned as objects.')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([PSCustomObject])] param( [Parameter()] [switch]$SkipDeviceRecords, [Parameter()] [switch]$ShowProgress, [Parameter()] [switch]$PassThru ) $seed = Get-ADTestSeedMarker $domain = Get-ADTestDomain $zone = Get-ADTestSeedZone -Marker $seed -Domain $domain $result = [PSCustomObject]@{ DnsAvailable = $true ZonesCreated = 0 ZonesExisting = 0 DeviceRecords = 0 ExtraRecords = 0 Zones = @() Errors = @() } try { Import-Module DnsServer -ErrorAction Stop -Verbose:$false } catch { $result.DnsAvailable = $false Write-Warning ('The DnsServer module is not available, so no zone is created and the ' + "seeded computers will not resolve: $($_.Exception.Message)") if ($PassThru) { return $result } return } # The DnsServer cmdlets default to the local machine, which is only the right answer when # this runs on a domain controller. The connection already knows which one to talk to. $dnsTarget = @{} if ($script:ADConnection -and $script:ADConnection.PSObject.Properties['Server'] -and $script:ADConnection.Server) { $dnsTarget['ComputerName'] = $script:ADConnection.Server } # --- the zones --------------------------------------------------------------------- $ready = @{} foreach ($entry in @( @{ Name = $zone.Forward; Kind = 'Forward' } @{ Name = $zone.Reverse; Kind = 'Reverse' })) { if (-not $PSCmdlet.ShouldProcess($entry.Name, "Create $($entry.Kind.ToLowerInvariant()) DNS zone")) { continue } try { $existing = Get-DnsServerZone -Name $entry.Name @dnsTarget -ErrorAction SilentlyContinue if ($existing) { # Present already: ours only if it carries the tag. Anything else is somebody's # zone that happens to share the name, and this module does not touch it. $zoneObject = Get-ADTestDnsZoneObject -ZoneName $entry.Name -DomainDN $domain.DomainDN if ($zoneObject -and $zoneObject.adminDescription -eq $seed.Tag) { $result.ZonesExisting++ } else { throw ("A zone named $($entry.Name) already exists and does not carry " + "$($seed.Tag) in adminDescription, so this module cannot prove it created it. Leaving it alone.") } } else { if ($entry.Kind -eq 'Forward') { $null = Add-DnsServerPrimaryZone -Name $entry.Name -ReplicationScope 'Domain' @dnsTarget -ErrorAction Stop } else { $null = Add-DnsServerPrimaryZone -NetworkId $zone.NetworkId -ReplicationScope 'Domain' @dnsTarget -ErrorAction Stop } $result.ZonesCreated++ Write-Verbose "Created DNS zone $($entry.Name)" # Stamped so teardown can prove it. Without the tag the zone would be # removable only by its name, which is the rule this provider does not use. $zoneObject = Get-ADTestDnsZoneObject -ZoneName $entry.Name -DomainDN $domain.DomainDN if ($zoneObject) { Set-ADObject -Identity $zoneObject.DistinguishedName -Replace @{ adminDescription = $seed.Tag } -ErrorAction Stop } else { Write-Warning ("Created $($entry.Name) but could not find its directory object to tag it. " + 'Teardown will not remove it; remove it by hand.') } } $ready[$entry.Kind] = $entry.Name $result.Zones += [PSCustomObject]@{ Name = $entry.Name; Kind = $entry.Kind } } catch { $message = "DNS zone '$($entry.Name)': $($_.Exception.Message)" $result.Errors += $message Write-Error $message } } # --- a record for every addressed device --------------------------------------------- if (-not $SkipDeviceRecords -and $ready.ContainsKey('Forward')) { $devicePath = Join-Path -Path (Get-ADTestDataPath) -ChildPath 'ADDevices.csv' $devices = @(Import-Csv -Path $devicePath -Encoding UTF8 | Where-Object { $_.IPAddress }) $index = 0 foreach ($device in $devices) { $index++ $recordName = ('{0}{1}' -f $seed.Prefix, $device.DeviceName).ToLowerInvariant() Write-TestProgress -Activity 'Writing device DNS records' -Status "$index of $($devices.Count): $recordName" ` -PercentComplete ([int](100 * $index / [Math]::Max(1, $devices.Count))) -ShowProgress:$ShowProgress if (-not $PSCmdlet.ShouldProcess("$recordName ($($device.IPAddress))", 'Create DNS A record')) { continue } try { $already = Get-DnsServerResourceRecord -ZoneName $ready.Forward -Name $recordName -RRType A @dnsTarget -ErrorAction SilentlyContinue if ($already) { continue } # -CreatePtr writes the reverse record too, which is why the reverse zone is # created first; without it the A record is written and the PTR is silently not. $null = Add-DnsServerResourceRecordA -ZoneName $ready.Forward -Name $recordName ` -IPv4Address $device.IPAddress -CreatePtr @dnsTarget -ErrorAction Stop $result.DeviceRecords++ } catch { $message = "DNS record '$recordName': $($_.Exception.Message)" $result.Errors += $message Write-Error $message } } Write-TestProgress -Activity 'Writing device DNS records' -Completed -ShowProgress:$ShowProgress } # --- the records that are not a device ------------------------------------------------ $recordPath = Join-Path -Path (Get-ADTestDataPath) -ChildPath 'ADDnsRecords.csv' foreach ($row in @(Import-Csv -Path $recordPath -Encoding UTF8)) { if (-not $ready.ContainsKey($row.Zone)) { continue } $zoneName = $ready[$row.Zone] # {prefix} and {zone} become the session's prefix and the forward zone, so an alias # target is a seeded computer's real name. $values = @(($row.Data -split ';') | Where-Object { $_ } | ForEach-Object { $_.Replace('{prefix}', $seed.Prefix.ToLowerInvariant()).Replace('{zone}', $ready.Forward) }) if (-not $PSCmdlet.ShouldProcess("$($row.Name) $($row.Type) in $zoneName", 'Create DNS record')) { continue } try { $already = Get-DnsServerResourceRecord -ZoneName $zoneName -Name $row.Name -RRType $row.Type @dnsTarget -ErrorAction SilentlyContinue if ($already) { continue } foreach ($value in $values) { switch ($row.Type) { 'A' { $null = Add-DnsServerResourceRecordA -ZoneName $zoneName -Name $row.Name -IPv4Address $value @dnsTarget -ErrorAction Stop } 'CNAME' { $null = Add-DnsServerResourceRecordCName -ZoneName $zoneName -Name $row.Name -HostNameAlias $value @dnsTarget -ErrorAction Stop } 'TXT' { $null = Add-DnsServerResourceRecord -Txt -ZoneName $zoneName -Name $row.Name -DescriptiveText $value @dnsTarget -ErrorAction Stop } 'PTR' { $null = Add-DnsServerResourceRecordPtr -ZoneName $zoneName -Name $row.Name -PtrDomainName $value @dnsTarget -ErrorAction Stop } default { throw "Unknown record type '$($row.Type)'." } } $result.ExtraRecords++ } } catch { $message = "DNS record '$($row.Name)' ($($row.Type)) in ${zoneName}: $($_.Exception.Message)" $result.Errors += $message Write-Error $message } } Write-Verbose ("DNS: $($result.ZonesCreated) zones created, $($result.DeviceRecords) device records, " + "$($result.ExtraRecords) other records, $($result.Errors.Count) problems") if ($PassThru) { return $result } } |