Private/New-Template.ps1

Function New-Template {
    <#
        .SYNOPSIS
            Creates a new PKI template in Active Directory Certificate Services.
 
        .DESCRIPTION
            This function creates a new certificate template in Active Directory Certificate Services (AD CS).
            It performs the following actions:
            1. Generates a unique template OID
            2. Creates the OID object in AD
            3. Creates the certificate template with specified attributes
            All operations are performed against a writable Domain Controller.
 
        .PARAMETER DisplayName
            Display Name of the new template.
            Must be unique in the forest.
            Cannot contain special characters.
 
        .PARAMETER TemplateOtherAttributes
            Hashtable containing additional template attributes.
            Common attributes include:
            - msPKI-Certificate-Name-Flag
            - msPKI-Enrollment-Flag
            - msPKI-Private-Key-Flag
            - msPKI-RA-Signature
            - pKIExtendedKeyUsage
            - pKIKeyUsage
            - pKIMaxIssuingDepth
            - revision
 
        .INPUTS
            System.String
            System.Collections.Hashtable
            You can pipe a DisplayName and attributes to this function.
 
        .OUTPUTS
            [System.Void]
            This function does not produce any output.
 
        .EXAMPLE
            $attributes = @{
                'msPKI-Certificate-Name-Flag' = 1
                'msPKI-Enrollment-Flag' = 0
                'pKIKeyUsage' = [byte[]](0x80)
                'pKIExtendedKeyUsage' = '1.3.6.1.5.5.7.3.2'
            }
            New-Template -DisplayName "WebServer2025" -TemplateOtherAttributes $attributes
 
            Creates a new web server certificate template.
 
        .EXAMPLE
            $splat = @{
                DisplayName = "UserSign2025"
                TemplateOtherAttributes = @{
                    'KeyType' = 'ExchangeSignature'
                    'KeyUsage' = 'DigitalSignature'
                }
            }
            New-Template @splat -Verbose -WhatIf
 
            Shows what would happen when creating a new user signing template.
 
        .NOTES
            Used Functions:
                Name ║ Module/Namespace
                ═══════════════════════════════╬══════════════════════════
                Get-ADDomainController ║ ActiveDirectory
                New-ADObject ║ ActiveDirectory
                New-TemplateOID ║ EguibarIT
                Write-Verbose ║ Microsoft.PowerShell.Utility
                Write-Debug ║ Microsoft.PowerShell.Utility
                Write-Error ║ Microsoft.PowerShell.Utility
                Get-FunctionDisplay ║ EguibarIT
                Import-MyModule ║ EguibarIT
 
        .NOTES
            Version: 1.6
            DateModified: 22/May/2025
            LastModifiedBy: Vicente Rodriguez Eguibar
                            vicente@eguibar.com
                            Eguibar IT
                            http://www.eguibarit.com
 
        .LINK
            https://github.com/vreguibar/EguibarIT/blob/main/Private/New-Template.ps1
 
        .COMPONENT
            PKI
 
        .ROLE
            Certificate Management
 
        .FUNCTIONALITY
            Certificate Template Creation
    #>

    [CmdletBinding(
        SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium'
    )]
    [OutputType([void])]

    Param(
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ValueFromRemainingArguments = $False,
            HelpMessage = 'Display Name of the new template.',
            Position = 0)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern('^[a-zA-Z0-9\s-_]+$')]
        [Alias('Name', 'Template')]
        [System.String]
        $DisplayName,

        [Parameter(Mandatory = $True,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ValueFromRemainingArguments = $False,
            HelpMessage = 'Other attributes in form of HashTable of the new template.',
            Position = 1)]
        [ValidateNotNull()]
        [Alias('Attributes', 'Properties')]
        [System.Collections.Hashtable]
        $TemplateOtherAttributes
    )

    Begin {
        Set-StrictMode -Version Latest

        # Output header information
        if ($null -ne $Variables -and
            $null -ne $Variables.Header) {

            $txt = ($Variables.Header -f
                (Get-Date).ToString('dd/MMM/yyyy'),
                $MyInvocation.Mycommand,
                (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False)
            )
            Write-Verbose -Message $txt
        } #end if

        ##############################
        # Module imports

        Import-MyModule -ModuleName 'ActiveDirectory' -Verbose:$false


        ##############################
        # Variables Definition

        [string]$WhatIfMessage = 'Creating PKI template: {0}' -f $DisplayName
        [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)

        try {
            # Get writable DC
            $GetDCParams = @{
                Discover      = $true
                ForceDiscover = $true
                Writable      = $true
                ErrorAction   = 'Stop'
            }
            $Server = (Get-ADDomainController @GetDCParams).HostName[0]
            Write-Debug -Message ('Using Domain Controller: {0}' -f $Server)

            # Get Configuration NC
            $ConfigNC = $Variables.configurationNamingContext
            Write-Debug -Message ('Using Configuration NC: {0}' -f $ConfigNC)

        } catch {

            Write-Error -Message ('Failed to initialize: {0}' -f $_.Exception.Message)
            throw

        } #end Try-Catch

    } # End BEGIN section

    Process {
        Try {

            Write-Debug -Message ('Creating template: {0}' -f $DisplayName)

            #Create OID
            $OID = New-TemplateOID -Server $Server -ConfigNC $ConfigNC
            Write-Debug -Message ('Generated OID: {0}' -f $OID.TemplateOID)

            if ($PSCmdlet.ShouldProcess($TemplateOIDPath, 'Create template OID')) {

                $TemplateOIDPath = 'CN=OID,CN=Public Key Services,CN=Services,{0}' -f $ConfigNC
                $OIDOtherAttributes = @{
                    'DisplayName'             = $DisplayName
                    'flags'                   = [System.Int32]'1'
                    'msPKI-Cert-Template-OID' = $OID.TemplateOID
                }
                $Splat = @{
                    Path            = $TemplateOIDPath
                    OtherAttributes = $OIDOtherAttributes
                    Name            = $OID.TemplateName
                    Type            = 'msPKI-Enterprise-Oid'
                    Server          = $Server
                    ErrorAction     = 'Stop'
                }
                New-ADObject @Splat

                Write-Verbose -Message ('Created OID object: {0}' -f $OID.TemplateName)

            } #end If

            # Ensure if msPKI-Cert-Template-OID already add it to hashtable
            If (-not $TemplateOtherAttributes.ContainsKey('msPKI-Cert-Template-OID')) {

                #Create Template itself
                $TemplateOtherAttributes += @{
                    'msPKI-Cert-Template-OID' = $OID.TemplateOID
                }
            } #end If
            $TemplatePath = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,{0}' -f $ConfigNC


            if ($PSCmdlet.ShouldProcess($TemplatePath, $WhatIfMessage)) {
                $Splat = @{
                    Path            = $TemplatePath
                    OtherAttributes = $TemplateOtherAttributes
                    Name            = $DisplayName
                    DisplayName     = $DisplayName
                    Type            = 'pKICertificateTemplate'
                    Server          = $Server
                    ErrorAction     = 'Stop'
                }
                New-ADObject @Splat

                Write-Verbose -Message ('Created template: {0}' -f $DisplayName)
            } #end If

        } catch {

            Write-Error -Message ('Failed to create template {0}: {1}' -f $DisplayName, $_.Exception.Message)
            throw

        } #end Try-Catch

    } # End PROCESS section

    End {
        if ($null -ne $Variables -and
            $null -ne $Variables.Footer) {

            $txt = ($Variables.Footer -f $MyInvocation.InvocationName,
                'adding new PKI template (Private Function).'
            )
            Write-Verbose -Message $txt
        } #end if

    } #end End section

} # End Function New-Template