Functions/Get-IssuedCertificate.ps1

<#
    .SYNOPSIS
    Allows for Submission of a Certificate Request to a Certification Authority.
    Allows for retrieval of a previously issued Certificate from a Certification Authority.
 
    .PARAMETER CertificateRequest
    The BASE64 encoded Certificate Request to be submitted to the Certification Authority.
 
    .PARAMETER RequestId
    The Request Identifier that was given to a previously submitted Certificate Request.
 
    .PARAMETER ConfigString
    The Comnfiguration String for the Certificate Authority to connect to,
    in the Form of "<Hostname>\<Common-Name-of-CA>" for a RPC/DCOM Enrollment and
    in for Form of "https://<Hostname>/<Common-Name-of-CA>_CES_<Authentication-Type/service.svc/CES"
    for a WSTEP (Certificate Enrollment Web Service) Enrollment.
 
    .PARAMETER CertificateTemplate
    Optional: The name of the Certificate Template to request a Certificate from.
    Must be used if the Certificate does not contain this information.
 
    .PARAMETER Credential
    Credentials when performing a WSTEP Enrollment with Username/Password Authentication.
 
    .PARAMETER ClientCertificate
    Thumbprint of an authentication Certificate when performing a WSTEP Enrollment with Client Certificate Authentication.
 
    .OUTPUTS
    An object representing the Enrollment/Retrieval result.
#>


