Bulk request and export client certificates with PowerShell

I did an implementation of Active Directory Certificate Services  for a customer recently, and they had a requirement to use the new environment to request a load of user client certificates for mobility testing.

This part of the project wasn’t integrated with any MDM solution – which is the normal path to get internally-generated certificates onto a managed mobile device – so the internal team needed a way to quickly request and export a load of certificates (at least 500) which they could work with manually.

So I came up with this PowerShell script to handle the whole thing programatically.  The script is designed to take a CSV input, which in this case is defined as a dump of sAMAccountName properties of the user accounts for which you want to generate the certificates.  A folder is created for each sAMAccountName, the certificate request is generated and processed, and then the certificate is exported to a PFX file (so that it contains the private key) and everything apart from the exported PFX file is deleted at the end.

The solution is designed to use a Certificate Template, which means you need an Enterprise CA.  The template needs to be configured so that the Subject Name is supplied in the certificate request, and the private key is exportable.  Obviously, the user account running the script has to have Enroll permissions on the template.

## Bulk Certificate Creation & Export Script
## Created by James Bannan on 31/03/2015
## Original New-CertificateRequestion function created by Matt Terblanche
## //blog.kloud.com.au/2013/07/30/ssl-san-certificate-request-and-import-from-powershell/
 
Function New-CertificateRequest {
    param (
        [Parameter(Mandatory=$true)][string]$sAMAccountName,
        [Parameter(Mandatory=$true)][string]$UserPrincipalName,
        [Parameter(Mandatory=$true)][string]$IssuingCA,
        [Parameter(Mandatory=$true)][string]$CATemplate,
        [Parameter(Mandatory=$false)][string]$ExportPath
    )
 
    ### Define Variables
    $CertificateINI = "$sAMAccountName.ini"
    $CertificateREQ = "$sAMAccountName.req"
    $CertificateRSP = "$sAMAccountName.rsp"
    $CertificateCER = "$sAMAccountName.cer"
 
    ### Define Export Location
    if ((Test-Path $ExportLocation) -eq $false){New-Item -Path $ExportLocation -ItemType Directory -Force}
 
    ### INI file generation
    Set-Location $ExportLocation
    New-Item -type file $CertificateINI -force
    Add-Content $CertificateINI '[Version]'
    Add-Content $CertificateINI 'Signature="$Windows NT$"'
    Add-Content $CertificateINI ''
    Add-Content $CertificateINI '[NewRequest]'
    $temp = 'Subject="' + $SubjectName + '"'
    Add-Content $CertificateINI $temp
    Add-Content $CertificateINI 'FriendlyName="Custom User Certificate"'
    Add-Content $CertificateINI 'Exportable=TRUE'
    Add-Content $CertificateINI 'KeyLength=2048'
    Add-Content $CertificateINI 'KeySpec=1'
    Add-Content $CertificateINI 'KeyUsage=0xA0'
    Add-Content $CertificateINI 'MachineKeySet=True'
    Add-Content $CertificateINI 'ProviderName="Microsoft RSA SChannel Cryptographic Provider"'
    Add-Content $CertificateINI 'ProviderType=12'
    Add-Content $CertificateINI 'SMIME=FALSE'
    Add-Content $CertificateINI 'RequestType=PKCS10'
    Add-Content $CertificateINI '[Strings]'
    Add-Content $CertificateINI 'szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"'
    Add-Content $CertificateINI 'szOID_SUBJECT_ALT_NAME2 = "2.5.29.17"'
    Add-Content $CertificateINI '[Extensions]'
    Add-Content $CertificateINI '2.5.29.17 = "{text}"'
    $temp = '_continue_ = "UPN=' + $UserPrincipalName + '&"'
    Add-Content $CertificateINI $temp
 
 
    ### Certificate request generation
    if (Test-Path $CertificateREQ) {Remove-Item $CertificateREQ}
    certreq.exe -new $CertificateINI $CertificateREQ
 
    ### Online certificate request and import
    if ($IssuingCA){
        if (Test-Path $CertificateCER) {Remove-Item $CertificateCER}
        if (Test-Path $CertificateRSP) {Remove-Item $CertificateRSP}
        certreq.exe -submit -attrib "CertificateTemplate:$CATemplate" -config $IssuingCA $CertificateREQ $CertificateCER
 
        certreq.exe -accept $CertificateCER
    }
}
Function Export-CertificateRequest {
    param (
        [Parameter(Mandatory=$true)][string]$sAMAccountName,
        [Parameter(Mandatory=$true)][string]$ExportLocation
    )
 
    $Certificate = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -eq $SubjectName}
    $CertificateExport = $ExportLocation + '\' + $sAMAccountName + '.pfx'
    Export-PfxCertificate -Cert $Certificate.PSPath -FilePath $CertificateExport -Password $Password
}
Function Clean-CertificateRequest {
 
    $Certificate = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -eq $SubjectName}
 
    if (Test-Path $CertificateREQ) {Remove-Item $CertificateREQ}
    if (Test-Path $CertificateCER) {Remove-Item $CertificateCER}
    if (Test-Path $CertificateRSP) {Remove-Item $CertificateRSP}
    if (Test-Path $CertificateINI) {Remove-Item $CertificateINI}
 
    Remove-Item $certificate.PSPath
 
}
 
