Add location-aware DNS suffix auto-switch for LBOOK
LBOOK is domain-joined to intern.beletage.ro, so the AD suffix was searched first for every single-label name. Off the office LAN, home names like home-ws were tried as home-ws.intern.beletage.ro, routed by NRPT to the AD DNS over VPN, stalling ~11s on timeout before falling back to .lan — slow RDP to home hosts. scripts/dns-location-suffix.ps1 sets the global suffix search order from the physical NIC subnet (Sophos TAP excluded): intern-first on the office LAN (10.10.10.x / 10.10.40.x), lan-first everywhere else. install-dns-location-task.ps1 registers it as a SYSTEM scheduled task triggered on network-connect (NetworkProfile 10000) and logon; verify-dns-location-task.ps1 reads it back (the task is not queryable unelevated). Also adds .claude/settings.json allowlisting read-only network diagnostics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Auto-switch the global DNS suffix search order based on network location.
|
||||
|
||||
.DESCRIPTION
|
||||
Office LAN (physical NIC in 10.10.10.x / 10.10.40.x) -> intern.beletage.ro first.
|
||||
Home / remote / VPN-from-anywhere -> lan first.
|
||||
|
||||
Rationale: LBOOK is domain-joined to intern.beletage.ro. When the AD suffix is
|
||||
searched first, every single-label home name (e.g. "home-ws") is first tried as
|
||||
<name>.intern.beletage.ro, which the NRPT rule routes to the AD DNS 10.10.10.2.
|
||||
Off the office LAN that server is only reachable over VPN (and not at all when the
|
||||
VPN is down), so the query stalls on a timeout before falling back to <name>.lan.
|
||||
Putting "lan" first makes home names resolve locally (~3 ms); work names still
|
||||
resolve via the NRPT rule after a fast local .lan NXDOMAIN.
|
||||
|
||||
The Sophos TAP adapter is excluded from detection so that starting the VPN from
|
||||
home (which may assign a 10.10.x address on the tunnel) does not flip us to the
|
||||
"office" ordering.
|
||||
|
||||
Installed as a SYSTEM scheduled task (see install-dns-location-task.ps1), triggered
|
||||
on network-connect events and at logon. Idempotent: only writes when the order
|
||||
actually needs to change.
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$log = 'C:\ProgramData\Beletage\dns-location.log'
|
||||
|
||||
$dir = Split-Path $log
|
||||
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null }
|
||||
|
||||
function Write-Log($msg) {
|
||||
$line = '{0} {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg
|
||||
Add-Content -Path $log -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
try {
|
||||
$officePrefixes = @('10.10.10.', '10.10.40.')
|
||||
|
||||
# Indices of VPN/tunnel adapters to ignore (Sophos SSL VPN TAP).
|
||||
$vpnIdx = (Get-NetAdapter -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.InterfaceDescription -like '*Sophos*' }).ifIndex
|
||||
|
||||
$ips = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.IPAddress -notlike '169.254.*' -and
|
||||
$_.IPAddress -ne '127.0.0.1' -and
|
||||
($vpnIdx -notcontains $_.InterfaceIndex)
|
||||
}).IPAddress
|
||||
|
||||
$atOffice = $false
|
||||
foreach ($ip in $ips) {
|
||||
foreach ($p in $officePrefixes) {
|
||||
if ($ip.StartsWith($p)) { $atOffice = $true }
|
||||
}
|
||||
}
|
||||
|
||||
$desired = if ($atOffice) { @('intern.beletage.ro', 'lan') } else { @('lan', 'intern.beletage.ro') }
|
||||
$current = (Get-DnsClientGlobalSetting).SuffixSearchList
|
||||
|
||||
if (($current -join ',') -ne ($desired -join ',')) {
|
||||
Set-DnsClientGlobalSetting -SuffixSearchList $desired
|
||||
Clear-DnsClientCache
|
||||
Write-Log ('CHANGED ips=[{0}] office={1} [{2}] -> [{3}]' -f ($ips -join ' '), $atOffice, ($current -join ','), ($desired -join ','))
|
||||
}
|
||||
else {
|
||||
Write-Log ('OK ips=[{0}] office={1} already [{2}]' -f ($ips -join ' '), $atOffice, ($desired -join ','))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log ('ERROR {0}' -f $_.Exception.Message)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
One-time elevated installer for the location-aware DNS suffix task.
|
||||
|
||||
.DESCRIPTION
|
||||
Copies dns-location-suffix.ps1 to C:\ProgramData\Beletage and registers a SYSTEM
|
||||
scheduled task that runs it on every network-connect event and at logon. Re-runnable
|
||||
(idempotent) and device-independent (locates the payload via $PSScriptRoot), so the
|
||||
same script installs the automation on any machine where this repo is cloned.
|
||||
|
||||
Run elevated:
|
||||
Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','<path>\install-dns-location-task.ps1'
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$taskName = 'Beletage-DNS-Location-Suffix'
|
||||
$dst = 'C:\ProgramData\Beletage'
|
||||
$payload = Join-Path $dst 'dns-location-suffix.ps1'
|
||||
$log = Join-Path $dst 'install.log'
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $dst | Out-Null
|
||||
Start-Transcript -Path $log -Append | Out-Null
|
||||
|
||||
try {
|
||||
# 1. Deploy the detection script next to where SYSTEM will run it.
|
||||
Copy-Item (Join-Path $PSScriptRoot 'dns-location-suffix.ps1') $payload -Force
|
||||
Write-Host "Copied payload -> $payload"
|
||||
|
||||
# 2. Action: run the detection script hidden, policy-bypassed.
|
||||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
|
||||
-Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$payload`""
|
||||
|
||||
# 3a. Trigger: NetworkProfile "network connected" event (ID 10000).
|
||||
$evtTrigger = New-CimInstance -ClientOnly `
|
||||
-CimClass (Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler)
|
||||
$evtTrigger.Enabled = $true
|
||||
$evtTrigger.Subscription = '<QueryList><Query Id="0" Path="Microsoft-Windows-NetworkProfile/Operational"><Select Path="Microsoft-Windows-NetworkProfile/Operational">*[System[(EventID=10000)]]</Select></Query></QueryList>'
|
||||
|
||||
# 3b. Trigger: at any user logon (covers boot / resume).
|
||||
$logonTrigger = New-ScheduledTaskTrigger -AtLogOn
|
||||
|
||||
# 4. Run as SYSTEM, highest privileges.
|
||||
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
|
||||
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 5)
|
||||
|
||||
Register-ScheduledTask -TaskName $taskName -Force `
|
||||
-Action $action -Trigger $evtTrigger, $logonTrigger -Principal $principal -Settings $settings | Out-Null
|
||||
Write-Host "Registered task: $taskName"
|
||||
|
||||
# 5. Run once now to apply the correct order immediately.
|
||||
Start-ScheduledTask -TaskName $taskName
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Host "Triggered initial run. Current suffix order: [$((Get-DnsClientGlobalSetting).SuffixSearchList -join ', ')]"
|
||||
}
|
||||
catch {
|
||||
Write-Host "INSTALL FAILED: $($_.Exception.Message)"
|
||||
throw
|
||||
}
|
||||
finally {
|
||||
Stop-Transcript | Out-Null
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Elevated read-out of the Beletage-DNS-Location-Suffix task (triggers, principal,
|
||||
last run) to C:\ProgramData\Beletage\verify.txt. The task runs as SYSTEM with a
|
||||
restrictive security descriptor, so it is not queryable from a non-elevated shell.
|
||||
|
||||
Run elevated:
|
||||
Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','<path>\verify-dns-location-task.ps1'
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$out = 'C:\ProgramData\Beletage\verify.txt'
|
||||
$name = 'Beletage-DNS-Location-Suffix'
|
||||
$lines = @()
|
||||
|
||||
try {
|
||||
$t = Get-ScheduledTask -TaskName $name
|
||||
$lines += "FOUND $($t.TaskName) State=$($t.State)"
|
||||
$lines += "Principal $($t.Principal.UserId) RunLevel=$($t.Principal.RunLevel) LogonType=$($t.Principal.LogonType)"
|
||||
$lines += "Triggers $($t.Triggers.Count)"
|
||||
foreach ($tr in $t.Triggers) {
|
||||
$cn = $tr.CimClass.CimClassName
|
||||
$tag = if ($tr.Subscription) { ' (event: NetworkProfile connected / EventID 10000)' } else { '' }
|
||||
$lines += " - $cn$tag"
|
||||
}
|
||||
$info = Get-ScheduledTaskInfo -TaskName $name
|
||||
$lines += "LastRun $($info.LastRunTime) LastResult=0x$('{0:X}' -f $info.LastTaskResult)"
|
||||
$lines += "NextRun $($info.NextRunTime)"
|
||||
}
|
||||
catch {
|
||||
$lines += "ERROR $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$lines | Set-Content -Path $out -Encoding UTF8
|
||||
Reference in New Issue
Block a user