Function Get-IssuedCertificate {

    [CmdletBinding()]
    param (

        [Parameter(
            ParameterSetName="Submit",
            Mandatory=$True,
            ValuefromPipeline=$True
            )]
        [ValidateNotNullOrEmpty()]
        [String]
        $CertificateRequest,

        [Parameter(
            ParameterSetName="Retrieve",
            Mandatory=$True
            )]
        [ValidateRange(1,4294967295)] # According to ADCS Database Schema, maximum should be 32 Bit
        [System.Uint32]
        $RequestId,

        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()]
        [String]
        $ConfigString,
    
        [Parameter(
            ParameterSetName="Submit",
            Mandatory=$False
            )]
        [ValidateNotNullOrEmpty()]
        # [ValidatePattern("^[0-9a-fA-F]$")] Clarify which characters are allowed
        [String]
        $CertificateTemplate,
        
        [Parameter(Mandatory=$False)]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter(Mandatory=$False)]
        [ValidatePattern("^[0-9a-fA-F]{40}$")]
        [String]
        $ClientCertificate
    )
    
    begin {

        $CertRequest = New-Object -ComObject CertificateAuthority.Request

        # https://docs.microsoft.com/en-us/windows/win32/api/certcli/nf-certcli-icertrequest3-setcredential
        # WSTEP with Username and Password Authentication
        If ($ConfigString.StartsWith("https://") -and 
            $ConfigString.EndsWith("UsernamePassword/service.svc/CES", [System.StringComparison]::OrdinalIgnoreCase)
        ) {

            If ($Credential) {

                $CertRequest.SetCredential(
                    [Int]$null, # no Window Handle
                    $X509EnrollmentAuthFlags.X509AuthUsername,
                    $Credential.UserName,
                    [Runtime.InteropServices.Marshal]::PtrToStringAuto(
                        [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.Password)
                    )
                )

            }
            Else {
                Write-Error -Message "You must provide Authentication Credentials."
                return
            }
        }

        # WSTEP with Client Certificate Authentication
        If ($ConfigString.StartsWith("https://") -and 
            $ConfigString.EndsWith("Certificate/service.svc/CES", [System.StringComparison]::OrdinalIgnoreCase)
        ) {

            If ($ClientCertificate) {

                $CertRequest.SetCredential(
                    [Int]$null, # no Window Handle
                    $X509EnrollmentAuthFlags.X509AuthCertificate,
                    $ClientCertificate,
                    [String]::Empty
                )

            }
            Else {
                Write-Error -Message "You must provide a Client Authentication Certificate Thumbprint."
                return
            }
        }

        # WSTEP with Kerberos Authentication
        If ($ConfigString.StartsWith("https://") -and 
            $ConfigString.EndsWith("Kerberos/service.svc/CES", [System.StringComparison]::OrdinalIgnoreCase)
        ) {
 
            $CertRequest.SetCredential(
                [Int]$null, # no Window Handle
                $X509EnrollmentAuthFlags.X509AuthKerberos,
                [String]::Empty,
                [String]::Empty
            )

        }
    }
    
    process {

        # Submit a Certificate Request
        If ($CertificateRequest) {

            If ($CertificateTemplate) {
                $Attributes = "CertificateTemplate:$($CertificateTemplate)"
            }
            Else {
                $Attributes = [String]::Empty
            }

            Try {
                # https://docs.microsoft.com/en-us/windows/win32/api/certcli/nf-certcli-icertrequest-submit
                $Status = $CertRequest.Submit(
                    $RequestFlags.CR_IN_ENCODEANY,
                    $CertificateRequest,
                    $Attributes,
                    $ConfigString
                )
            }
            Catch {
                Write-Error -Message $PSItem.Exception.Message
                return
            }
        }

        # Retrieve a pending Certificate Request
        If ($RequestId) {

            Try {
                # https://docs.microsoft.com/en-us/windows/win32/api/certcli/nf-certcli-icertrequest-retrievepending
                $Status = $CertRequest.RetrievePending(
                    $RequestId,
                    $ConfigString
                )
            }
            Catch {
                Write-Error -Message $PSItem.Exception.Message
                return
            }
        }

        $StatusCode = "0x" + ('{0:x}' -f $CertRequest.GetLastStatus())
        $StatusMessage = (New-Object System.ComponentModel.Win32Exception($CertRequest.GetLastStatus())).Message

        # Process the Submission Result and return it
        Switch ($Status) {

            $DispositionType.CR_DISP_INCOMPLETE {

                [PSCustomObject]@{
                    RequestId = $CertRequest.GetRequestId()
                    Disposition = $Status
                    Result = "Request is incomplete"
                    StatusCode = $StatusCode
                    StatusMessage = $StatusMessage
                    Certificate = $null
                    RawCertificate = $null
                }
            }
           
            $DispositionType.CR_DISP_ERROR {

                [PSCustomObject]@{
                    RequestId = $CertRequest.GetRequestId()
                    Disposition = $Status
                    Result = "There was an error during submission"
                    StatusCode = $StatusCode
                    StatusMessage = $StatusMessage
                    Certificate = $null
                    RawCertificate = $null
                }
            }

            $DispositionType.CR_DISP_DENIED {

                [PSCustomObject]@{
                    RequestId = $CertRequest.GetRequestId()
                    Disposition = $Status
                    Result = "Request was denied"
                    StatusCode = $StatusCode
                    StatusMessage = $StatusMessage
                    Certificate = $null
                    RawCertificate = $null
                }
            }

            $DispositionType.CR_DISP_ISSUED {

                # https://docs.microsoft.com/en-us/windows/win32/api/certcli/nf-certcli-icertrequest-getcertificate
                # https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.import
                $CertificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
                $CertificateObject.Import(
                    [Convert]::FromBase64String(
                        $CertRequest.GetCertificate($RequestFlags.CR_OUT_BASE64)
                    )
                )

                [PSCustomObject]@{
                    RequestId = $CertRequest.GetRequestId()
                    Disposition = $Status
                    Result = "Certificate was issued"
                    StatusCode = $StatusCode
                    StatusMessage = $StatusMessage
                    Certificate = $CertificateObject
                    RawCertificate = $CertRequest.GetCertificate($RequestFlags.CR_OUT_BASE64HEADER)
                }
            }

            $DispositionType.CR_DISP_ISSUED_OUT_OF_BAND {

                [PSCustomObject]@{
                    RequestId = $CertRequest.GetRequestId()
                    Disposition = $Status
                    Result = "Certificate was issued out of band"
                    StatusCode = $StatusCode
                    StatusMessage = $StatusMessage
                    Certificate = $null
                    RawCertificate = $null
                }
            }

            $DispositionType.CR_DISP_UNDER_SUBMISSION {
                
                [PSCustomObject]@{
                    RequestId = $CertRequest.GetRequestId()
                    Disposition = $Status
                    Result = "Request was taken under submission"
                    StatusCode = $StatusCode
                    StatusMessage = $StatusMessage
                    Certificate = $null
                    RawCertificate = $null
                }
            }

            $DispositionType.CR_DISP_REVOKED {

                [PSCustomObject]@{
                    RequestId = $CertRequest.GetRequestId()
                    Disposition = $Status
                    Result = "Certificate has been revoked"
                    StatusCode = $StatusCode
                    StatusMessage = $StatusMessage
                    Certificate = $null
                    RawCertificate = $null
                }
            }

            # This should never happen, but just to be on the safe side
            default{
                Write-Error -Message "Retrieved unsupported Disposition Code $Status from the Certification Authority."
            }

        }

    }
    
    end {
        [void]([System.Runtime.Interopservices.Marshal]::ReleaseComObject($CertRequest))
    }
}