$Password = Read-Host 'Enter Password' -AsSecureString
$users = Import-Csv -Path C:\Temp\users.csv
$DomainSuffix = 'sccmlab.net'
 
foreach ($user in $users){
 
    $sAMAccountName = $user.sAMAccountName
    $SubjectName = 'CN=' + $sAMAccountName
    $UserPrincipalName = $sAMAccountName + '@' + $DomainSuffix
    $ExportPath = 'C:\CertExport'
    $ExportLocation = $ExportPath + '\' + $sAMAccountName
 
    New-CertificateRequest `
        -sAMAccountName $sAMAccountName `
        -UserPrincipalName $UserPrincipalName `
        -IssuingCA 'DC01.sccmlab.net\sccmlab-DC01-CA' `
        -CATemplate 'CustomUserCertificate' 
 
    Export-CertificateRequest `
        -sAMAccountName $sAMAccountName `
        -ExportLocation $ExportLocation
 
    Clean-CertificateRequest
}

12 comments to Bulk request and export client certificates with PowerShell

  • Baard

    Shouldn’t “[Parameter(Mandatory=$false)][string]$ExportPath” be “[Parameter(Mandatory=$false)][string]$ExportLocation”?

    • James

      No – $ExportPath is the top-level folder (e.g. C:\CertExport) while $ExportLocation is the sub-level folder (e.g. C:\CertExport\username). $ExportLocation is a dynamically-built variable which get constantly modified in the foreach() section.

  • Baard

    Ah, now i see…
    Too tired to get that there were three functions defined *facepalm*

  • Stefan

    James, thank you for the script.
    Trying to run it on a Windows 2012 R2 box but encountered this error:

    Export-PfxCertificate : Cannot bind argument to parameter ‘Cert’ because it is null.
    At C:\CertExport\certExport.ps1:75 char:33
    + Export-PfxCertificate -Cert $Certificate.PSPath -FilePath $CertificateExport …
    + ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Export-PfxCertificate], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.CertificateServices.Commands.ExportPfxCertificate

    cer/ini/req/rsp are all created.

    Any hint?
    Thank you.

  • Vincent

    Hi,

    This is a great automation tool, a huge thank you !

    but I have one question : How do you manage the working cert repository ?

    In your script to export the private key (pfx) you request the repository : Cert:\LocalMachine\My .

    On my AD, this repository doesnt contains anything (the single command ‘Get-ChildItem -Path Cert:\LocalMachine\My’ doesn’t list anything related to the generated key.

  • Vincent

    Finally, this script doesn’t work on a 2012R2 server :

    This command always generate a .req for the admin account running the script :
    certreq.exe -new $CertificateINI $CertificateREQ

    So I obtain a .cer file for the admin account….

  • Kevin

    Hi Well done! Do the users need to be logged in when this is ran? and what if their machines are not domain machines? would it work?
    I also want to make sure that it request from the user personal store not the local machine. Does this mean I have to change the “Cert:\LocalMachine” to ” Current user”?

  • Mark

    Hi James.

    Thank you for sharing this script, it is really appreciated. I would like to share a couple of tweaks I had to do that may help others.

    On the export section I was getting nothing returned with:-
    Where-Object {$_.Subject -eq $SubjectName}

    Changing the logic operator to –match worked:-
    Where-Object {$_.Subject -match $SubjectName}

    My certificate server is Windows Server 2008 R2, so doesn’t have the Powershell module PKI available. So the command “Export-PfxCertificate” isn’t available. Instead used Certutil.exe to export the certificates:-

    certutil -exportpfx -p “Welcome123” MY $MYCERTNAME $path\clientcerts\$MYCERTNAME.pfx

    Cheers

    Mark

  • Sudheep

    Hi James,

    I was trying this script to generate certificate for bulk user list. But what I noticed the PFX file is generated only for the logged in user (if the logged in user id included in the csv file). Due to this all id’s .cer file generating to the same CN samaccountname (as in Issues To filed in certificate file), and updating it to My respository with logged in user’s CN name only for all samaccountname. Hence for all remaining id, the PFX exporting is not failing due to the Where-Object {$_.Subject -eq $SubjectName} is not matching.

    Any solution for this ?

  • Carlos Monteiro

    Hi James,

    What should be the content of the CSV file?
    Can You give an example of the file and how to get it work?

    Regards,

  • Matthias Frigge

    Hi James,

    i need the email in the ### INI file Generation for the request. How can i do it?

    Regards,

  • Kim

    $sAMAccountName = $user.sAMAccountName
    $SubjectName = ‘CN=’ + $sAMAccountName
    $UserPrincipalName = $sAMAccountName + ‘@’ + $DomainSuffix
    $ExportPath = ‘C:\CertExport’
    $ExportLocation = $ExportPath + ‘\’ + $sAMAccountName

Leave a Reply to Stefan Cancel reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>