Import PFX certificates on remote servers (PSv2-compatible)

From Svendsen Tech PowerShell Wiki
Jump to: navigation, search

Having a need to install PFX certificates on various 2008 R2 servers with PowerShell version 2, I couldn't use the new 2012 R2/Win 8.1 (PSv4) Import-PfxCertificate cmdlet. Using the code I found here in "mao47"'s answer as a base, I wrote up some code to remotely install PFX certificates - supporting specific certificate stores.

I copy to a temporary file in the remote \\computer\admin$ share and then use PowerShell remoting to run the code to import it remotely, so this requires that both administrative shares via SMB/CIFS and PSRemoting are configured and available in the environment.

Client operating systems later than Vista(?), at least from Windows 7 on, do not by default expose the administrative shares, as far as I can remember from when I set up a GPO for Windows 7 clients and tested stuff. This can be enabled on clients via a GPO, but does represent somewhat of a security risk. Servers should have them enabled by default.

By default it installs with the flags PersistKeySet and MachineKeySet. The flags are documented here. Send them in in a string separated by commas.

The X509Certificates class I use under System.Security.Cryptography is documented here.

This isn't a super polished product, but should be perfectly usable for various scenarios, and can be adapted as needed (editing the source code if the parameters aren't enough for you).




Example of successful import

The screenshot shows the verb Install; this has been changed to Import.

Import-PfxCertificate-example.png

PS D:\> . .\scripts\Import-STPfxCertificate.ps1

PS D:\> Import-STPfxCertificate -Cn admin-01 -CertFilePath \\someserver\Cert\AD2023.pfx
ADMIN-01: Successfully added certificate.

Parameters

ComputerName Target computer(s). (alias: -Cn)
CertFilePath Path to the PFX certificate you want to install, as found in an accessible file system.
CertRootStore Certificate root store to install to. Default: 'localmachine'.
CertStore Certificate store within root store to install to. Default: 'my' ("Personal" when viewed in mmc.exe).
X509Flags X509 flags to pass to the import method. Default: 'PersistKeySet,MachineKeySet'. Documented here.
Password Leave empty to be prompted for the PFX certificate password. System.SecureString. You can store it beforehand with $mypass = Read-Host -AsSecureString

Download

Import-STPfxCertificate.ps1.txt - right-click and download, remember to unblock, rename to have the .ps1 extension only.

Earlier versions (if any): File:Import-STPfxCertificate.ps1.txt.

Source code

Store in a file called Import-PfxCert.ps1 (or whatever you want, but use the .ps1 extension). Dot-source this file (see screenshot example) to get the function into your session, and then call it.

#requires -version 2
function Import-STPfxCertificate
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][Alias('Cn')] [string[]] $ComputerName,
        [Parameter(Mandatory=$true)][string] $CertFilePath,
        [string] $CertRootStore = 'localmachine',
        [string] $CertStore = 'My',
        [string] $X509Flags = 'PersistKeySet,MachineKeySet',
        [System.Security.SecureString] $Password = $null)
    $ErrorActionPreference = 'Continue'
    $TempCertFileName = 'Svendsen.Tech.temporary.abcdefg.pfx'
    if ($Password -eq $null)
    {
        $Password = Read-Host -Prompt 'Enter PFX cert password' -AsSecureString
    }
    foreach ($Computer in $ComputerName)
    {
        $Destination = "\\$Computer\admin$\$TempCertFileName"
        try
        {
            Copy-Item -LiteralPath $CertFilePath -Destination $Destination -ErrorAction Stop
        }
        catch
        {
            Write-Error -Message "${Computer}: Unable to copy '$CertFilePath' to '$Destination'. Aborting further processing of this computer."
            continue
        }
        Invoke-Command -ComputerName $Computer -ScriptBlock {
            param(
                [string] $CertFileName,
                [string] $CertRootStore,
                [string] $CertStore,
                [string] $X509Flags,
                $PfxPass)
            $CertPath = "$Env:SystemRoot\$CertFileName"
            $Pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
            # Flags to send in are documented here: https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509keystorageflags%28v=vs.110%29.aspx
            $Pfx.Import($CertPath, $PfxPass, $X509Flags) #"Exportable,PersistKeySet")
            $Store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $CertStore, $CertRootStore
            $Store.Open("MaxAllowed")
            $Store.Add($Pfx)
            if ($?)
            {
                "${Env:ComputerName}: Successfully added certificate."
            }
            else
            {
                "${Env:ComputerName}: Failed to add certificate! $($Error[0].ToString() -replace '[\r\n]+', ' ')"
            }
            $Store.Close()
            Remove-Item -LiteralPath $CertPath
        } -ArgumentList $TempCertFileName, $CertRootStore, $CertStore, $X509Flags, $Password
    }
}