Configure private IPv4 during Windows enrollment

This commit is contained in:
2026-09-09 10:05:59 -06:00
parent a6bd625e4e
commit 14c919b380
7 changed files with 221 additions and 14 deletions
+143 -9
View File
@@ -3,6 +3,9 @@
param(
[ipaddress]$DomainControllerIPv4Address,
[string]$NetworkInterfaceAlias,
[ipaddress]$ClientIPv4Address,
[ValidateRange(1, 32)]
[int]$ClientPrefixLength = 24,
[PSCredential]$DomainCredential,
[string]$DomainName = 'lci.lasalle.mx',
[string]$DomainNetbios = 'LCI',
@@ -16,6 +19,7 @@ param(
[securestring]$VpnClientCertificatePfxPassword,
[string]$VpnClientRootCertificatePath,
[string[]]$AzureNetworkPrefixes = @('10.77.0.0/16'),
[switch]$PauseOnError,
[switch]$SkipRestart
)
@@ -24,6 +28,35 @@ $brokerRecordName = 'sgu-auth'
$brokerDnsName = "$brokerRecordName.$DomainName"
$brokerEndpoint = "https://${brokerDnsName}:8443/v1/authenticate"
$temporaryRoot = Join-Path $env:ProgramData ("SGU\Bootstrap\Client-" + [Guid]::NewGuid().ToString('N'))
$bootstrapLogRoot = Join-Path $env:ProgramData 'SGU\Bootstrap\Client'
$bootstrapErrorLog = Join-Path $bootstrapLogRoot 'latest-error.log'
trap {
$failure = $_
$failureText = @(
"SGU client enrollment failed at $((Get-Date).ToString('s')).",
'',
$failure.Exception.Message,
'',
$failure.ScriptStackTrace
) -join [Environment]::NewLine
try {
New-Item -ItemType Directory -Path $bootstrapLogRoot -Force | Out-Null
[IO.File]::WriteAllText($bootstrapErrorLog, $failureText, [Text.UTF8Encoding]::new($false))
}
catch {
# Keep the original enrollment error when diagnostics cannot be written.
}
Write-Host ''
Write-Host 'SGU client enrollment did not complete.' -ForegroundColor Red
Write-Host $failure.Exception.Message -ForegroundColor Red
Write-Host "Diagnostic log: $bootstrapErrorLog" -ForegroundColor Yellow
if ($PauseOnError -and [Environment]::UserInteractive) {
Read-Host 'Press ENTER to close this window' | Out-Null
}
exit 1
}
function Assert-Administrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
@@ -62,21 +95,119 @@ function Resolve-ClientInterfaceAlias {
return $RequestedAlias
}
$defaultRoute = Get-NetRoute -AddressFamily IPv4 -DestinationPrefix '0.0.0.0/0' `
-ErrorAction SilentlyContinue |
Sort-Object RouteMetric,InterfaceMetric |
Select-Object -First 1
if ($defaultRoute) {
return [string](Get-NetAdapter -InterfaceIndex $defaultRoute.InterfaceIndex).Name
}
$upAdapters = @(Get-NetAdapter | Where-Object Status -eq 'Up')
$withoutDefaultGateway = @($upAdapters | Where-Object {
-not (Get-NetIPConfiguration -InterfaceIndex $_.ifIndex).IPv4DefaultGateway
})
if ($withoutDefaultGateway.Count -eq 1) {
return [string]$withoutDefaultGateway[0].Name
}
if ($upAdapters.Count -eq 1) {
return [string]$upAdapters[0].Name
}
$aliases = ($upAdapters.Name | Sort-Object) -join ', '
throw "Could not select a network adapter. Re-run with -NetworkInterfaceAlias. Available adapters: $aliases"
throw "Could not select the private domain adapter unambiguously. Re-run with -NetworkInterfaceAlias. Available adapters: $aliases"
}
function Test-IPv4AddressesSharePrefix {
param(
[Parameter(Mandatory)][ipaddress]$FirstAddress,
[Parameter(Mandatory)][ipaddress]$SecondAddress,
[Parameter(Mandatory)][ValidateRange(1, 32)][int]$PrefixLength
)
if ($FirstAddress.AddressFamily -ne [Net.Sockets.AddressFamily]::InterNetwork -or
$SecondAddress.AddressFamily -ne [Net.Sockets.AddressFamily]::InterNetwork) {
return $false
}
$firstBytes = $FirstAddress.GetAddressBytes()
$secondBytes = $SecondAddress.GetAddressBytes()
$remainingBits = $PrefixLength
for ($index = 0; $index -lt 4; $index++) {
$bits = [Math]::Min(8, $remainingBits)
$mask = if ($bits -eq 0) {
0
}
elseif ($bits -eq 8) {
255
}
else {
256 - [int][Math]::Pow(2, 8 - $bits)
}
if (($firstBytes[$index] -band $mask) -ne ($secondBytes[$index] -band $mask)) {
return $false
}
$remainingBits -= $bits
}
return $true
}
function Assert-UsableClientIPv4Address {
param(
[Parameter(Mandatory)][ipaddress]$Address,
[Parameter(Mandatory)][ipaddress]$DomainControllerAddress,
[Parameter(Mandatory)][ValidateRange(1, 32)][int]$PrefixLength
)
if ($Address.AddressFamily -ne [Net.Sockets.AddressFamily]::InterNetwork) {
throw "The SGU client address '$Address' must be IPv4."
}
if ($Address.IPAddressToString -eq $DomainControllerAddress.IPAddressToString) {
throw 'The SGU client and domain controller cannot use the same IPv4 address.'
}
if ($Address.IPAddressToString -match '^(0\.|127\.|169\.254\.|22[4-9]\.|23\d\.)') {
throw "The SGU client address '$Address' is not usable on the private domain network."
}
if (-not (Test-IPv4AddressesSharePrefix -FirstAddress $Address `
-SecondAddress $DomainControllerAddress -PrefixLength $PrefixLength)) {
throw "The SGU client address '$Address/$PrefixLength' is not on the same network as domain controller $DomainControllerAddress."
}
}
function Set-ClientDomainAddress {
param(
[Parameter(Mandatory)][string]$InterfaceAlias,
[Parameter(Mandatory)][ipaddress]$DomainControllerAddress,
[ipaddress]$RequestedAddress,
[Parameter(Mandatory)][ValidateRange(1, 32)][int]$PrefixLength
)
$adapter = Get-NetAdapter -Name $InterfaceAlias -ErrorAction Stop
$matchingAddress = Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
-ErrorAction SilentlyContinue |
Where-Object {
$_.IPAddress -notmatch '^(127\.|169\.254\.)' -and
(Test-IPv4AddressesSharePrefix -FirstAddress ([ipaddress]$_.IPAddress) `
-SecondAddress $DomainControllerAddress -PrefixLength $PrefixLength)
} |
Select-Object -First 1
if (-not $RequestedAddress -and $matchingAddress) {
return [ipaddress]$matchingAddress.IPAddress
}
if (-not $RequestedAddress) {
$RequestedAddress = [ipaddress](Read-Host "Fixed IPv4 address for this SGU client on '$InterfaceAlias'")
}
Assert-UsableClientIPv4Address -Address $RequestedAddress `
-DomainControllerAddress $DomainControllerAddress -PrefixLength $PrefixLength
Set-NetIPInterface -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 -Dhcp Disabled
$existingAddresses = @(Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
-ErrorAction SilentlyContinue | Where-Object PrefixOrigin -ne 'WellKnown')
foreach ($existingAddress in $existingAddresses) {
if ($existingAddress.IPAddress -ne $RequestedAddress.IPAddressToString -or
[int]$existingAddress.PrefixLength -ne $PrefixLength) {
Remove-NetIPAddress -InputObject $existingAddress -Confirm:$false
}
}
if (-not (Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
-IPAddress $RequestedAddress.IPAddressToString -ErrorAction SilentlyContinue)) {
New-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 `
-IPAddress $RequestedAddress.IPAddressToString -PrefixLength $PrefixLength | Out-Null
}
return $RequestedAddress
}
function Test-TcpPort {
@@ -210,6 +341,9 @@ if ($ConnectivityMode -eq 'AzureP2S') {
}
else {
$NetworkInterfaceAlias = Resolve-ClientInterfaceAlias -RequestedAlias $NetworkInterfaceAlias
$ClientIPv4Address = Set-ClientDomainAddress -InterfaceAlias $NetworkInterfaceAlias `
-DomainControllerAddress $DomainControllerIPv4Address `
-RequestedAddress $ClientIPv4Address -PrefixLength $ClientPrefixLength
Set-DnsClientServerAddress -InterfaceAlias $NetworkInterfaceAlias `
-ServerAddresses $DomainControllerIPv4Address.IPAddressToString
}