Hello S-1-1-0, Crypto Guy is on a failboat board again.

Sometimes it is useful to export a certificate template to a file for future use. For example:

  • for custom certificate template backup;
  • if you have multiple AD forests, you can transfer configured certificate templates between forests;
  • share custom certificate template with partners;
  • share certificate template when you are making a online learning session;
  • etc..

Till Windows Server 2008 R2 release there was no supported way to export (or serialize) certificate template and move it out of band between two forests. With Windows Server 2008 R2 there was the only publically described way to transfer templates between two forests: AD CS: Cross-forest Certificate Enrollment with Windows Server 2008 R2. This whitepaper includes a PKISync.ps1 script (the script was written by a man who first time faced PowerShell, he-he) which copies certificate templates along other AD data between two forests. The downside of this approach is that it requires a two-way trust between forests and performs data transfer online.

In addition, Windows Server 2008 R2 introduces Enrollment Web Services. These services implement two communication protocols: MS-XCEP and MS-WSTEP.

Certificate template export

MS-XCEP is used to transfer policy information from policy (CEP) server to client. Policy information contains the following data:

  • Information about Certificate Enrollment Server (CES) endpoints and CA servers. Clients will communicate with CES by using MS-WSTEP protocol.
  • Information about available certificate templates with all settings.

Since, these protocols implement simple (comparing with low-level DCOM communications) XML over HTTPS, there is a way to reuse them manually. What you need in order to write a code that reads certificate templates from Active Directory and convert them to a XCEP-compatible XML format:

  • MS-CRTD – Certificate Template Structure
  • MS-XCEP - X.509 Certificate Enrollment Policy Protocol

MS-CRTD contains information about certificate template structure, field meanings, dependencies and other useful information. From MS-XCEP you will find information about XML structure and XML Schema. Also, a sample XML response will be very helpful. I used these documents and got the following script:

Note: there is no built-in support for certificate templates neither in .NET or PowerShell, therefore the script relies on a PKI.Core.dll which contains a set of underlying APIs for PowerShell PKI Module and exposes a set of classes to work with certificate templates in PowerShell. If you have module installed, just import it to a current PS session. Alternatively, you can download this DLL from PSPKI project home page (in the Downloads section, select PSPKI sources) and use Add-Type cmdlet to load it to current PS session.

# Export-CertificateTemplate.ps1
# Version 1.0
# Exports certificate templates to a serialized format.
# Vadims Podans (c) 2013
# http://en-us.sysadmins.lv/
#requires -Version 2.0

