Skip to content

Instantly share code, notes, and snippets.

@b401
Last active March 16, 2026 22:10
Show Gist options
  • Select an option

  • Save b401/46a5492c1b3de24bd69774966060db2c to your computer and use it in GitHub Desktop.

Select an option

Save b401/46a5492c1b3de24bd69774966060db2c to your computer and use it in GitHub Desktop.
poll USN for certificate changes
Add-Type -AssemblyName System.DirectoryServices.Protocols
Add-Type -AssemblyName System.DirectoryServices
$pollIntervalSeconds = 5
$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$dc = $domain.FindDomainController().Name
$rootDSE = New-Object System.DirectoryServices.DirectoryEntry("LDAPS://$dc/RootDSE")
$configNC = $rootDSE.configurationNamingContext
$baseDN = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$configNC"
$ldapId = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($dc, 389)
$conn = New-Object System.DirectoryServices.Protocols.LdapConnection($ldapId)
$conn.SessionOptions.ProtocolVersion = 3
$conn.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate
$conn.Bind()
function Get-TemplateState {
param($conn, $baseDN)
$attrs = [string[]]@("name","usnchanged","whenchanged")
$request = New-Object System.DirectoryServices.Protocols.SearchRequest(
$baseDN,
"(objectClass=pKICertificateTemplate)",
[System.DirectoryServices.Protocols.SearchScope]::OneLevel,
$attrs
)
$response = $conn.SendRequest($request)
$state = @{}
foreach ($entry in $response.Entries) {
$name = $entry.Attributes["name"][0]
$usn = $entry.Attributes["usnchanged"][0]
$state[$name] = @{
USN = $usn
WhenChanged = $entry.Attributes["whenchanged"][0]
DN = $entry.DistinguishedName
}
}
return $state
}
$previousState = Get-TemplateState -conn $conn -baseDN $baseDN
Write-Host "[*] Baseline captured: $($previousState.Count) templates" -ForegroundColor Green
foreach ($tpl in $previousState.Keys | Sort-Object) {
Write-Host " $tpl USN=$($previousState[$tpl].USN) Changed=$($previousState[$tpl].WhenChanged)"
}
Write-Host "[*] Polling every ${pollIntervalSeconds}s on $dc ..."
# --- Poll loop ---
while ($true) {
Start-Sleep -Seconds $pollIntervalSeconds
try {
$currentState = Get-TemplateState -conn $conn -baseDN $baseDN
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
# Check for new or modified templates
foreach ($name in $currentState.Keys) {
if (-not $previousState.ContainsKey($name)) {
Write-Host "[$timestamp] [NEW] Template: $name" -ForegroundColor Yellow
Write-Host "[*] Invoking Locksmith..." -ForegroundColor Cyan
# Invoke-Locksmith -Mode 4
}
elseif ($currentState[$name].USN -ne $previousState[$name].USN) {
Write-Host "[$timestamp] [MODIFIED] Template: $name USN: $($previousState[$name].USN) -> $($currentState[$name].USN)" -ForegroundColor Yellow
Write-Host "[*] Invoking Locksmith..." -ForegroundColor Cyan
# Invoke-Locksmith -Mode 4
}
}
# Check for deleted templates
foreach ($name in $previousState.Keys) {
if (-not $currentState.ContainsKey($name)) {
Write-Host "[$timestamp] [DELETED] Template: $name" -ForegroundColor Red
Write-Host "[*] Invoking Locksmith..." -ForegroundColor Cyan
# Invoke-Locksmith -Mode 4
}
}
$previousState = $currentState
}
catch {
Write-Warning "[$( Get-Date -Format HH:mm:ss)] Error: $_ — retrying in 15s"
Start-Sleep -Seconds 15
try {
$conn.Bind()
Write-Host "[*] Reconnected." -ForegroundColor Green
}
catch { Write-Warning "Reconnect failed: $_" }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment