I need to blow the dust off this sight.

About 2 months ago I rejoined the PowerShell team after a 7 year hiatus. I’ve continued to use PowerShell everyday, and I’m excited to be part of the team again. I had been in the Office org working on Office online services and most of the PowerShell work I did was pretty specific to that, so I didn’t really have much to share. That condition has changed, so I expect I’ll have more to share.

One of the issues that we had when working with online services was certificate replacement. From a security perspective, you want to be able to replace certificates on a fairly regular basis; however, it’s sometimes hard to be sure that you’re not changing more than you should when you get a new cert (we had that problem quite a bit). To that end, I created a certificate comparison script which helped make sure that the new cert would not have the wrong changes. It allows you to compare arbitrary properties or extensions of two certificates (public (cer) or private (pfx)) so you could easily see if the new certificate was consistent with the prior cert in the places it should, and have only the changes (usually NotBefore and NotAfter).

Here’s the script:

[CmdletBinding()]

param (

[Parameter(Position=0,Mandatory=$true,ParameterSetName="pfx")]$pfx1,

[Parameter(Position=1,Mandatory=$true,ParameterSetName="pfx")]$pass1,

[Parameter(Position=2,Mandatory=$true,ParameterSetName="pfx")]$pfx2,

[Parameter(Position=3,Mandatory=$true,ParameterSetName="pfx")]$pass2,

[Parameter(Position=0,Mandatory=$true,ParameterSetName="cer")]$cer1,

[Parameter(Position=1,Mandatory=$true,ParameterSetName="cer")]$cer2,

[Parameter()]$properties = @("NotBefore","NotAfter","Subject","Issuer"),

[Parameter()]$extensions = "Key Usage",

[Parameter()][switch]$raw

)

BEGIN

{

$X509T = "System.Security.Cryptography.X509Certificates.X509Certificate2"

}

END

{

if ( $PSCmdlet.ParameterSetName -eq "pfx")

{

$fullname = (get-item $pfx1).fullname

$cert1 = new-object $X509T ([io.file]::ReadAllBytes($fullname)),$pass1

$fullname = (get-item $pfx2).fullname

$cert2 = new-object $X509T ([io.file]::ReadAllBytes($fullname)),$pass2

$file1 = $pfx1

$file2 = $pfx2

if ( $file1 -eq $file2 ) { $file2 = "${file2} (2)" }

}

else

{

$fullname = (get-item $cer1).fullname

$cert1 = new-object $X509T $fullname

$fullname = (get-item $cer2).fullname

$cert2 = new-object $X509T $fullname

$file1 = $cer1

$file2 = $cer2

if ( $file1 -eq $file2 ) { $file2 = "${file2} (2)" }

}



$collection = @()

foreach($property in $properties)

{

$collection += new-object psobject -prop @{

Property = $property

$file1 = $cert1.$property

$File2 = $cert2.$property

Different = $cert1.$property -ne $cert2.$property

}

}

foreach ( $extension in $extensions)

{

$v1 = $cert1.Extensions|%{$_.oid}|?{$_.friendlyname -eq "$extension"}|%{$_.value}

$v2 = $cert2.Extensions|%{$_.oid}|?{$_.friendlyname -eq "$extension"}|%{$_.value}

$k = $Extension -replace " "

$collection += new-object psobject -prop @{

Property = $k

$File1 = $v1

$File2 = $v2

Different = $v1 -ne $v2

}

}

if ( $raw )

{

$cert1

$cert2

,$collection

}

else

{

$collection|ft @{ L="Property";W = 14; E={$_.property}},@{L="Different";W=10;E={$_.different}},$file1,$file2

}

}

Because this was a tool for our service engineers to use, I provide formatted data, rather than raw objects. However, if you use the –Raw switch, you’ll get the certs that you’re comparing as well as the collection of differences. Here’s an example of output (using a cert provided by the network tool ‘fiddler2’)

PS# .\Compare-Certificate.ps1 -pfx1 C:\temp\fiddlerCert.pfx -pfx2 C:\temp\fiddlerCert.pfx

cmdlet Compare-Certificate.ps1 at command pipeline position 1

Supply values for the following parameters:

pass1:

pass2:

Property Different C:\temp\fiddlerCert.pfx C:\temp\fiddlerCert.pfx (2)

——– ——— ———————– —————————

NotBefore False 8/2/2013 12:00:00 AM 8/2/2013 12:00:00 AM

NotAfter False 8/1/2024 11:59:59 PM 8/1/2024 11:59:59 PM

Subject False CN=clients1.google.com, O=DO_NOT_TRUST, OU=Created by… CN=clients1.google.com, O=DO_NOT_TRUST, OU=Created by…

Issuer False CN=DO_NOT_TRUST_FiddlerRoot, O=DO_NOT_TRUST, OU=Creat… CN=DO_NOT_TRUST_FiddlerRoot, O=DO_NOT_TRUST, OU=Creat…

KeyUsage False

Since it’s the same cert in this case, there’s no differences. That in itself would be pretty handy if you gotten a new cert and it was identical to the old cert.