100 lines
4.5 KiB
PowerShell
100 lines
4.5 KiB
PowerShell
#Requires -Version 5.1
|
|
#Requires -RunAsAdministrator
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$ConnectionName = 'SGU Azure Device',
|
|
[ValidateRange(30,600)][int]$WaitSeconds = 180
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$computer = Get-CimInstance Win32_ComputerSystem
|
|
if (-not $computer.PartOfDomain) { throw 'The device must already be joined to its domain.' }
|
|
$logPath = Join-Path $env:ProgramData 'SGU\Enrollment\azure-domain-connectivity.json'
|
|
$deadline = (Get-Date).AddSeconds($WaitSeconds)
|
|
$restarted = $false
|
|
$controller = $null
|
|
try {
|
|
do {
|
|
$vpn = Get-VpnConnection -Name $ConnectionName -AllUserConnection -ErrorAction SilentlyContinue
|
|
$reachable = $false
|
|
if ($vpn -and $vpn.ConnectionStatus -eq 'Connected') {
|
|
$record = Resolve-DnsName "_ldap._tcp.dc._msdcs.$($computer.Domain)" -Type SRV -ErrorAction SilentlyContinue |
|
|
Where-Object Type -eq 'SRV' | Select-Object -First 1
|
|
if ($record) {
|
|
$controller = $record.NameTarget.TrimEnd('.')
|
|
$socket = [Net.Sockets.TcpClient]::new()
|
|
try {
|
|
$connect = $socket.BeginConnect($controller, 389, $null, $null)
|
|
if ($connect.AsyncWaitHandle.WaitOne(2000)) {
|
|
$socket.EndConnect($connect)
|
|
$reachable = $socket.Connected
|
|
}
|
|
} catch { $reachable = $false }
|
|
finally { $socket.Dispose() }
|
|
}
|
|
}
|
|
if ($reachable) { break }
|
|
Start-Sleep -Seconds 5
|
|
} while ((Get-Date) -lt $deadline)
|
|
if (-not $reachable) { throw "The VPN and a domain controller were not reachable within $WaitSeconds seconds." }
|
|
|
|
# An early Netlogon attempt can remain failed after the device VPN connects.
|
|
# Refresh only that service, after confirming the domain is reachable.
|
|
if (-not (Test-ComputerSecureChannel -Server $controller)) {
|
|
Restart-Service -Name Netlogon
|
|
$restarted = $true
|
|
}
|
|
$secure = $false
|
|
for ($attempt = 0; $attempt -lt 6; $attempt++) {
|
|
$secure = Test-ComputerSecureChannel -Server $controller
|
|
if ($secure) { break }
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
if (-not $secure) { throw 'The domain is reachable but the secure channel is still invalid. Administrative repair is required.' }
|
|
$guard = Get-ScheduledTask -TaskName 'SGU-CredentialProvider-EnrollmentGuard' -ErrorAction SilentlyContinue
|
|
$guardResult = $null
|
|
if ($guard) {
|
|
# Domain principal lookup can recover after the secure channel itself.
|
|
# Await the guard and retry a transient failure instead of reporting
|
|
# success while its asynchronous repair is still running or failed.
|
|
$guardDeadline = (Get-Date).AddMinutes(3)
|
|
do {
|
|
$guard = Get-ScheduledTask -TaskName $guard.TaskName
|
|
if ($guard.State -notin @('Running','Queued')) {
|
|
$previousRun = (Get-ScheduledTaskInfo -TaskName $guard.TaskName).LastRunTime
|
|
Start-ScheduledTask -InputObject $guard
|
|
do {
|
|
Start-Sleep -Seconds 2
|
|
$guard = Get-ScheduledTask -TaskName $guard.TaskName
|
|
$info = Get-ScheduledTaskInfo -TaskName $guard.TaskName
|
|
} while (($info.LastRunTime -le $previousRun -or $guard.State -in @('Running','Queued')) -and (Get-Date) -lt $guardDeadline)
|
|
if ($info.LastRunTime -gt $previousRun -and $guard.State -notin @('Running','Queued')) {
|
|
$guardResult = $info.LastTaskResult
|
|
if ($guardResult -eq 0) { break }
|
|
}
|
|
}
|
|
Start-Sleep -Seconds 10
|
|
} while ((Get-Date) -lt $guardDeadline)
|
|
if ($guardResult -ne 0) { throw "The secure channel recovered, but the enrollment guard did not succeed (result $guardResult)." }
|
|
}
|
|
[pscustomobject]@{
|
|
CheckedAt = (Get-Date).ToString('o')
|
|
ComputerName = $computer.Name
|
|
Domain = $computer.Domain
|
|
DomainController = $controller
|
|
ConnectionName = $ConnectionName
|
|
NetlogonRestarted = $restarted
|
|
SecureChannel = $secure
|
|
EnrollmentGuardResult = $guardResult
|
|
} | ConvertTo-Json | Set-Content -LiteralPath $logPath
|
|
} catch {
|
|
[pscustomobject]@{
|
|
CheckedAt = (Get-Date).ToString('o')
|
|
ConnectionName = $ConnectionName
|
|
NetlogonRestarted = $restarted
|
|
SecureChannel = $false
|
|
Error = $_.Exception.Message
|
|
} | ConvertTo-Json | Set-Content -LiteralPath $logPath
|
|
throw
|
|
}
|