Last active
March 31, 2025 14:56
-
-
Save Bill-Stewart/f4c4bdb6e1fe851e1a203d674111dbb1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Wait-NetConnectionDomain.ps1 | |
# Written by Bill Stewart | |
# Under certain circumstances, Windows might not detect it's network location | |
# correctly. The most obvious manifestation of this problem is that the Windows | |
# Firewall 'profile' gets set to the wrong network. On a domain, this can be a | |
# headache when firewall rules are set to only be active for the 'Domain' | |
# firewall profile. It seems that, under some conditions on domain-joined | |
# computers, the Network Location Awareness (NlaSvc) service doesn't update | |
# its status correctly. | |
# | |
# However, on some Windows versions, we can't just stop the NlaSvc service | |
# using standard Windows service control commands. To work around this, we | |
# kill the service's process by process ID, then start the service again. | |
# | |
# The purpose of this script is to run the Get-NetConnectionProfile cmdlet | |
# and check whether the system detects if any of the connected networks are | |
# classified as 'DomainAuthenticated'. If not, we need to kill the NlaSvc | |
# service process and restart the service. | |
# | |
# The script makes liberal use of Write-Host to output status to the console, | |
# and also logs its activity to the following file: | |
# | |
# %SystemRoot%\Logs\<script file name>.log | |
# | |
# The script does the following: | |
# | |
# 1. If the NlaSvc service is not running, wait for up to 5 minutes for it to | |
# start (handy if the service is set to 'Automatic (Delayed Start)') | |
# 2. Check for any domain-authenticated network connections; if none found: | |
# a. Kill the NlaSvc service process | |
# b. Start the NlaSvc service | |
# | |
# The script repeats the last step until either a) it finds a domain- | |
# authenticated network connection or b) it times out after 5 minutes. | |
# | |
# Version history: | |
# | |
# 2025-01-29 | |
# * Fixed accidental omission of error catching at top of script. | |
# | |
# 2025-01-22 | |
# * Initial version. | |
#requires -version 5 | |
#requires -RunAsAdministrator | |
$ERROR_SERVICE_REQUEST_TIMEOUT = 1053 | |
$ERROR_SERVICE_DOES_NOT_EXIST = 1060 | |
# Restart this service | |
$ServiceName = "NlaSvc" | |
$TranscriptFilePath = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::Windows)) "Logs" | |
$TranscriptFileName = "{0}.log" -f [IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name) | |
Start-Transcript (Join-Path $TranscriptFilePath $TranscriptFileName) | |
try { | |
# We need Get-NetConnectionProfile from the NetConnection module | |
Import-Module NetConnection -ErrorAction Stop | |
# So we can use types from ServiceProcess class before we call any of the | |
# service management cmdlets | |
Add-Type -AssemblyName System.ServiceProcess -ErrorAction Stop | |
} | |
catch { | |
Write-Error -Exception $_.Exception | |
Stop-Transcript | |
exit | |
} | |
# Exit script if the computer is not joined to a domain. | |
if ( -not (Get-CimInstance -Class Win32_ComputerSystem).PartOfDomain ) { | |
Write-Host "This computer is not joined to a domain." | |
Stop-Transcript | |
return | |
} | |
# Tests whether any network connection is domain-authenticated. | |
function Test-NetConnectionDomain { | |
(Get-NetConnectionProfile | Select-Object -ExpandProperty NetworkCategory) -contains | |
[Microsoft.PowerShell.Cmdletization.GeneratedTypes.NetConnectionProfile.NetworkCategory]::DomainAuthenticated | |
} | |
# Exit script if we find a domain-authenticated network connection. | |
if ( Test-NetConnectionDomain ) { | |
Write-Host "Domain-authenticated network connection found." | |
Stop-Transcript | |
return | |
} | |
# Waits for a service to get to a desired status. | |
function Wait-ServiceStatus { | |
param( | |
[String] | |
$serviceName, | |
[ServiceProcess.ServiceControllerStatus] | |
$status, | |
[Int] | |
$timeoutSeconds = 30 | |
) | |
$svcController = Get-Service $serviceName -ErrorAction SilentlyContinue | |
if ( $null -eq $svcController ) { | |
return $ERROR_SERVICE_DOES_NOT_EXIST | |
} | |
if ( $svcController.Status -eq $status ) { | |
Write-Host ("Service '{0}' ({1}) is running." -f | |
$svcController.DisplayName,$svcController.Name) | |
return 0 | |
} | |
$timeSpan = New-Object Timespan 0,0,$timeoutSeconds | |
try { | |
Write-Host ("Waiting for service '{0}' ({1}) to start." -f | |
$svcController.DisplayName,$svcController.Name) | |
$svcController.WaitForStatus($status,$timeSpan) | |
return 0 | |
} | |
catch [Management.Automation.MethodInvocationException],[ServiceProcess.TimeoutException] { | |
Write-Host ("Service '{0}' ({1}) did not start within {2:N0} second(s)." -f | |
$svcController.DisplayName,$svcController.Name,$timeoutSeconds) | |
return $ERROR_SERVICE_REQUEST_TIMEOUT | |
} | |
} | |
# Wait for up to 5 minutes for the service to start. This is handy in the case | |
# where this script runs at startup and the service start might be delayed. | |
if ( (Wait-ServiceStatus $ServiceName "Running" 300) -ne 0 ) { | |
Stop-Transcript | |
return | |
} | |
# Kills a service's process and waits up to a specified number of seconds for | |
# it to terminate. Returns 0 for success, or non-zero for failure. | |
function Stop-ServiceProcess { | |
param( | |
[String] | |
$serviceName, | |
[Int] | |
$timeoutSeconds = 30 | |
) | |
$cimService = Get-CimInstance Win32_Service -Filter ("Name='{0}'" -f $serviceName) -ErrorAction SilentlyContinue | |
if ( $null -eq $cimService ) { | |
Write-Host "Service '$serviceName' does not exist." | |
return $ERROR_SERVICE_DOES_NOT_EXIST | |
} | |
$stopwatch = New-Object Diagnostics.Stopwatch | |
$stopwatch.Start() | |
do { | |
$processId = $cimService.ProcessId | |
Write-Host ("Stopping process ID {0} for service '{1}' ({2})." -f | |
$processId,$cimService.DisplayName,$cimService.Name) | |
Stop-Process $processId -Force -ErrorAction SilentlyContinue | |
Start-Sleep -Milliseconds 250 | |
$cimService = Get-CimInstance Win32_Service -Filter ("Name='{0}'" -f $serviceName) | |
} | |
until ( ($cimService.State -eq "Stopped") -or ($stopwatch.Elapsed.TotalSeconds -ge $timeoutSeconds) ) | |
if ( $cimService.State -eq "Stopped" ) { | |
Write-Host ("Process ID {0} for service '{1}' ({2}) stopped successfully." -f | |
$processId,$cimService.DisplayName,$cimService.Name) | |
return 0 | |
} | |
else { | |
Write-Host ("Failed to stop process for service '{0}' ({1}) within {2:N0} second(s)." -f | |
$cimService.DisplayName,$cimService.Name,$timeoutSeconds) | |
return $ERROR_SERVICE_REQUEST_TIMEOUT | |
} | |
} | |
# Starts a service and waits for up to a specified number of seconds for it to | |
# start. Returns 0 for success, or non-zero for failure. | |
function Start-ServiceEx { | |
param( | |
[String] | |
$serviceName, | |
[Int] | |
$timeoutSeconds = 30 | |
) | |
$svcController = Get-Service $serviceName | |
if ( $null -eq $svcController ) { | |
return $ERROR_SERVICE_DOES_NOT_EXIST | |
} | |
if ( $svcController.Status -eq [ServiceProcess.ServiceControllerStatus]::Running ) { | |
Write-Host ("Service '{0}' ({1}) is already running." -f | |
$svcController.DisplayName,$svcController.Name) | |
return 0 | |
} | |
$timeSpan = New-Object Timespan 0,0,$timeoutSeconds | |
try { | |
Write-Host ("Starting service '{0}' ({1})." -f $svcController.DisplayName, | |
$svcController.Name) | |
$svcController.Start() | |
$svcController.WaitForStatus([ServiceProcess.ServiceControllerStatus]::Running,$timeSpan) | |
Write-Host ("Service '{0}' ({1}) started successfully." -f | |
$svcController.DisplayName,$svcController.Name) | |
return 0 | |
} | |
catch [Management.Automation.MethodInvocationException],[ServiceProcess.TimeoutException] { | |
Write-Host ("Service '{0}' ({1}) did not start within {2:N0} second(s)." -f | |
$svcController.DisplayName,$svcController.Name,$timeoutSeconds) | |
return $ERROR_SERVICE_REQUEST_TIMEOUT | |
} | |
} | |
# Check for a domain-authenticated network connection for up to 5 minutes. | |
# If we don't find a domain-authenticated network connection: | |
# * Kill the NlaSvc service process | |
# * Start the NlaSvc service | |
# * Wait 5 seconds | |
function Wait-NetConnectionDomain { | |
$timeoutSeconds = 300 | |
$stopwatch = New-Object Diagnostics.Stopwatch | |
$stopwatch.Start() | |
do { | |
$domainAuthenticated = Test-NetConnectionDomain | |
if ( $domainAuthenticated ) { | |
Write-Host "Domain-authenticated network connection found." | |
} | |
else { | |
Write-Host "Domain-authenticated network connection not found. Will retry." | |
if ( (Stop-ServiceProcess $ServiceName) -eq 0 ) { | |
Start-ServiceEx $ServiceName | Out-Null | |
} | |
Start-Sleep -Seconds 5 | |
} | |
} | |
until ( ($domainAuthenticated) -or ($stopwatch.Elapsed.TotalSeconds -ge $timeoutSeconds) ) | |
if ( -not $domainAuthenticated ) { | |
Write-Host ("No domain-authenticated network connections found within '{0:N0}' second(s)." -f | |
$timeoutSeconds) | |
} | |
} | |
Wait-NetConnectionDomain | |
Stop-Transcript |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment