Last active
March 16, 2026 22:10
-
-
Save b401/46a5492c1b3de24bd69774966060db2c to your computer and use it in GitHub Desktop.
poll USN for certificate changes
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
| 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