Fix fresh domain controller bootstrap
This commit is contained in:
@@ -171,8 +171,10 @@ function Ensure-OrganizationalUnit {
|
||||
)
|
||||
|
||||
$distinguishedName = "OU=$Name,$Path"
|
||||
$existing = Get-ADOrganizationalUnit -Identity $distinguishedName -Server $Server `
|
||||
-ErrorAction SilentlyContinue
|
||||
$escapedName = $Name.Replace('\', '\5c').Replace('*', '\2a').Replace('(', '\28').Replace(')', '\29')
|
||||
$existing = Get-ADOrganizationalUnit -LDAPFilter "(ou=$escapedName)" `
|
||||
-SearchBase $Path -SearchScope OneLevel -Server $Server -ErrorAction Stop |
|
||||
Select-Object -First 1
|
||||
if (-not $existing) {
|
||||
New-ADOrganizationalUnit -Name $Name -Path $Path `
|
||||
-ProtectedFromAccidentalDeletion $true -Server $Server | Out-Null
|
||||
@@ -180,6 +182,57 @@ function Ensure-OrganizationalUnit {
|
||||
return $distinguishedName
|
||||
}
|
||||
|
||||
function Wait-ActiveDirectoryReady {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ExpectedBaseDn,
|
||||
[ValidateRange(1, 120)][int]$Attempts = 36,
|
||||
[ValidateRange(1, 30)][int]$DelaySeconds = 5
|
||||
)
|
||||
|
||||
for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
|
||||
try {
|
||||
$rootDse = Get-ADRootDSE -Server localhost -ErrorAction Stop
|
||||
if ($rootDse.DefaultNamingContext -eq $ExpectedBaseDn) {
|
||||
return
|
||||
}
|
||||
}
|
||||
catch {
|
||||
if ($attempt -eq $Attempts) {
|
||||
throw
|
||||
}
|
||||
}
|
||||
Start-Sleep -Seconds $DelaySeconds
|
||||
}
|
||||
throw "Active Directory did not publish $ExpectedBaseDn before the readiness timeout."
|
||||
}
|
||||
|
||||
function Wait-DnsZoneReady {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ZoneName,
|
||||
[Parameter(Mandatory)][ipaddress]$DnsServer,
|
||||
[ValidateRange(1, 120)][int]$Attempts = 30,
|
||||
[ValidateRange(1, 30)][int]$DelaySeconds = 2
|
||||
)
|
||||
|
||||
for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
|
||||
try {
|
||||
$soa = @(Resolve-DnsName $ZoneName -Type SOA -DnsOnly `
|
||||
-Server $DnsServer.IPAddressToString -ErrorAction Stop |
|
||||
Where-Object Type -eq SOA)
|
||||
if ($soa.Count -gt 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
catch {
|
||||
if ($attempt -eq $Attempts) {
|
||||
throw
|
||||
}
|
||||
}
|
||||
Start-Sleep -Seconds $DelaySeconds
|
||||
}
|
||||
throw "DNS did not load the $ZoneName zone before the readiness timeout."
|
||||
}
|
||||
|
||||
function Set-PackageShare {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Path,
|
||||
@@ -234,6 +287,10 @@ function Set-PackageShare {
|
||||
}
|
||||
|
||||
Assert-Administrator
|
||||
trap {
|
||||
Write-BootstrapLog ("ERROR: " + $_.Exception.Message)
|
||||
throw
|
||||
}
|
||||
$operatingSystem = Get-CimInstance Win32_OperatingSystem
|
||||
if ([int]$operatingSystem.ProductType -eq 1) {
|
||||
throw 'The domain controller bootstrap requires Windows Server, not a Windows client edition.'
|
||||
@@ -379,24 +436,75 @@ if (-not $computer.Domain.Equals($DomainName, [StringComparison]::OrdinalIgnoreC
|
||||
}
|
||||
|
||||
Write-BootstrapLog 'Finalizing Active Directory, DNS, policies, broker, shares, and remote management.'
|
||||
Import-Module ActiveDirectory -ErrorAction Stop
|
||||
$domain = Get-ADDomain -Identity $DomainName -Server $env:COMPUTERNAME
|
||||
|
||||
$laboratoryOuDn = Ensure-OrganizationalUnit -Name 'Laboratorio' -Path $baseDn -Server $env:COMPUTERNAME
|
||||
$usersOuDn = Ensure-OrganizationalUnit -Name 'Usuarios-SGU' -Path $baseDn -Server $env:COMPUTERNAME
|
||||
# Once the machine is a DC, every active adapter must query the local DNS
|
||||
# service. Only the private domain adapter may publish its address in the AD
|
||||
# zone; otherwise clients can receive the DHCP/NAT address of the Internet NIC.
|
||||
Get-NetAdapter | Where-Object Status -eq 'Up' | ForEach-Object {
|
||||
Set-DnsClientServerAddress -InterfaceIndex $_.ifIndex `
|
||||
-ServerAddresses $ServerIPv4Address.IPAddressToString
|
||||
Set-DnsClient -InterfaceIndex $_.ifIndex `
|
||||
-RegisterThisConnectionsAddress:($_.Name -eq $NetworkInterfaceAlias)
|
||||
}
|
||||
|
||||
Clear-DnsClientCache
|
||||
Register-DnsClient
|
||||
Import-Module ActiveDirectory -ErrorAction Stop
|
||||
Wait-ActiveDirectoryReady -ExpectedBaseDn $baseDn
|
||||
|
||||
# A newly promoted Windows Server 2025 DC can retain the Public firewall
|
||||
# profile because network identification ran before local DNS and LDAP were
|
||||
# ready. A private-adapter bounce triggers the supported domain-detection path.
|
||||
$domainProfile = Get-NetConnectionProfile -InterfaceAlias $NetworkInterfaceAlias `
|
||||
-ErrorAction SilentlyContinue
|
||||
if (-not $domainProfile -or $domainProfile.NetworkCategory -ne 'DomainAuthenticated') {
|
||||
Write-BootstrapLog "Refreshing $NetworkInterfaceAlias so Windows detects the domain network profile."
|
||||
Restart-NetAdapter -Name $NetworkInterfaceAlias -Confirm:$false
|
||||
for ($attempt = 1; $attempt -le 15; $attempt++) {
|
||||
Start-Sleep -Seconds 2
|
||||
$domainProfile = Get-NetConnectionProfile -InterfaceAlias $NetworkInterfaceAlias `
|
||||
-ErrorAction SilentlyContinue
|
||||
if ($domainProfile -and $domainProfile.NetworkCategory -eq 'DomainAuthenticated') {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Apply the single-address DNS listener only after any adapter refresh. That
|
||||
# avoids transient DNS socket errors while the private address is momentarily
|
||||
# unavailable, while still preventing the Internet/NAT address from being
|
||||
# published once finalization completes.
|
||||
New-ItemProperty `
|
||||
-Path 'HKLM:\SYSTEM\CurrentControlSet\Services\DNS\Parameters' `
|
||||
-Name PublishAddresses `
|
||||
-PropertyType String `
|
||||
-Value $ServerIPv4Address.IPAddressToString `
|
||||
-Force | Out-Null
|
||||
$dnsServerSetting = Get-DnsServerSetting -All -WarningAction SilentlyContinue
|
||||
$dnsServerSetting.ListeningIPAddress = @($ServerIPv4Address)
|
||||
Set-DnsServerSetting -InputObject $dnsServerSetting -WarningAction SilentlyContinue | Out-Null
|
||||
Restart-Service DNS -Force
|
||||
Wait-DnsZoneReady -ZoneName $DomainName -DnsServer $ServerIPv4Address
|
||||
|
||||
$adServer = 'localhost'
|
||||
$domain = Get-ADDomain -Identity $DomainName -Server $adServer
|
||||
$laboratoryOuDn = Ensure-OrganizationalUnit -Name 'Laboratorio' -Path $baseDn -Server $adServer
|
||||
$usersOuDn = Ensure-OrganizationalUnit -Name 'Usuarios-SGU' -Path $baseDn -Server $adServer
|
||||
foreach ($ouName in @('Docentes', 'Alumnos', 'Administrativos')) {
|
||||
Ensure-OrganizationalUnit -Name $ouName -Path $usersOuDn -Server $env:COMPUTERNAME | Out-Null
|
||||
Ensure-OrganizationalUnit -Name $ouName -Path $usersOuDn -Server $adServer | Out-Null
|
||||
}
|
||||
|
||||
$remoteDesktopGroupName = 'SG-Laboratorio-Usuarios-RDP'
|
||||
$remoteDesktopGroup = Get-ADGroup -Identity $remoteDesktopGroupName -Server $env:COMPUTERNAME `
|
||||
$remoteDesktopGroup = Get-ADGroup -LDAPFilter "(sAMAccountName=$remoteDesktopGroupName)" `
|
||||
-SearchBase $baseDn -SearchScope Subtree -Server $adServer `
|
||||
-ErrorAction SilentlyContinue
|
||||
if (-not $remoteDesktopGroup) {
|
||||
New-ADGroup -Name $remoteDesktopGroupName -SamAccountName $remoteDesktopGroupName `
|
||||
-GroupCategory Security -GroupScope Global -Path $laboratoryOuDn `
|
||||
-Description 'SGU users permitted to use Remote Desktop on laboratory clients.' `
|
||||
-Server $env:COMPUTERNAME | Out-Null
|
||||
$remoteDesktopGroup = Get-ADGroup -Identity $remoteDesktopGroupName -Server $env:COMPUTERNAME
|
||||
-Server $adServer | Out-Null
|
||||
$remoteDesktopGroup = Get-ADGroup -LDAPFilter "(sAMAccountName=$remoteDesktopGroupName)" `
|
||||
-SearchBase $laboratoryOuDn -SearchScope OneLevel -Server $adServer
|
||||
}
|
||||
|
||||
& (Join-Path $scriptsRoot 'Set-LabBrokerDns.ps1') `
|
||||
@@ -444,16 +552,30 @@ if (Test-Path -LiteralPath $brokerConfigurationPath -PathType Leaf) {
|
||||
-PublishPath $brokerPublishPath `
|
||||
-ServerCertificateSubject $brokerDnsName `
|
||||
-AllowedClientThumbprints $allowedClientThumbprints `
|
||||
-LdapHost $env:COMPUTERNAME `
|
||||
-LdapHost $adServer `
|
||||
-BaseDn $baseDn `
|
||||
-DomainNetbios $DomainNetbios `
|
||||
-UpnSuffix $DomainName `
|
||||
-RemoteDesktopGroupDn $remoteDesktopGroup.DistinguishedName `
|
||||
-DefaultCompany 'La Salle' `
|
||||
-FirewallLocalAddress $ServerIPv4Address `
|
||||
-FirewallRemoteAddress "$($ServerIPv4Address.IPAddressToString)/$PrefixLength" `
|
||||
-CreateMissingOus `
|
||||
-DisableCertificateRevocationCheckForLab | Out-Null
|
||||
|
||||
& (Join-Path $scriptsRoot 'Enable-SguServerRemoteManagement.ps1') | Out-Null
|
||||
# Remove stale A records registered by any non-domain/NAT adapter before its
|
||||
# dynamic DNS registration was disabled.
|
||||
$hostRecords = @(Get-DnsServerResourceRecord -ZoneName $DomainName `
|
||||
-Name $env:COMPUTERNAME -RRType A -ErrorAction SilentlyContinue)
|
||||
foreach ($hostRecord in $hostRecords) {
|
||||
if ($hostRecord.RecordData.IPv4Address.IPAddressToString -ne $ServerIPv4Address.IPAddressToString) {
|
||||
Remove-DnsServerResourceRecord -ZoneName $DomainName -InputObject $hostRecord -Force
|
||||
}
|
||||
}
|
||||
|
||||
$privateSubnet = "$($ServerIPv4Address.IPAddressToString)/$PrefixLength"
|
||||
& (Join-Path $scriptsRoot 'Enable-SguServerRemoteManagement.ps1') `
|
||||
-AllowedRemoteAddress $privateSubnet | Out-Null
|
||||
|
||||
$contentPath = Join-Path $bootstrapRoot 'payload\server-content\Packages'
|
||||
if (Test-Path -LiteralPath $contentPath -PathType Container) {
|
||||
@@ -463,6 +585,27 @@ if (Test-Path -LiteralPath $contentPath -PathType Container) {
|
||||
Set-PackageShare -Path $PackageSharePath -NetbiosName $DomainNetbios `
|
||||
-DomainSid $domain.DomainSID.Value
|
||||
|
||||
$packageFirewallRule = Get-NetFirewallRule -DisplayName 'SGU Bootstrap Packages (SMB)' `
|
||||
-ErrorAction SilentlyContinue
|
||||
if (-not $packageFirewallRule) {
|
||||
New-NetFirewallRule `
|
||||
-DisplayName 'SGU Bootstrap Packages (SMB)' `
|
||||
-Direction Inbound `
|
||||
-Action Allow `
|
||||
-Protocol TCP `
|
||||
-LocalPort 445 `
|
||||
-LocalAddress $ServerIPv4Address.IPAddressToString `
|
||||
-RemoteAddress $privateSubnet `
|
||||
-Profile Any | Out-Null
|
||||
}
|
||||
else {
|
||||
$packageFirewallRule | Set-NetFirewallRule -Enabled True -Profile Any
|
||||
$packageFirewallRule | Get-NetFirewallAddressFilter |
|
||||
Set-NetFirewallAddressFilter `
|
||||
-LocalAddress $ServerIPv4Address.IPAddressToString `
|
||||
-RemoteAddress $privateSubnet | Out-Null
|
||||
}
|
||||
|
||||
& (Join-Path $scriptsRoot 'Set-SguDomainComputerPolicies.ps1') `
|
||||
-TargetOuDn $laboratoryOuDn -DomainController $env:COMPUTERNAME | Out-Null
|
||||
$userPolicyParameters = @{
|
||||
@@ -493,6 +636,8 @@ $validation = [ordered]@{
|
||||
LaboratoryOu = $laboratoryOuDn
|
||||
UsersOu = $usersOuDn
|
||||
RemoteDesktopGroup = $remoteDesktopGroup.DistinguishedName
|
||||
DomainNetworkProfile = [string](Get-NetConnectionProfile `
|
||||
-InterfaceAlias $NetworkInterfaceAlias -ErrorAction SilentlyContinue).NetworkCategory
|
||||
}
|
||||
|
||||
if ($validation.BrokerService -ne 'Running' -or
|
||||
|
||||
Reference in New Issue
Block a user