function Export-CertificateTemplate {
    Exports certificate templates to a serialized format.
    Exports certificate templates to a serialized format. Exported templates can be distributed
    and imported in another forest.
.Parameter Template
    A collection of certificate templates to export. A collection can be retrieved by running
    Get-CertificateTemplate that is a part of PSPKI module: https://pspki.codeplex.com
.Parameter Path
    Specifies the path to export.
    $Templates = Get-CertificateTemplate -Name SmartCardV2, WebServerV3
    PS C:\> Export-CertificateTemplate $templates c:\temp\templates.dat
        [Parameter(Mandatory = $true)]
        [Parameter(Mandatory = $true)]
    if ($Template.Count -lt 1) {throw "At least one template must be specified in the 'Template' parameter."}
    $ErrorActionPreference = "Stop"

#region enums
    $HashAlgorithmGroup = 1
    $EncryptionAlgorithmGroup = 2
    $PublicKeyIdGroup = 3
    $SigningAlgorithmIdGroup = 4
    $RDNIdGroup = 5
    $ExtensionAttributeGroup = 6
    $EKUGroup = 7
    $CertificatePolicyGroup = 8
    $EnrollmentObjectGroup = 9

#region funcs
    function Get-OIDid ($OID,$group) {
        $found = $false
        :outer for ($i = 0; $i -lt $oids.Count; $i++) {
            if ($script:oids[$i].Value -eq $OID.Value) {
                $ID = ++$i
                $found = $true
                break outer
        if (!$found) {
            $script:oids += New-Object psobject -Property @{
                Value = $OID.Value;
                Group = $group;
                Name = $OID.FriendlyName;
            $ID = $script:oids.Count
    function Get-Seconds ($str) {
        [void]("$str" -match "(\d+)\s(\w+)")
        $period = $matches[1] -as [int]
        $units = $matches[2]
        switch ($units) {
            "hours" {$period * 3600}
            "days" {$period * 3600 * 24}
            "weeks" {$period * 3600 * 168}
            "months" {$period * 3600 * 720}
            "years" {$period * 3600 * 8760}

    $SB = New-Object Text.StringBuilder
<GetPoliciesResponse xmlns="http://schemas.microsoft.com/windows/pki/2009/01/enrollmentpolicy">
        <policiesNotChanged a:nil="true" xmlns:a="http://www.w3.org/2001/XMLSchema-instance"/>
    $script:oids = @()
    foreach ($temp in $Template) {
        $OID = New-Object Security.Cryptography.Oid $temp.OID.Value, $temp.DisplayName
        $tempID = Get-OIDid $OID $EnrollmentObjectGroup
        # validity/renewal
        $validity = Get-Seconds $temp.Settings.ValidityPeriod
        $renewal = Get-Seconds $temp.Settings.RenewalPeriod
        # key usages
        $KU = if ([int]$temp.Settings.Cryptography.CNGKeyUsage -eq 0) {
            '<keyUsageProperty xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
        } else {
        # private key security
        $PKS = if ([string]::IsNullOrEmpty($temp.Settings.Cryptography.PrivateKeySecuritySDDL)) {
            '<permissions xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
        } else {
        # public key algorithm
        $KeyAlgorithm = if ($temp.Settings.Cryptography.KeyAlgorithm.Value -eq "1.2.840.113549.1.1.1") {
            '<algorithmOIDReference xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
        } else {
            $kalgID = Get-OIDid $temp.Settings.Cryptography.KeyAlgorithm $PublicKeyIdGroup
        # superseded templates
        $superseded = if ($temp.Settings.SupersededTemplates.Length -eq 0) {
            '<supersededPolicies xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'    
        } else {
            $str = "<supersededPolicies>"
            $temp.Settings.SupersededTemplates | ForEach-Object {$str += "<commonName>$_</commonName>"}
            $str + "</supersededPolicies>"
        # list of CSPs
        $CSPs = if ($temp.Settings.Cryptography.CSPList.Count -eq 0) {
            '<cryptoProviders xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
        } else {
            $str = "<cryptoProviders>`n"
            $temp.Settings.Cryptography.CSPList | ForEach-Object {
                $str += "<provider>$_</provider>`n"
            $str + "</cryptoProviders>"
        # version
        [void]($temp.Version -match "(\d+)\.(\d+)")
        $major = $matches[1]
        $minor = $matches[2]
        # hash algorithm
        $hash = if ($temp.Settings.Cryptography.HashAlgorithm.Value -eq "") {
            '<hashAlgorithmOIDReference xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
        } else {
            $hashID = Get-OIDid $temp.Settings.Cryptography.HashAlgorithm $HashAlgorithmGroup
        # enrollment agent
        $RAR = if ($temp.Settings.RegistrationAuthority.SignatureCount -eq 0) {
            '<rARequirements xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
        } else {
            $str = @"
            if ([string]::IsNullOrEmpty($temp.Settings.RegistrationAuthority.ApplicationPolicy.Value)) {
                $str += '<rAEKUs xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
            } else {
                $raapID = Get-OIDid $temp.Settings.RegistrationAuthority.ApplicationPolicy $EKUGroup
                $str += @"
            if ($temp.Settings.RegistrationAuthority.CertificatePolicies.Count -eq 0) {
                $str += '<rAPolicies xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
            } else {
                $str += "                       <rAPolicies>"
                $temp.Settings.RegistrationAuthority.CertificatePolicies | ForEach-Object {
                    $raipID = Get-OIDid $_ $CertificatePolicyGroup
                    $str += "<oIDReference>$raipID</oIDReference>`n"
                $str += "</rAPolicies>`n"
            $str += "</rARequirements>`n"
        # key archival
        $KAS = if (!$temp.Settings.KeyArchivalSettings.KeyArchival) {
            '<keyArchivalAttributes xmlns:a="http://www.w3.org/2001/XMLSchema-instance" a:nil="true"/>'
        } else {
            $kasID = Get-OIDid $temp.Settings.KeyArchivalSettings.EncryptionAlgorithm $EncryptionAlgorithmGroup
        $sFlags = [Convert]::ToUInt32($("{0:x2}" -f [int]$temp.Settings.SubjectName),16)
        foreach ($ext in $temp.Settings.Extensions) {
            $extID = Get-OIDid ($ext.Oid) $ExtensionAttributeGroup
            $critical = $ext.Critical.ToString().ToLower()
            $value = [Convert]::ToBase64String($ext.RawData)
    $n = 1
    $script:oids | ForEach-Object {
    Set-Content -Path $Path -Value $SB.ToString() -Encoding Ascii

Although, the code is large, it is quite straightforward and tests certificate template properties, composes XML and saves it to a file. I didn’t bothered myself with XML formatting for brevity and it is not relevant in our case.

Certificate template import

Ok, we exported the template. How we can restore it or import it to another AD forest? Now we need to take a look at CertEnroll COM interfaces:

IX509CertificateTemplateWritable interface has a Initialize method that accepts a pointer to a IX509CertificateTemplate interface. However IX509CertificateTemplate do not contains any methods that could be used to instantiate a certificate template and implements the only read-only property that contains certificate template. Moreover, there is no appropriate public COM class for this interface. In other words, a way from nowhere to nowhere.

After careful research of related interfaces, I noticed that IX509EnrollmentPolicyServer interface (which has appropriate COM class) which implements GetTemplates method. This method returns a pointer (or pointers) to IX509CertificateTemplate interface. Luckily, there is a InitializeImport method that accepts an array of certificate templates. Since we have only XML file and the way how CryptoAPI COM interfaces works, I tried the most logical solution: read the file to a byte array and pass this array to a method and, BINGO, I got it working! I successfully initialized IX509EnrollmentPolicyServer interface object, retrieved a pointer to a IX509CertificateTemplate interface, instantiated IX509CertificateTemplateWritable interface, ??????, PROFIT!!!111oneone, hurrah!

To avoid long descriptions, I provide a small (comparing with Export-CertificateTemplate) function that illustrates all said above:

Note: in order to import certificate templates, you must run Windows 7 or Windows Server 2008 R2 at a minimum. Previous OS do not support these interfaces (as XCEP was first implemented only in mentioned OS versions)

# Import-CertificateTemplate.ps1
# Version 1.0
# Imports and registers certificate templates in Active Directory from a file.
# Note: this function supports only Windows 7/Windows Server 2008 R2 and newer systems.
# Vadims Podans (c) 2013
# http://en-us.sysadmins.lv/
#requires -Version 2.0

function Import-CertificateTemplate {
    Imports and registers certificate templates in Active Directory from a file.
    Imports certificate templates from a file that contains serialized templates. Use
    Export-CertificateTemplate command to export and serialize certificate templates.
    If certificate template is successfully imported, it is installed to Active Directory.
    The command must be run on a Windows 7/Windows Server 2008 R2 or newer OS. Windows
    Server 2003 and Windows Server 2008 are not supported.
    Note: the command generates new object identifier (OID) for the template. Existing
    OID reuse is not supported.
.Parameter Path
    Specifies the path to a file that contains exported certificate templates.
.Parameter ServerName
    Specifies the DNS name of the Active Directory server to which the changes will be applied.
    If this value is NULL, the changes will be applied to the default domain controller.
    Import-CertificateTemplate c:\temp\templates.dat
        [Parameter(Mandatory = $true)]
    if (
        [Environment]::OSVersion.Version.Major -lt 6 -or
        [Environment]::OSVersion.Version.Major -eq 6 -and
        [Environment]::OSVersion.Version.Minor -lt 1
    ) {throw New-Object PlatformNotSupportedException}
    $bytes = [IO.File]::ReadAllBytes($Path)
    $pol = New-Object -ComObject X509Enrollment.CX509EnrollmentPolicyWebService
    $templates = $pol.GetTemplates() | ForEach-Object {$_}
    $templates | ForEach-Object {
        $adwt = New-Object -ComObject X509Enrollment.CX509CertificateTemplateADWritable

The code do not implements any error handling, so errors may occur. To avoid errors and other unpredictable results, consider the following common restrictions:

  • Certificate template MUST NOT be version 1 (default unmanaged templates);
  • Certificate template MUST NOT be default certificate that are installed by default when you install first CA server in the forest;
  • Target forest MUST NOT contains a certificate template with common and/or display name that matches with names of the template being imported;
  • you MUST NOT modify generated XML file.

These scripts are great examples that demonstrate new techniques in advanced PKI/ADCS management with PowerShell. Please, test them in a test environment and add error handling before you will use them in a production environment.


Share this article:



Hi Vadims, I love this code and all the great PKI things you do. I wonder if it would be possible to update the code such that singular dat files are created for each certificate template listed - rather than just one blob. Using your example you'd then have SmartCardV2.dat and WebServerV3.dat, etc? This way gives more flexibility - I think to update / replace / etc. specific templates which is particularly helpful when going through development phases. FYI: I've previously always used LDIFDE to export and import certificate templates: Export: ldifde -m -v -d cn=%Template1%,%LDAP% -f %Template1%.ldf Import: ldifde -i -k -f %Template1%.ldf I doubt LDIFDE is a supported method but it certainly does work - tested through to 2012 R2. It also has the neat advantage that I can edit certain properties (such as issuance policy and CSP) directly in the ldf file. However, I do recognise that PowerShell is the future and want to get everything working that way. Anyway, if you were able to update the PowerShell code to make it do separate dat files great - if not, keep up the good work anyway. Cheers, Chipeater

Vadims Podans

Few notes here: 1) LDIFDE is a working solution too, however (afaik) it export unnecessary attributes which may not be compatible when transferring certificate templates between forests. 2) you SHOULD NOT modify .ldf or .dat file directly, because there is a non-trivial logic on how different template settings are related between each other. 3) you definitely can export just single template: $Templates = Get-CertificateTemplate -Name SmartCardV2 Export-CertificateTemplate $templates c:\temp\SmartCardV2.dat you just pass a single template to "Template" parameter. or do this: Get-CertificateTemplate | foreach-object {Export-CertificateTemplate -Template $_ -Path c:\temp\($_.name).dat} in this case each template will be saved to a separate file. HTH.


Hi Vadims, I agree that LDIFDE isn't the best solution - though FYI it's the -m switch on export which allows the resulting file to be "Forest-Free". The Get-CertificateTemplate | foreach-object {Export-CertificateTemplate -Template $_ -Path c:\temp\($_.name).dat} code is exactly what I was after - many thanks. I guess this seems obvious to you... but I'm tip toeing in PowerShell. Thanks again, Dave

Vadims Podans

Ok, you win. LDIFDE looks like a another option. I'm not very familiar with this tool, so I ended up with PS version. Actually, this topic was a part of my MS-XCEP protocol learning. That is, you can use any tool you prefer. In the case if you want to dive to low-level details, this PS example would be helpful.


I am trying to run these on Windows 2012 and I am not getting any output. Also when i run just the file name it doesn't output any help message as expected. I've installed the PSPKI and imported the module, get-CertificateTemplate works just fine. Any ideas what could be wrong here?

Vadims Podans

This is because you didn't attached the function via dot-sourcing. In the console, you have to run" . path\scriptfile.ps1 and then run the function.


Hi, In windows 2012, there is some issues with your scripts. I have described it in my blog post: http://wawszczak.pr0.pl/en/2014/03/09/eksportimport-szablon%c3%b3w-certyfikat%c3%b3w-z-ad-w-windows-2012/ Regards, Stanislaw Wawszczak

Vadims Podans

yeah, it is the bug with with superseded templates. However, it is the only issue I see here. I don't understand your claims about function definition and CmdletBinding attribute. Can you elaborate?

Allen Hinkle

i am running under 2012.  new to all this power shell stuff.  But I created a copy of template.  ran the export, did indeed get an xml file that had stuff.  the import fails with:


Exception calling "InitializeImport" with "1" argument(s): "CX509EnrollmentPolicyWebService::InitializeImport: The input data was not in the expected format or did not

have the expected value. 0x803d0000 (-2143485952 WS_E_INVALID_FORMAT)"

At C:\temp\Import-CertificateTemplate.ps1:50 char:5

+ $pol.InitializeImport($bytes)

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException

+ FullyQualifiedErrorId : ComMethodTargetInvocation

Exception calling "GetTemplates" with "0" argument(s): "CX509EnrollmentPolicyWebService::GetTemplates: Uninitialized object 0x80040007 (-2147221497 OLE_E_BLANK)"

At C:\temp\Import-CertificateTemplate.ps1:51 char:5

+ $templates = $pol.GetTemplates() | ForEach-Object {$_}

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException

+ FullyQualifiedErrorId : ComMethodTargetInvocation

Exception calling "Initialize" with "1" argument(s): "Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))"

At C:\temp\Import-CertificateTemplate.ps1:54 char:9

+ $adwt.Initialize($_)

+ ~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException

+ FullyQualifiedErrorId : ComMethodTargetInvocation

Exception calling "Commit" with "2" argument(s): "CertEnroll::CX509CertificateTemplateADWritable::Commit: Uninitialized object 0x80040007 (-2147221497 OLE_E_BLANK)"

At C:\temp\Import-CertificateTemplate.ps1:55 char:9

+ $adwt.Commit(1,$ServerName)

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException

+ FullyQualifiedErrorId : ComMethodTargetInvocation


Vadims, I prefer using this over the LDIFDE method when importing to different forests because it's cleaner by not re-using the Template OID or having to edit the LDF file with forest specific RootDSE.

I did notice the Private Key SDDL was not exporting...looks like the ELSE statement in $PKS= section is missing "Cryptography". If i am not mistaken, "$temp.Settings.PrivateKeySecuritySDDL" should read  "$temp.Settings.Cryptography.PrivateKeySecuritySDDL". Please verify my thinking is valid. Thanks for all your work here. Regards, Tim.


Tried running this code to export cert templates but nothing happens. Have installed the Public Key Infrastructure PowerShell module but do not see those even loading when i execute your code. Execution poiicy is RemoteSigned.

Only modules I see on my Cert server are:


    Directory: C:\Windows\system32\WindowsPowerShell\v1.0\Modules

ModuleType Name                                ExportedCommands
---------- ----                                ----------------
Manifest   ADRMS                               {Update-ADRMS, Uninstall-ADRMS, Install-ADRMS}
Manifest   AppLocker                           {Set-AppLockerPolicy, Get-AppLockerPolicy, Test-AppLockerPolicy, Get-...
Manifest   BestPractices                       {Get-BpaModel, Invoke-BpaModel, Get-BpaResult, Set-BpaResult}
Manifest   BitsTransfer                        {Add-BitsFile, Remove-BitsTransfer, Complete-BitsTransfer, Get-BitsTr...
Manifest   CimCmdlets                          {Get-CimAssociatedInstance, Get-CimClass, Get-CimInstance, Get-CimSes...
Script     ISE                                 {New-IseSnippet, Import-IseSnippet, Get-IseSnippet}
Manifest   Microsoft.PowerShell.Diagnostics    {Get-WinEvent, Get-Counter, Import-Counter, Export-Counter...}
Manifest   Microsoft.PowerShell.Host           {Start-Transcript, Stop-Transcript}
Manifest   Microsoft.PowerShell.Management     {Add-Content, Clear-Content, Clear-ItemProperty, Join-Path...}
Manifest   Microsoft.PowerShell.Security       {Get-Acl, Set-Acl, Get-PfxCertificate, Get-Credential...}
Manifest   Microsoft.PowerShell.Utility        {Format-List, Format-Custom, Format-Table, Format-Wide...}
Manifest   Microsoft.WSMan.Management          {Disable-WSManCredSSP, Enable-WSManCredSSP, Get-WSManCredSSP, Set-WSM...
Script     PSDiagnostics                       {Disable-PSTrace, Disable-PSWSManCombinedTrace, Disable-WSManTrace, E...
Binary     PSScheduledJob                      {New-JobTrigger, Add-JobTrigger, Remove-JobTrigger, Get-JobTrigger...}
Manifest   PSWorkflow                          {New-PSWorkflowExecutionOption, New-PSWorkflowSession, nwsn}
Manifest   PSWorkflowUtility                   Invoke-AsWorkflow
Manifest   ServerManager                       {Get-WindowsFeature, Add-WindowsFeature, Remove-WindowsFeature}
Manifest   TroubleshootingPack                 {Get-TroubleshootingPack, Invoke-TroubleshootingPack}
Manifest   WebAdministration                   {Start-WebCommitDelay, Stop-WebCommitDelay, Get-WebConfigurationLock,...


Please advise how to get this code to export our certificate templates

Vadims Podāns

Hi, these commands are not part of the PSPKI module, they are available as a standalone scripts. You can copy/paste the code from this article.


just comment the line
function Export-CertificateTemplate {
 and the last curly brace

then the script works as expected.




Hi Vadims,

Quick question, in a non XP/2003 environment, do you still need to copy the template over and/or execute PKISync ? I thought this was only a requirement when you had XP or 2003 based clients.



Vadims Podāns

Template copy between forests (in the case of cross-forest enrollment) is still necessary if you are not using certificate enrollment web services which are supported starting with Windows 7.


Hi Vadims,

not quite sure what's wrong in my lab - I've dot sourced the function, and can see it under dir function:\. No problem getting it to start. When checking the available template with Get-CertificateTemplate, I see a list of all available templates, 'FredLab OCSP Signing Response' amongst others. This is a v4 copy of the OCSP Signing Response Template. When selecting any copied Template (all copied Templates are v4), I receive the error, that 'a Template with that name' could not be found - even though I've copy/pasted it from the output of Get-CertificateTemplate. Any idea what might be going on there?

Thanks & Best Regards,


Ben Coremans

Hi Vadims,

In a Windows 2016 environment the import function throws an error "The input data was not in the expected format..."
After investigation it was the export function which causes the problem. The function checks for a number of attributes in SupersededTemplates.

$superseded = if ($temp.Settings.SupersededTemplates -eq 0) {

Change the line as follow:
$superseded = if ($temp.Settings.SupersededTemplates.Length -eq 0) {

After changing this line, the scripts work as aspected.


Vadims Podāns

Thanks Ben, I fixed the code.


Hi Vadims,

First of all thanks for excelent code.

I just got a question regarding importing certificate templates into AD from xml file produced by Export-CertificateTemplate.

Importing works fine, but my problem is that native ADCS cmdlet Add-CATemplate does not see imported template immediatelly.

Restarting ADCS services  does not help as well. Adding template in Certification Authority console works, and after I use console Add-CATemplate starts to see imported template as well. But I'm developing fully scripted solution to install ADCS and configure templates and GUI approach does not work for me.

Is there any way to force ADCS to read templates from AD through some COM object interface?



Is there any way to for

Vadims Podāns

> Importing works fine, but my problem is that native ADCS cmdlet Add-CATemplate does not see imported template immediatelly.

This might be associated with internal cache of built-in Add-CATemplate.

Darren Johnson

> Importing works fine, but my problem is that native ADCS cmdlet Add-CATemplate does not see imported template immediatelly.

Hey I had the same issue, using certutil -SetCAtemplates +templateName didn't work either.  I came across the git repo below and did it using Set-ADObject in the end and it worked without a delay.


I hope this helps somebody :)



Vadims Podāns

> https://github.com/GoateePFE/ADCSTemplate/blob/master/ADCSTemplate.psm1

I did a review of that module previously and it has serious design issues and may make your configuration unsupported by Microsoft. I would not recommend to use it until issues are solved.

Jon Pennycook


I tried to export the certificate templates from one forest and import them in another.  The OIDs of the certificate templates were different between the two forests - is this expected?


Vadims Podāns

> The OIDs of the certificate templates were different between the two forests - is this expected?

I would say that yes. IX509CertificateTemplateADWritable COM interface re-generates template OIDs.

Jon Pennycook

Never mind - I see "Existing OID reuse is not supported" in the Description of the Import- script.


Hi Vadims - Thanks for this post. I have a question on cross forest enrollment.

We have two way trust between the resource (has the root and issuing CA) and account forest. I am trying to copy one template from resource to account forest running pkisync.ps1 but get write error. I can see template created in account forest but its incomplete. The account used has full permissions on Certificate Template and OID containers in AD. I also tried creating the template manually directly on account forest with same name and properties but the server in account forest cant see the template in MMC for enrollment. It is unavailable and error is "The requested certificate template is not supported by this CA. A valid certification...."

The template in resource forest is added to the issuing CA and being used for enrollment by servers in resource forst. When creating manually the oid is different to one in resource forest. Does the oid need to be same between resource and account forest in this case?

Post your comment:

Please, solve this little equation and enter result below. Captcha