I am trying to load details about the Microsoft PKI Issuing CAs from my AD Forest by iterating through the Enrollment Services AD container. One of the details that I'd like to include is the list of certificate templates enabled on each CA. In ADSIEdit, I can clearly see that there is a property named certificateTemplates in each Enrollment Services object which contains the information I need (please forgive all the redaction):
However, when I pull any of these objects into a .NET DirectoryEntry object, "certificateTemplates" is not one of the properties that is included by default. I discovered the RefreshCache method, which should allow you to pull in additional properties from a directory entry (so long as they exist on the entry), but this did not work for me:
DirectoryEntry EnrollmentServices = new DirectoryEntry($"LDAP://{MyDC}/CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=com");
DirectoryEntries CAs = EnrollmentServices.Children;
foreach(DirectoryEntry CA in CAs)
{
object[] objectClass = (object[])CA.Properties[objectClassProperty].Value;
if (objectClass[1].ToString() == @"pKIEnrollmentService")
{
CA.RefreshCache(new string[] { "certificateTemplates" });
var Templates = CA.Properties["certificateTemplates"].Value;
//Templates remains null
}
}
Has anyone encountered/resolved this issue before?
The RefreshCache() method does, in fact, correctly add the certificateTemplates property to each CA DirectoryEntry object. The first CA encountered in my loop simply has no templates assigned, which explains the null value for Templates in the first iteration of my foreach loop.
Update: If the certificateTemplates property contains only one entry, the property value will be a single string rather than an array containing only 1 element (thanks, Microsoft!). I use a try/catch to handle this edge case. If anyone has a more elegant approach, be my guest.
DirectoryEntry EnrollmentServices = new DirectoryEntry($"LDAP://{MyDC}/CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=com");
DirectoryEntries CAs = EnrollmentServices.Children;
List<string> templateNames;
foreach(DirectoryEntry CA in CAs)
{
object[] objectClass = (object[])CA.Properties[objectClassProperty].Value;
if (objectClass[1].ToString() == @"pKIEnrollmentService")
{
CA.RefreshCache(new string[] { "certificateTemplates" });
object[] Templates;
try
{
Templates = (object[])CAEntry.Properties[PropertyIndex.CertificateTemplates].Value;
}
catch (InvalidCastException)
{
Templates = new object[] { CAEntry.Properties[PropertyIndex.CertificateTemplates].Value };
}
//I place the template names into a string list for ease of retrieval later.
templateNames = new List<string>();
if (Templates != null)
{
for (int x = 0; x < Templates.Length; x++) templateNames.Add(Templates[x].ToString());
}
}
}