Add one-command server and client bootstraps
This commit is contained in:
@@ -0,0 +1,514 @@
|
||||
#Requires -Version 5.1
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[ipaddress]$ServerIPv4Address,
|
||||
[ValidateRange(1, 32)]
|
||||
[int]$PrefixLength = 24,
|
||||
[string]$NetworkInterfaceAlias,
|
||||
[ipaddress]$DefaultGateway,
|
||||
[ipaddress[]]$DnsForwarders = @(),
|
||||
[string]$DomainName = 'lci.lasalle.mx',
|
||||
[string]$DomainNetbios = 'LCI',
|
||||
[string]$BrokerRecordName = 'sgu-auth',
|
||||
[string]$PackageSharePath = 'C:\Packages',
|
||||
[securestring]$SafeModeAdministratorPassword,
|
||||
[switch]$SkipRestart,
|
||||
[switch]$Resume
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$bootstrapRoot = Join-Path $env:ProgramData 'SGU\Bootstrap\Server'
|
||||
$statePath = Join-Path $bootstrapRoot 'bootstrap-state.json'
|
||||
$completionPath = Join-Path $bootstrapRoot 'bootstrap-complete.json'
|
||||
$logPath = Join-Path $bootstrapRoot 'bootstrap.log'
|
||||
$taskName = 'SGU-Complete-Domain-Controller-Bootstrap'
|
||||
|
||||
function Write-BootstrapLog {
|
||||
param([Parameter(Mandatory)][string]$Message)
|
||||
|
||||
$line = '{0:u} {1}' -f (Get-Date), $Message
|
||||
Write-Host $line
|
||||
if (Test-Path -LiteralPath $bootstrapRoot) {
|
||||
Add-Content -LiteralPath $logPath -Value $line -Encoding UTF8
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-Administrator {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'Run this bootstrap from an elevated Windows PowerShell session.'
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-PackageManifest {
|
||||
param([Parameter(Mandatory)][string]$PackageRoot)
|
||||
|
||||
$manifestPath = Join-Path $PackageRoot 'package-manifest.json'
|
||||
if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
|
||||
throw 'package-manifest.json is missing. Use the complete SGU server bootstrap release.'
|
||||
}
|
||||
|
||||
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
|
||||
foreach ($entry in $manifest.Files) {
|
||||
$path = Join-Path $PackageRoot ([string]$entry.Path)
|
||||
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
|
||||
throw "Bootstrap package file is missing: $($entry.Path)"
|
||||
}
|
||||
$actual = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash
|
||||
if ($actual -ne [string]$entry.Sha256) {
|
||||
throw "Bootstrap package integrity check failed: $($entry.Path)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-DomainBaseDn {
|
||||
param([Parameter(Mandatory)][string]$DnsDomainName)
|
||||
|
||||
return (($DnsDomainName -split '\.') | ForEach-Object { "DC=$_" }) -join ','
|
||||
}
|
||||
|
||||
function Resolve-PrivateInterfaceAlias {
|
||||
param([string]$RequestedAlias)
|
||||
|
||||
if ($RequestedAlias) {
|
||||
Get-NetAdapter -Name $RequestedAlias -ErrorAction Stop | Out-Null
|
||||
return $RequestedAlias
|
||||
}
|
||||
|
||||
$upAdapters = @(Get-NetAdapter | Where-Object Status -eq 'Up')
|
||||
$withoutGateway = @($upAdapters | Where-Object {
|
||||
-not (Get-NetIPConfiguration -InterfaceIndex $_.ifIndex).IPv4DefaultGateway
|
||||
})
|
||||
if ($withoutGateway.Count -eq 1) {
|
||||
return [string]$withoutGateway[0].Name
|
||||
}
|
||||
if ($upAdapters.Count -eq 1) {
|
||||
return [string]$upAdapters[0].Name
|
||||
}
|
||||
|
||||
$aliases = ($upAdapters.Name | Sort-Object) -join ', '
|
||||
throw "Could not select the private domain adapter unambiguously. Re-run with -NetworkInterfaceAlias. Available adapters: $aliases"
|
||||
}
|
||||
|
||||
function Set-StaticDomainAddress {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$InterfaceAlias,
|
||||
[Parameter(Mandatory)][ipaddress]$Address,
|
||||
[Parameter(Mandatory)][int]$NetworkPrefixLength,
|
||||
[ipaddress]$Gateway
|
||||
)
|
||||
|
||||
$adapter = Get-NetAdapter -Name $InterfaceAlias -ErrorAction Stop
|
||||
Set-NetIPInterface -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 -Dhcp Disabled
|
||||
|
||||
$addresses = @(Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||
Where-Object PrefixOrigin -ne 'WellKnown')
|
||||
foreach ($existingAddress in $addresses) {
|
||||
if ($existingAddress.IPAddress -ne $Address.IPAddressToString -or
|
||||
[int]$existingAddress.PrefixLength -ne $NetworkPrefixLength) {
|
||||
Remove-NetIPAddress -InputObject $existingAddress -Confirm:$false
|
||||
}
|
||||
}
|
||||
|
||||
$matchingAddress = Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
|
||||
-IPAddress $Address.IPAddressToString -ErrorAction SilentlyContinue
|
||||
if (-not $matchingAddress) {
|
||||
$addressParameters = @{
|
||||
InterfaceIndex = $adapter.ifIndex
|
||||
IPAddress = $Address.IPAddressToString
|
||||
PrefixLength = $NetworkPrefixLength
|
||||
AddressFamily = 'IPv4'
|
||||
}
|
||||
if ($Gateway) {
|
||||
$addressParameters.DefaultGateway = $Gateway.IPAddressToString
|
||||
}
|
||||
New-NetIPAddress @addressParameters | Out-Null
|
||||
}
|
||||
|
||||
if ($Gateway) {
|
||||
$defaultRoutes = @(Get-NetRoute -InterfaceIndex $adapter.ifIndex `
|
||||
-AddressFamily IPv4 -DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue)
|
||||
foreach ($route in $defaultRoutes) {
|
||||
if ($route.NextHop -ne $Gateway.IPAddressToString) {
|
||||
Remove-NetRoute -InputObject $route -Confirm:$false
|
||||
}
|
||||
}
|
||||
if (-not (Get-NetRoute -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
|
||||
-DestinationPrefix '0.0.0.0/0' -ErrorAction SilentlyContinue |
|
||||
Where-Object NextHop -eq $Gateway.IPAddressToString)) {
|
||||
New-NetRoute -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
|
||||
-DestinationPrefix '0.0.0.0/0' -NextHop $Gateway.IPAddressToString | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
Set-DnsClientServerAddress -InterfaceIndex $adapter.ifIndex `
|
||||
-ServerAddresses $Address.IPAddressToString
|
||||
}
|
||||
|
||||
function Register-ResumeTask {
|
||||
param([Parameter(Mandatory)][string]$ScriptPath)
|
||||
|
||||
$powerShell = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
$action = New-ScheduledTaskAction -Execute $powerShell `
|
||||
-Argument "-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$ScriptPath`" -Resume"
|
||||
$trigger = New-ScheduledTaskTrigger -AtStartup
|
||||
$trigger.Delay = 'PT1M'
|
||||
$settings = New-ScheduledTaskSettingsSet `
|
||||
-StartWhenAvailable `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Minutes 30) `
|
||||
-RestartCount 3 `
|
||||
-RestartInterval (New-TimeSpan -Minutes 2)
|
||||
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
||||
-Settings $settings -User 'SYSTEM' -RunLevel Highest -Force | Out-Null
|
||||
}
|
||||
|
||||
function Ensure-OrganizationalUnit {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Name,
|
||||
[Parameter(Mandatory)][string]$Path,
|
||||
[Parameter(Mandatory)][string]$Server
|
||||
)
|
||||
|
||||
$distinguishedName = "OU=$Name,$Path"
|
||||
$existing = Get-ADOrganizationalUnit -Identity $distinguishedName -Server $Server `
|
||||
-ErrorAction SilentlyContinue
|
||||
if (-not $existing) {
|
||||
New-ADOrganizationalUnit -Name $Name -Path $Path `
|
||||
-ProtectedFromAccidentalDeletion $true -Server $Server | Out-Null
|
||||
}
|
||||
return $distinguishedName
|
||||
}
|
||||
|
||||
function Set-PackageShare {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Path,
|
||||
[Parameter(Mandatory)][string]$NetbiosName,
|
||||
[Parameter(Mandatory)][string]$DomainSid
|
||||
)
|
||||
|
||||
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||
$domainAdminsSid = [Security.Principal.SecurityIdentifier]::new("$DomainSid-512")
|
||||
$domainUsersSid = [Security.Principal.SecurityIdentifier]::new("$DomainSid-513")
|
||||
$domainComputersSid = [Security.Principal.SecurityIdentifier]::new("$DomainSid-515")
|
||||
$systemSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-18')
|
||||
|
||||
$acl = New-Object Security.AccessControl.DirectorySecurity
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
$inheritance = [Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit'
|
||||
$propagation = [Security.AccessControl.PropagationFlags]::None
|
||||
$allow = [Security.AccessControl.AccessControlType]::Allow
|
||||
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new(
|
||||
$systemSid, [Security.AccessControl.FileSystemRights]::FullControl,
|
||||
$inheritance, $propagation, $allow))
|
||||
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new(
|
||||
$domainAdminsSid, [Security.AccessControl.FileSystemRights]::FullControl,
|
||||
$inheritance, $propagation, $allow))
|
||||
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new(
|
||||
$domainComputersSid, [Security.AccessControl.FileSystemRights]'ReadAndExecute, Synchronize',
|
||||
$inheritance, $propagation, $allow))
|
||||
$acl.AddAccessRule([Security.AccessControl.FileSystemAccessRule]::new(
|
||||
$domainUsersSid, [Security.AccessControl.FileSystemRights]'ReadAndExecute, Synchronize',
|
||||
$inheritance, $propagation, $allow))
|
||||
Set-Acl -LiteralPath $Path -AclObject $acl
|
||||
|
||||
$domainAdmins = $domainAdminsSid.Translate([Security.Principal.NTAccount]).Value
|
||||
$domainUsers = $domainUsersSid.Translate([Security.Principal.NTAccount]).Value
|
||||
$domainComputers = $domainComputersSid.Translate([Security.Principal.NTAccount]).Value
|
||||
$share = Get-SmbShare -Name Packages -ErrorAction SilentlyContinue
|
||||
if ($share -and $share.Path -ne $Path) {
|
||||
throw "The existing Packages share points to $($share.Path), not $Path."
|
||||
}
|
||||
if (-not $share) {
|
||||
New-SmbShare -Name Packages -Path $Path -FullAccess $domainAdmins `
|
||||
-ReadAccess $domainComputers,$domainUsers -FolderEnumerationMode AccessBased | Out-Null
|
||||
}
|
||||
else {
|
||||
Grant-SmbShareAccess -Name Packages -AccountName $domainAdmins `
|
||||
-AccessRight Full -Force | Out-Null
|
||||
Grant-SmbShareAccess -Name Packages -AccountName $domainComputers `
|
||||
-AccessRight Read -Force | Out-Null
|
||||
Grant-SmbShareAccess -Name Packages -AccountName $domainUsers `
|
||||
-AccessRight Read -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
Assert-Administrator
|
||||
$operatingSystem = Get-CimInstance Win32_OperatingSystem
|
||||
if ([int]$operatingSystem.ProductType -eq 1) {
|
||||
throw 'The domain controller bootstrap requires Windows Server, not a Windows client edition.'
|
||||
}
|
||||
|
||||
$existingState = $null
|
||||
if (Test-Path -LiteralPath $statePath -PathType Leaf) {
|
||||
$existingState = Get-Content -LiteralPath $statePath -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
if ($Resume -or (-not $ServerIPv4Address -and $existingState)) {
|
||||
if (-not $existingState) {
|
||||
throw 'The persisted bootstrap state is missing; start the server bootstrap normally.'
|
||||
}
|
||||
$ServerIPv4Address = [ipaddress][string]$existingState.ServerIPv4Address
|
||||
$PrefixLength = [int]$existingState.PrefixLength
|
||||
$NetworkInterfaceAlias = [string]$existingState.NetworkInterfaceAlias
|
||||
$DefaultGateway = if ($existingState.DefaultGateway) { [ipaddress][string]$existingState.DefaultGateway } else { $null }
|
||||
$DnsForwarders = @($existingState.DnsForwarders | ForEach-Object { [ipaddress][string]$_ })
|
||||
$DomainName = [string]$existingState.DomainName
|
||||
$DomainNetbios = [string]$existingState.DomainNetbios
|
||||
$BrokerRecordName = [string]$existingState.BrokerRecordName
|
||||
$PackageSharePath = [string]$existingState.PackageSharePath
|
||||
}
|
||||
|
||||
if (-not $ServerIPv4Address) {
|
||||
$ServerIPv4Address = [ipaddress](Read-Host 'Fixed IPv4 address for this domain controller')
|
||||
}
|
||||
|
||||
$sourceRoot = $PSScriptRoot
|
||||
if (-not $Resume) {
|
||||
Assert-PackageManifest -PackageRoot $sourceRoot
|
||||
New-Item -ItemType Directory -Path $bootstrapRoot -Force | Out-Null
|
||||
if ((Resolve-Path -LiteralPath $sourceRoot).Path -ne (Resolve-Path -LiteralPath $bootstrapRoot).Path) {
|
||||
Copy-Item -Path (Join-Path $sourceRoot '*') -Destination $bootstrapRoot -Recurse -Force
|
||||
}
|
||||
Assert-PackageManifest -PackageRoot $bootstrapRoot
|
||||
}
|
||||
else {
|
||||
Assert-PackageManifest -PackageRoot $bootstrapRoot
|
||||
}
|
||||
|
||||
$NetworkInterfaceAlias = Resolve-PrivateInterfaceAlias -RequestedAlias $NetworkInterfaceAlias
|
||||
$baseDn = Get-DomainBaseDn -DnsDomainName $DomainName
|
||||
$brokerDnsName = "$BrokerRecordName.$DomainName"
|
||||
$stagedScriptPath = Join-Path $bootstrapRoot 'Initialize-SguDomainController.ps1'
|
||||
$scriptsRoot = Join-Path $bootstrapRoot 'payload\scripts'
|
||||
$brokerPublishPath = Join-Path $bootstrapRoot 'payload\broker'
|
||||
|
||||
foreach ($requiredPath in @(
|
||||
$stagedScriptPath,
|
||||
(Join-Path $scriptsRoot 'Deploy-AuthBroker.ps1'),
|
||||
(Join-Path $scriptsRoot 'New-LabCertificate.ps1'),
|
||||
(Join-Path $scriptsRoot 'Set-LabBrokerDns.ps1'),
|
||||
(Join-Path $scriptsRoot 'Set-SguDomainComputerPolicies.ps1'),
|
||||
(Join-Path $scriptsRoot 'Set-SguDomainUserPolicies.ps1'),
|
||||
(Join-Path $scriptsRoot 'Enable-SguServerRemoteManagement.ps1'),
|
||||
(Join-Path $brokerPublishPath 'SGU.AuthBroker.exe'))) {
|
||||
if (-not (Test-Path -LiteralPath $requiredPath -PathType Leaf)) {
|
||||
throw "The server bootstrap package is incomplete: $requiredPath"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $existingState) {
|
||||
if ($DnsForwarders.Count -eq 0) {
|
||||
$DnsForwarders = @(Get-DnsClientServerAddress -AddressFamily IPv4 |
|
||||
Where-Object InterfaceAlias -ne $NetworkInterfaceAlias |
|
||||
Select-Object -ExpandProperty ServerAddresses |
|
||||
Where-Object { $_ -and $_ -ne $ServerIPv4Address.IPAddressToString } |
|
||||
ForEach-Object { [ipaddress]$_ } |
|
||||
Select-Object -Unique)
|
||||
}
|
||||
|
||||
$existingState = [ordered]@{
|
||||
Phase = 'Promote'
|
||||
ServerIPv4Address = $ServerIPv4Address.IPAddressToString
|
||||
PrefixLength = $PrefixLength
|
||||
NetworkInterfaceAlias = $NetworkInterfaceAlias
|
||||
DefaultGateway = if ($DefaultGateway) { $DefaultGateway.IPAddressToString } else { $null }
|
||||
DnsForwarders = @($DnsForwarders | ForEach-Object IPAddressToString)
|
||||
DomainName = $DomainName
|
||||
DomainNetbios = $DomainNetbios
|
||||
BrokerRecordName = $BrokerRecordName
|
||||
PackageSharePath = $PackageSharePath
|
||||
}
|
||||
[IO.File]::WriteAllText(
|
||||
$statePath,
|
||||
($existingState | ConvertTo-Json -Depth 4),
|
||||
[Text.UTF8Encoding]::new($false))
|
||||
}
|
||||
|
||||
Write-BootstrapLog "Configuring $NetworkInterfaceAlias as $ServerIPv4Address/$PrefixLength."
|
||||
Set-StaticDomainAddress -InterfaceAlias $NetworkInterfaceAlias `
|
||||
-Address $ServerIPv4Address -NetworkPrefixLength $PrefixLength -Gateway $DefaultGateway
|
||||
|
||||
$computer = Get-CimInstance Win32_ComputerSystem
|
||||
if (-not $computer.PartOfDomain) {
|
||||
if ([string]$existingState.Phase -eq 'Finalize') {
|
||||
throw 'Active Directory promotion completed but Windows has not restarted. Restart the server to continue automatically.'
|
||||
}
|
||||
|
||||
if (-not $SafeModeAdministratorPassword) {
|
||||
$SafeModeAdministratorPassword = Read-Host `
|
||||
'Directory Services Restore Mode password (not stored)' -AsSecureString
|
||||
}
|
||||
|
||||
Write-BootstrapLog 'Installing Active Directory Domain Services, DNS, and management tools.'
|
||||
Install-WindowsFeature AD-Domain-Services,DNS,GPMC,RSAT-AD-Tools `
|
||||
-IncludeManagementTools | Out-Null
|
||||
Register-ResumeTask -ScriptPath $stagedScriptPath
|
||||
|
||||
Write-BootstrapLog "Creating the $DomainName forest. Windows must restart once."
|
||||
Install-ADDSForest `
|
||||
-DomainName $DomainName `
|
||||
-DomainNetbiosName $DomainNetbios `
|
||||
-InstallDns `
|
||||
-SafeModeAdministratorPassword $SafeModeAdministratorPassword `
|
||||
-NoRebootOnCompletion `
|
||||
-Force | Out-Null
|
||||
|
||||
$existingState.Phase = 'Finalize'
|
||||
[IO.File]::WriteAllText(
|
||||
$statePath,
|
||||
($existingState | ConvertTo-Json -Depth 4),
|
||||
[Text.UTF8Encoding]::new($false))
|
||||
|
||||
if ($SkipRestart) {
|
||||
Write-BootstrapLog 'Promotion succeeded. Restart manually; finalization will resume at startup.'
|
||||
return [pscustomobject]@{
|
||||
Phase = 'AwaitingRestart'
|
||||
DomainName = $DomainName
|
||||
ServerIPv4Address = $ServerIPv4Address.IPAddressToString
|
||||
ResumeTask = $taskName
|
||||
}
|
||||
}
|
||||
|
||||
Restart-Computer -Force
|
||||
return
|
||||
}
|
||||
|
||||
if (-not $computer.Domain.Equals($DomainName, [StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "This server belongs to $($computer.Domain), not $DomainName."
|
||||
}
|
||||
|
||||
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
|
||||
foreach ($ouName in @('Docentes', 'Alumnos', 'Administrativos')) {
|
||||
Ensure-OrganizationalUnit -Name $ouName -Path $usersOuDn -Server $env:COMPUTERNAME | Out-Null
|
||||
}
|
||||
|
||||
$remoteDesktopGroupName = 'SG-Laboratorio-Usuarios-RDP'
|
||||
$remoteDesktopGroup = Get-ADGroup -Identity $remoteDesktopGroupName -Server $env:COMPUTERNAME `
|
||||
-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
|
||||
}
|
||||
|
||||
& (Join-Path $scriptsRoot 'Set-LabBrokerDns.ps1') `
|
||||
-ZoneName $DomainName `
|
||||
-RecordName $BrokerRecordName `
|
||||
-IPv4Address $ServerIPv4Address `
|
||||
-ExternalForwarders $DnsForwarders | Out-Null
|
||||
|
||||
$certificateDirectory = Join-Path $bootstrapRoot 'certificates'
|
||||
$serverCertificate = Get-ChildItem Cert:\LocalMachine\My |
|
||||
Where-Object {
|
||||
$_.Subject -eq "CN=$brokerDnsName" -and
|
||||
$_.HasPrivateKey -and
|
||||
$_.NotAfter -gt (Get-Date).AddDays(30)
|
||||
} |
|
||||
Sort-Object NotAfter -Descending |
|
||||
Select-Object -First 1
|
||||
if (-not $serverCertificate) {
|
||||
$certificateResult = & (Join-Path $scriptsRoot 'New-LabCertificate.ps1') `
|
||||
-Role BrokerServer `
|
||||
-BrokerDnsName $brokerDnsName `
|
||||
-OutputDirectory $certificateDirectory
|
||||
$serverCertificate = Get-ChildItem Cert:\LocalMachine\My |
|
||||
Where-Object Thumbprint -eq $certificateResult.Thumbprint |
|
||||
Select-Object -First 1
|
||||
}
|
||||
else {
|
||||
New-Item -ItemType Directory -Path $certificateDirectory -Force | Out-Null
|
||||
$publicCertificatePath = Join-Path $certificateDirectory 'sgu-auth-broker.cer'
|
||||
Export-Certificate -Cert $serverCertificate -FilePath $publicCertificatePath -Force | Out-Null
|
||||
if (-not (Get-ChildItem Cert:\LocalMachine\Root | Where-Object Thumbprint -eq $serverCertificate.Thumbprint)) {
|
||||
Import-Certificate -FilePath $publicCertificatePath `
|
||||
-CertStoreLocation Cert:\LocalMachine\Root | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$allowedClientThumbprints = @()
|
||||
$brokerConfigurationPath = 'C:\Program Files\SGU\AuthBroker\appsettings.Production.json'
|
||||
if (Test-Path -LiteralPath $brokerConfigurationPath -PathType Leaf) {
|
||||
$priorConfiguration = Get-Content -LiteralPath $brokerConfigurationPath -Raw | ConvertFrom-Json
|
||||
$allowedClientThumbprints = @($priorConfiguration.Broker.Tls.AllowedClientThumbprints)
|
||||
}
|
||||
|
||||
& (Join-Path $scriptsRoot 'Deploy-AuthBroker.ps1') `
|
||||
-PublishPath $brokerPublishPath `
|
||||
-ServerCertificateSubject $brokerDnsName `
|
||||
-AllowedClientThumbprints $allowedClientThumbprints `
|
||||
-LdapHost $env:COMPUTERNAME `
|
||||
-BaseDn $baseDn `
|
||||
-DomainNetbios $DomainNetbios `
|
||||
-UpnSuffix $DomainName `
|
||||
-RemoteDesktopGroupDn $remoteDesktopGroup.DistinguishedName `
|
||||
-DefaultCompany 'La Salle' `
|
||||
-CreateMissingOus `
|
||||
-DisableCertificateRevocationCheckForLab | Out-Null
|
||||
|
||||
& (Join-Path $scriptsRoot 'Enable-SguServerRemoteManagement.ps1') | Out-Null
|
||||
|
||||
$contentPath = Join-Path $bootstrapRoot 'payload\server-content\Packages'
|
||||
if (Test-Path -LiteralPath $contentPath -PathType Container) {
|
||||
New-Item -ItemType Directory -Path $PackageSharePath -Force | Out-Null
|
||||
Copy-Item -Path (Join-Path $contentPath '*') -Destination $PackageSharePath -Recurse -Force
|
||||
}
|
||||
Set-PackageShare -Path $PackageSharePath -NetbiosName $DomainNetbios `
|
||||
-DomainSid $domain.DomainSID.Value
|
||||
|
||||
& (Join-Path $scriptsRoot 'Set-SguDomainComputerPolicies.ps1') `
|
||||
-TargetOuDn $laboratoryOuDn -DomainController $env:COMPUTERNAME | Out-Null
|
||||
$userPolicyParameters = @{
|
||||
TargetOuDn = $usersOuDn
|
||||
DomainController = $env:COMPUTERNAME
|
||||
}
|
||||
$wallpaper = Get-ChildItem -LiteralPath $PackageSharePath -File -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.BaseName -eq 'wallpaper' -and $_.Extension -in @('.jpg','.jpeg','.png','.bmp') } |
|
||||
Sort-Object Name |
|
||||
Select-Object -First 1
|
||||
if ($wallpaper) {
|
||||
$userPolicyParameters.WallpaperPath = "\\$env:COMPUTERNAME\Packages\$($wallpaper.Name)"
|
||||
}
|
||||
& (Join-Path $scriptsRoot 'Set-SguDomainUserPolicies.ps1') @userPolicyParameters | Out-Null
|
||||
|
||||
$validation = [ordered]@{
|
||||
CompletedAt = (Get-Date).ToString('o')
|
||||
ComputerName = $env:COMPUTERNAME
|
||||
DomainName = $DomainName
|
||||
ServerIPv4Address = $ServerIPv4Address.IPAddressToString
|
||||
BrokerDnsName = $brokerDnsName
|
||||
BrokerCertificateThumbprint = $serverCertificate.Thumbprint
|
||||
BrokerService = (Get-Service SGUAuthBroker).Status.ToString()
|
||||
BrokerPortListening = [bool](Get-NetTCPConnection -LocalPort 8443 -State Listen -ErrorAction SilentlyContinue)
|
||||
WinRM = (Get-Service WinRM).Status.ToString()
|
||||
RemoteDesktop = (Get-Service TermService).Status.ToString()
|
||||
PackageShare = "\\$env:COMPUTERNAME\Packages"
|
||||
LaboratoryOu = $laboratoryOuDn
|
||||
UsersOu = $usersOuDn
|
||||
RemoteDesktopGroup = $remoteDesktopGroup.DistinguishedName
|
||||
}
|
||||
|
||||
if ($validation.BrokerService -ne 'Running' -or
|
||||
-not $validation.BrokerPortListening -or
|
||||
$validation.WinRM -ne 'Running' -or
|
||||
$validation.RemoteDesktop -ne 'Running') {
|
||||
throw 'Server finalization did not pass service validation. Review bootstrap.log and re-run the bootstrap.'
|
||||
}
|
||||
|
||||
[IO.File]::WriteAllText(
|
||||
$completionPath,
|
||||
($validation | ConvertTo-Json -Depth 4),
|
||||
[Text.UTF8Encoding]::new($false))
|
||||
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
|
||||
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
|
||||
}
|
||||
Remove-Item -LiteralPath $statePath -Force -ErrorAction SilentlyContinue
|
||||
Write-BootstrapLog 'SGU domain controller bootstrap completed successfully.'
|
||||
[pscustomobject]$validation
|
||||
Reference in New Issue
Block a user