For those of you who have deployed Microsoft Password Change Notification Service (PCNS) services in a large domain (or as in this case a complete forest with many domains), you will know the mission of ensuring that PCNS is installed everywhere.
Sure you can use GPO or ConfigMgr to ensure the service is installed on all DC, but sometimes one (or many) is missed or fails. This results in password changes being “lost”.
I have found the simplest way to fix this frustration is to use a simple PowerShell script to enumerate the forest, domains and domain controllers to test if the service is installed and active.
The following script is a simple example of how this can be achieved. At Integralis we have turned this into a monitor that reports into our monitoring platforms to automate the process (for large environments that does not have our agents on all domain controllers).
The approach is generic and will work for any service.
<# | |
.SYNOPSIS | |
Tester – Service Status.ps1 – Tester to check the status of all PCNSSVC services on all current domain context DCs. | |
.DESCRIPTION | |
The scripts will look for all current domain context DC and query the status of the PCNSSVC service. | |
Possible status messages include: | |
– running | |
– stopped | |
– starting | |
– unreachable (the server is not avaiable on the network) | |
.LINK | |
Version 1.0 – Base features | |
Version 1.3 – Added nice(r) message for unreachable | |
#> | |
Import-Module ActiveDirectory | |
$ADForest = Get-ADForest | |
$ADForestDomainNamingMaster = $ADForest.DomainNamingMaster | |
$ADForestDomains = $ADForest.Domains | |
$ADForestSchemaMaster = $ADForest.SchemaMaster | |
$ADInfo = Get-ADDomain | |
$ADDomainDNSRoot = $ADInfo.DNSRoot | |
$ADDomainInfrastructureMaster = $ADInfo.InfrastructureMaster | |
$ADDomainPDCEmulator = $ADInfo.PDCEmulator | |
$ADDomainReadOnlyReplicaDirectoryServers = $ADInfo.ReadOnlyReplicaDirectoryServers | |
$ADDomainReplicaDirectoryServers = $ADInfo.ReplicaDirectoryServers | |
$ADDomainRIDMaster = $ADInfo.RIDMaster | |
Write-Verbose “Discovering Domain Controllers in the AD Forest $ADForestName `r “ | |
ForEach ($Domain in $ADForestDomains) | |
{ ## OPEN ForEach Domain in ADForestDomains | |
$DomainDCs = Get-ADDomainController -filter * -server $Domain | |
ForEach ($DC in $DomainDCs) | |
{ ## OPEN ForEach DC in DomainDCs | |
$DCName = $DC.HostName | |
Write-Verbose “Adding $DCName to ForestDC list `r “ | |
[array] $ForestDCs += $DC.HostName | |
} ## CLOSE ForEach DC in DomainDCs | |
} ## CLOSE ForEach Domain in ADForestDomains | |
$ForestDCsCount = $ForestDCs.count | |
Write-Verbose “Initial discovery found $ForestDCsCount DCs `r “ | |
# Add all DC lists into $DomainControllers | |
$DomainControllers = $ForestDCs + $ADDomainReadOnlyReplicaDirectoryServers + $ADDomainReplicaDirectoryServers + $ADForestGlobalCatalogs | |
# Remove duplicate DCs from $DomainControllers | |
$DomainControllers = $DomainControllers | Select-Object -Unique | |
# Sort the $DomainControllers DC list | |
$DomainControllers = $DomainControllers | Sort-Object | |
function Get-MrService { | |
[CmdletBinding()] | |
param ( | |
[ValidateNotNullOrEmpty()] | |
[string[]]$ComputerName, | |
[ValidateNotNullOrEmpty()] | |
[string[]]$ServiceName = 'pcnssvc' | |
) | |
foreach ($Computer in $ComputerName) { | |
foreach ($Service in $ServiceName) { | |
$ServiceInfo = Get-Service -Name $Service -ComputerName $Computer -ErrorAction SilentlyContinue | |
if (-not($ServiceInfo)) { | |
$ServiceInfo = @{ | |
MachineName = $Computer | |
Name = $Service | |
Status = 'Unreachable' | |
} | |
} | |
[PSCustomObject]@{ | |
ComputerName = $ServiceInfo.MachineName | |
Name = $ServiceInfo.Name | |
Status = $ServiceInfo.Status | |
} | |
} | |
} | |
} | |
foreach ($server in $DomainControllers) { | |
#Get-Service -ComputerName $server -Name PCNSSVC | select MachineName, Name, Status | |
Get-MrService -ComputerName $server | |
} |