Skip to content

Instantly share code, notes, and snippets.

@Bill-Stewart
Last active March 31, 2025 14:56
Show Gist options
  • Save Bill-Stewart/f4c4bdb6e1fe851e1a203d674111dbb1 to your computer and use it in GitHub Desktop.
Save Bill-Stewart/f4c4bdb6e1fe851e1a203d674111dbb1 to your computer and use it in GitHub Desktop.
# 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