Frustrated trying to Maintain PCNS in a Multi-Domain Environment

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
}