PKI: which templates are built-in and which are from my company?

A colleague asked me a question on behalf of his customer. They were doing a discovery in a rather messy PKI environment and the question arose: which templates are standard (default), and which ones were created manually? Hopefully they have a good naming convention to make this immediately obvious, but otherwise a deeper look is needed.

After some thinking I decided that there is not a 100% solution to discover which templates are manual. There can be lots of clues like a name or creation timestamp. However, I decided to look for something else. Every CA template has a unique identifier called an OID. It turns out that the auto-generated OIDs generated by manual creation differ significantly from the built-in ones:

OID built-in template: .1.3
OID manual template:

The first piece ( just says that the OID is owned by Microsoft. The next part (21.8) means that this is a CA template. For more information, see kb287547. The following parts up to .87 seem to be constants, but the final two fields are not. If the next to last field is 1, the template corresponding to the OID seems to be built-in. This trick can go wrong at customers that use their own OID range for templates. This should be extremely rare, though.

Armed with this knowledge the script looks like this:

$pkiContainerDN = "CN=Public Key Services,CN=Services,$((Get-ADRootDSE).configurationNamingContext)"
Get-ADObject -filter { objectclass -eq "pkiCertificateTemplate" } -SearchBase $pkiContainerDN -Properties * |
Sort-Object displayname -PipelineVariable template |
ForEach-Object {
$isBuiltin = ($template."mspki-cert-template-oid" -split '\.')[-2] -lt 10
[PSCustomObject] @{
displayName = $template.displayname
name = $
schemaVersion = $template."mspki-template-schema-version"
majorVersion = $template.revision
minorVersion = $template."mspki-template-minor-revision"
isBuiltin = $isBuiltin
whenCreated = $template.whenCreated
} | Out-GridView

The scripts reads all templates from the Configuration Container and display some properties. The only intelligent bit is where we split the OID on the dot, take the next to last field and if this is a small number, we assume that the template is built in.

Sample output: