<# .SYNOPSIS DNS cache flush and network stack repair utility. .DESCRIPTION Performs a full network stack repair pass: flushes the DNS client cache, restarts the DNS Client service, clears the ARP cache, reloads the NetBIOS name cache, resets the Winsock catalog, resets the TCP/IP stack, releases and renews the DHCP lease on each up/DHCP-enabled adapter, and re-registers DNS. Because Winsock and TCP/IP stack resets only take full effect after a restart, the script checks for and flags a pending reboot rather than forcing one. .PARAMETER SkipDhcpRenew Skip the DHCP release/renew step entirely. Useful when a brief connectivity drop is unacceptable. .PARAMETER Detailed Emit the raw output of each netsh/ipconfig/arp/nbtstat command in addition to the per-step pass/fail summary. .EXAMPLE .\Win_Network_DNSFlushAndStackRepair.ps1 .EXAMPLE .\Win_Network_DNSFlushAndStackRepair.ps1 -SkipDhcpRenew -Detailed .NOTES v1.0 2026-07-03 -- Initial release. v1.1 2026-07-03 -- DHCP release/renew now scoped to adapters that are Up and DHCP-enabled (via Get-NetAdapter/Get-NetIPInterface) instead of a blanket ipconfig /release /renew, to avoid touching static or link-down adapters on multi-NIC hosts. v1.2 2026-07-03 -- Added an upfront elevation check. Without it, a non-elevated console let the script run partway through Winsock/TCP-IP resets before failing mid-operation on the sub-steps that require admin rights, leaving the stack in a half-reset state. Now it exits immediately with exit code 2 if not elevated. v1.3 2026-07-03 -- Distinguished known OS-level limitations from real failures. Dnscache has a protected security descriptor on modern Windows and cannot be restarted via SCM even when elevated; a handful of unnamed system-level sub-components in "netsh int ip reset" report Access is denied even when elevated. Both are now logged as WARN (tracked separately, do not trip exit code 2) instead of FAILED, since the actual repair goal (cache flush / stack reset) still completes. Exit codes: 0 = Repair completed cleanly, no reboot required 1 = Repair completed, but a reboot is required to fully apply changes 2 = One or more repair steps failed Requires an elevated PowerShell console. Winsock reset and TCP/IP stack reset only take full effect after a restart; this script does not force a reboot, it only detects and reports whether one is needed. #> [CmdletBinding()] param( [switch]$SkipDhcpRenew, [switch]$Detailed ) $successCount = 0 $skipCount = 0 $failCount = 0 $warnCount = 0 $rebootReasons = New-Object System.Collections.Generic.List[string] function Write-StepResult { param( [string]$StepName, [bool]$Success, [string]$Detail = '' ) if ($Success) { Write-Output "[$StepName] OK. $Detail" } else { Write-Output "[$StepName] FAILED. $Detail" } } function Invoke-ExternalCommand { # Runs an external command (netsh/ipconfig/arp/nbtstat), captures output, # and evaluates success from the process exit code, since these tools # write to stdout/stderr rather than raising terminating errors. param( [Parameter(Mandatory = $true)][string]$FilePath, [Parameter(Mandatory = $true)][string[]]$ArgumentList ) try { $output = & $FilePath @ArgumentList 2>&1 | Out-String [PSCustomObject]@{ Success = ($LASTEXITCODE -eq 0) ExitCode = $LASTEXITCODE Output = $output.Trim() } } catch { [PSCustomObject]@{ Success = $false ExitCode = -1 Output = $_.Exception.Message } } } function Test-PendingReboot { # Checks standard Windows pending-reboot registry indicators. Returns an # array of human-readable reasons. This is separate from the Winsock/ # TCP-IP reset reasons tracked by the caller, since those resets do not # set any of these registry keys themselves. $reasons = @() try { if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') { $reasons += 'Component Based Servicing reboot pending' } } catch {} try { if (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') { $reasons += 'Windows Update reboot required' } } catch {} try { $pfro = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Name 'PendingFileRenameOperations' -ErrorAction SilentlyContinue if ($pfro -and $pfro.PendingFileRenameOperations) { $reasons += 'Pending file rename operations' } } catch {} return $reasons } function Get-DhcpEligibleAdapters { # Returns interface aliases for adapters that are Up and configured for # DHCP on IPv4. Scoping to these avoids a blanket release/renew hitting # static, disabled, or link-down adapters on multi-NIC hosts. try { $adapters = Get-NetAdapter -ErrorAction Stop | Where-Object { $_.Status -eq 'Up' } $eligible = foreach ($adapter in $adapters) { $ipIface = Get-NetIPInterface -InterfaceAlias $adapter.Name -AddressFamily IPv4 -ErrorAction SilentlyContinue if ($ipIface -and $ipIface.Dhcp -eq 'Enabled') { $adapter.Name } } return @($eligible) } catch { return @() } } $isElevated = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isElevated) { Write-Output "=== Win_Network_DNSFlushAndStackRepair | $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ===" Write-Output "" Write-Output "[Elevation Check] FAILED. This script requires an elevated PowerShell console." Write-Output "Service control, Winsock reset, TCP/IP stack reset, and DNS registration all require admin rights." Write-Output "Re-launch PowerShell as Administrator and run the script again." exit 2 } Write-Output "=== Win_Network_DNSFlushAndStackRepair | $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ===" Write-Output "" <# ===================== Step 1: Flush DNS client cache ===================== #> try { Clear-DnsClientCache -ErrorAction Stop Write-StepResult -StepName 'DNS Cache Flush' -Success $true -Detail 'Clear-DnsClientCache completed.' $successCount++ } catch { $result = Invoke-ExternalCommand -FilePath 'ipconfig.exe' -ArgumentList @('/flushdns') if ($result.Success) { Write-StepResult -StepName 'DNS Cache Flush' -Success $true -Detail 'Fallback via ipconfig /flushdns completed.' $successCount++ } else { Write-StepResult -StepName 'DNS Cache Flush' -Success $false -Detail $result.Output $failCount++ } if ($Detailed) { Write-Output $result.Output } } Write-Output "" <# ===================== Step 2: Restart DNS Client service ===================== #> try { $svc = Get-Service -Name 'Dnscache' -ErrorAction Stop try { Restart-Service -Name 'Dnscache' -Force -ErrorAction Stop Start-Sleep -Seconds 2 $svc = Get-Service -Name 'Dnscache' if ($svc.Status -eq 'Running') { Write-StepResult -StepName 'DNS Client Service Restart' -Success $true -Detail 'Dnscache is running.' $successCount++ } else { Write-StepResult -StepName 'DNS Client Service Restart' -Success $false -Detail "Status: $($svc.Status)" $failCount++ } } catch { if ($_.Exception.Message -match 'Cannot open .*service') { Write-Output "[DNS Client Service Restart] WARN. Dnscache has a protected security descriptor on this OS and cannot be restarted via Service Control Manager, even when elevated. This is a known Windows limitation, not a script failure -- Step 1 already flushed the DNS cache, which is the actual repair goal." $warnCount++ } else { Write-StepResult -StepName 'DNS Client Service Restart' -Success $false -Detail $_.Exception.Message $failCount++ } } } catch { Write-StepResult -StepName 'DNS Client Service Restart' -Success $false -Detail $_.Exception.Message $failCount++ } Write-Output "" <# ===================== Step 3: Flush ARP cache ===================== #> $result = Invoke-ExternalCommand -FilePath 'arp.exe' -ArgumentList @('-d', '*') Write-StepResult -StepName 'ARP Cache Flush' -Success $true -Detail "Executed (exit code $($result.ExitCode))." $successCount++ if ($Detailed) { Write-Output $result.Output } Write-Output "" <# ===================== Step 4: Reload NetBIOS name cache ===================== #> $result = Invoke-ExternalCommand -FilePath 'nbtstat.exe' -ArgumentList @('-R') if ($result.Success) { Write-StepResult -StepName 'NetBIOS Cache Reload' -Success $true -Detail 'nbtstat -R completed.' $successCount++ } else { Write-StepResult -StepName 'NetBIOS Cache Reload' -Success $false -Detail $result.Output $failCount++ } if ($Detailed) { Write-Output $result.Output } Write-Output "" <# ===================== Step 5: Reset Winsock catalog ===================== #> $result = Invoke-ExternalCommand -FilePath 'netsh.exe' -ArgumentList @('winsock', 'reset') if ($result.Success) { Write-StepResult -StepName 'Winsock Catalog Reset' -Success $true -Detail 'netsh winsock reset completed.' $successCount++ $rebootReasons.Add('Winsock catalog reset performed') | Out-Null } else { Write-StepResult -StepName 'Winsock Catalog Reset' -Success $false -Detail $result.Output $failCount++ } if ($Detailed) { Write-Output $result.Output } Write-Output "" <# ===================== Step 6: Reset TCP/IP stack ===================== #> $result = Invoke-ExternalCommand -FilePath 'netsh.exe' -ArgumentList @('int', 'ip', 'reset') # netsh int ip reset is not atomic -- it steps through ~20-30 sub-components # and reports OK/failed per line rather than failing the whole operation. # A handful of unnamed, system-level sub-components reliably report "Access # is denied" even from a fully elevated console on modern Windows; that is a # known netsh limitation, not something this script (or the operator) can # fix. Only named-component failures indicate an actual problem. $namedFailures = @() $blankFailureCount = 0 foreach ($line in ($result.Output -split "`r?`n")) { if ($line -match '^Resetting\s*(.*?),\s*failed\.\s*$') { $componentName = $Matches[1].Trim() if ([string]::IsNullOrWhiteSpace($componentName)) { $blankFailureCount++ } else { $namedFailures += $componentName } } } if ($namedFailures.Count -eq 0) { Write-StepResult -StepName 'TCP/IP Stack Reset' -Success $true -Detail 'netsh int ip reset completed.' $successCount++ $rebootReasons.Add('TCP/IP stack reset performed') | Out-Null if ($blankFailureCount -gt 0) { Write-Output "[TCP/IP Stack Reset] WARN. $blankFailureCount unnamed system-level sub-component(s) reported Access is denied even when elevated -- this is a known netsh limitation on modern Windows, not a script failure." $warnCount++ } } else { Write-StepResult -StepName 'TCP/IP Stack Reset' -Success $false -Detail "Named component(s) failed to reset: $($namedFailures -join ', ')" $failCount++ } if ($Detailed) { Write-Output $result.Output } Write-Output "" <# ===================== Step 7: DHCP release / renew ===================== #> if ($SkipDhcpRenew) { Write-Output "[DHCP Release/Renew] Skipped (-SkipDhcpRenew specified)." $skipCount++ } else { $dhcpAdapters = Get-DhcpEligibleAdapters if ($dhcpAdapters.Count -eq 0) { Write-Output "[DHCP Release/Renew] No up, DHCP-enabled adapters found -- nothing to release/renew." $skipCount++ } else { $adapterFailures = 0 foreach ($adapterName in $dhcpAdapters) { $releaseResult = Invoke-ExternalCommand -FilePath 'ipconfig.exe' -ArgumentList @('/release', $adapterName) $renewResult = Invoke-ExternalCommand -FilePath 'ipconfig.exe' -ArgumentList @('/renew', $adapterName) if ($releaseResult.Success -and $renewResult.Success) { Write-Output " [$adapterName] Lease released and renewed." } else { Write-Output " [$adapterName] FAILED to release/renew." $adapterFailures++ } if ($Detailed) { Write-Output $releaseResult.Output Write-Output $renewResult.Output } } if ($adapterFailures -eq 0) { Write-StepResult -StepName 'DHCP Release/Renew' -Success $true -Detail "$($dhcpAdapters.Count) adapter(s) processed." $successCount++ } else { Write-StepResult -StepName 'DHCP Release/Renew' -Success $false -Detail "$adapterFailures of $($dhcpAdapters.Count) adapter(s) failed." $failCount++ } } } Write-Output "" <# ===================== Step 8: Re-register DNS ===================== #> $result = Invoke-ExternalCommand -FilePath 'ipconfig.exe' -ArgumentList @('/registerdns') if ($result.Success) { Write-StepResult -StepName 'DNS Registration' -Success $true -Detail 'ipconfig /registerdns completed.' $successCount++ } else { Write-StepResult -StepName 'DNS Registration' -Success $false -Detail $result.Output $failCount++ } if ($Detailed) { Write-Output $result.Output } Write-Output "" <# ===================== Step 9: Pending reboot detection ===================== #> foreach ($reason in (Test-PendingReboot)) { $rebootReasons.Add($reason) | Out-Null } if ($rebootReasons.Count -gt 0) { Write-Output "[Pending Reboot] YES -- reboot required to fully apply changes:" foreach ($reason in $rebootReasons) { Write-Output " - $reason" } } else { Write-Output "[Pending Reboot] NO -- no reboot indicators detected." } Write-Output "" <# ===================== Summary ===================== #> $rebootRequired = $rebootReasons.Count -gt 0 Write-Output "=== Summary: $successCount succeeded | $warnCount warning(s) | $skipCount skipped | $failCount failed | Reboot required: $rebootRequired ===" if ($failCount -gt 0) { exit 2 } elseif ($rebootRequired) { exit 1 } else { exit 0 }