Use PowerShell and .NET to Find Expired Certificates

Use PowerShell and .NET to Find Expired Certificates

  • Comments 18
  • Likes

Summary: Learn how to use Windows PowerShell and Microsoft .NET classes to find expired certificates on local and remote computers.

Hey, Scripting Guy! Question

  Hey, Scripting Guy! How can I use Windows PowerShell and the .NET Framework classes to work with certificates?

-- PB

 

Hey, Scripting Guy! Answer Hello PB,

Microsoft Scripting Guy Ed Wilson here. We continue Guest Blogger Week today with Boe Prox, currently a senior systems administrator with BAE Systems. He has been in the IT industry since 2003 and has spent the past three years working with VBScript and Windows PowerShell and now looks to script whatever he can, whenever he can. He is also a moderator on the Hey, Scripting Guy! Forum. You can check out his blog at http://boeprox.wordpress.com and also see his current project, the WSUS Administrator module, just recently published on CodePlex.

Photo of Boe Prox

PKI certificates are one of those things that most system administrators know about but probably never spend a lot of time with until something goes wrong. Usually a certificate expires and causes some sort of interruption to a service in which users or the boss are asking what is happening.

What I am first going to do is to show you how to connect to a local or remote computer and view their certificates using the .NET class, System.Security.Cryptography.X509Certificates.X509Store.

There are two locations that you can connect to:

  • LocalMachine: Global certificates that affect the computer and user accounts such as machine certificates for network access or SSL certificates for website access.
  • CurrentUser: Certificates that are user specific, such as smart card certificates and certificates used for encryption.

Of these two certificate store locations, only LocalMachine can be accessed remotely via the .NET class. Attempting to access CurrentUser will result in an “Access Denied” message because of security reasons.

Connecting to the local machine LocalMachine certificate store requires a few lines of code in order to gain access to the certificates:

$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")

$store.Open("ReadOnly")

$store.Certificates

The first thing we see here is that we are creating the object using the X509 class and calling two values that represent the store name and store location that we will be connecting to. There are several store names that are available to view, as shown in the following image.

Image of available store names

For this article, we are going to focus on the LocalMachine store name as that is where we will be looking for web SSL certificates, domain controller certificates, and machine certificates.

The next line of code has us setting the flag that we will be using when we open up the store. Because we are only viewing the certificates on the store and nothing else, we opt to go with the ReadOnly flag. There are other flags available when opening the store, as shown in the following image

Image of available flags

Finally, we are able to view the machine certificates within store in the third line, as shown in the following image.

Image of viewing machine certificates

If anyone has used the Certificate provider in Windows PowerShell before, this will look very familiar. In fact, look at the output of the each command I run for both the .NET class and using the Certificate provider:

PS C:\Users\boe> $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")

PS C:\Users\boe> $store.Open("ReadOnly")

PS C:\Users\boe> ($store.certificates | Select -First 1).gettype() | Format-Table Name, BaseType -auto

Name BaseType

---- --------

X509Certificate2 System.Security.Cryptography.X509Certificates.X509Certificate

PS C:\Users\boe> (GCI cert:/localmachine/my | Select -First 1).gettype() | Format-Table Name,BaseType -auto

Name BaseType

---- --------

X509Certificate2 System.Security.Cryptography.X509Certificates.X509Certificate

The BaseType of each is System.Security.Cryptography.X509Certificates.X509Certificate, which might make you wonder: “Boe, why in the world are we using the .NET classes when we can so easily perform this with the provider?”

Remember when I mentioned that you could use the .NET class to connect to a certificate store on a remote machine? Well, here lies the limitation of the provider, at least if you do not have Windows PowerShell remoting enabled in your environment. With the provider, though you can more easily view all of the certificates in both the LocalMachine and CurrentUser store locations with nothing more than a line of code, you are unable to use it to make a query to a remote machine without first initiating a remote Windows PowerShell session to that server.

Using the same code snippet earlier, we make one small change to the store name by adding the UNC path to the system:

$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("\\dc1\My","LocalMachine")

$store.Open("ReadOnly")

$store.Certificates

The results are shown in the following image.

Image of script results

Pretty cool, huh?

You can do this using the certificate provider, assuming you not only have Windows PowerShell 2.0 installed on both your client and the remote system, but also have remoting enabled on the remote system. However, if your place of business is still running Windows PowerShell 1.0 or has not made the leap to allow Windows PowerShell remoting for one reason or another, using the .NET classes to make the remote connection is your best bet.

 

Locating Expiring Certificates

Now that we have made a connection to the remote LocalMachine store on a server, let us go ahead and find out if any certificates are going to expire within the next 14 days.

Using my existing connection, we will look at the NotAfter property—the property that tells us the expiration date of the certificate—to find out if it has expired or if it will expire within 14 days. The NotAfter property of the certificate represents the local time that the given certificate will expire. With 10 lines of code, I can run a query against a remote server to find any certificates that are due to expire in the next 14 days.

#Number of days to look for expiring certificates

$threshold = 14

#Set deadline date

$deadline = (Get-Date).AddDays($threshold)

$store=new-object System.Security.Cryptography.X509Certificates.X509Store("\\dc1\my","LocalMachine")

$store.open("ReadOnly")

$store.certificates | % {

If ($_.NotAfter -lt $deadline) {

$_ | Select Issuer, Subject, NotAfter, @{Label="ExpiresIn"; Expression={($_.NotAfter - (Get-Date)).Days}}

}

}

Issuer Subject NotAfter ExpiresIn

------ ------- -------- ---------

CN=Root CA, DC=r... 1/25/2011 10:32:... 5

From the looks of it, there is one certificate that is going to expire in five days. As you can tell, I added the custom property ExpiresIn, which lists how many days until the certificate expires to the output, by using the @{Label=””;Expression={}} hash table. Imagine running this across your domain and finding out if you had certificates expiring before it became an issue! It is as easy as adding a few more lines of code to use a ForEach loop to iterate through all of the computers. Better yet, configure the code to send an email with this information, save as a script, and then set the script up as a scheduled job. You will then have this process completely automated.

 

Locate Expired Certificates

Building on what I covered with locating expiring certificates, I will slightly modify the existing code to find certificates that have expired.

$store=new-object System.Security.Cryptography.X509Certificates.X509Store("\\dc1\my","LocalMachine")

$store.open("ReadOnly")

$store.certificates | % {

If ($_.NotAfter -lt (Get-Date)) {

$_ | Select Issuer, Subject, @{Label="ExpiredOn";Expression={$_.NotAfter}}

}

}

Issuer Subject ExpiredOn

------ ------- --------

CN=Root CA, DC=r... 1/24/2011 04:13:...

Just like that, we found a certificate that has expired which leaves us to decide whether it needs to be renewed or removed from the store. Of course, the best part is this can easily be automated to send out an email to you if any expired certificates are found.

 

Get-PKICertificates Function

To assist with performing queries against local and remote systems and find expiring or expired certificates, I wrote an advanced function that you could use to accomplish this task. This advanced function allows you to supply a local or remote system, determine whether to use the LocalMachine or CurrentUser store, and search store names other than the “My” name. In addition, you can set the OpenFlag that you want to use when you query the store. The script is available for download in the Script Repository.

Here are a few examples of using the advanced function to locate certificates.

Listing Certificates That Expire in 14 Days

PS C:\Users\boe> Get-PKICertificates -comp dc1 -StoreLocation LocalMachine -StoreName My -ExpiresIn 14 | Format-Table Su

bject, FriendlyName,Issuer, @{Label="ExpiresIn";Expression={($_.NotAfter - (get-Date)).Days}} -auto

Subject FriendlyName Issuer ExpiresIn

------- ------------ ------ ---------

DC1 CN=Root CA, DC=rivendell, DC=com 4

 

List All Certificates on a Remote Computer

PS C:\Users\boe> Get-PKICertificates -comp dc1 -StoreLocation LocalMachine -StoreName My

Thumbprint Subject

---------- -------

A1CECD6C1B0A61F3D784EC29C60BD0D8F2FA46BD

7A42B8E32FABF3B85B1BAEF5D6FF6F29EB03C58E CN=Root CA, DC=rivendell, DC=com

3B0D348B90D663293067F8C481075927016F56CE CN=dc1.rivendell.com

10E740A73045250E9AF0778C7DAABB2E1F22C6D9 CN=dc1, OU=Scripting, O=Prox, L=Bellevue, S=NE, C=US

 

List Certificates on Multiple Computers

PS C:\Users\boe> Get-PKICertificates -comp dc1,boe-laptop -StoreLocation LocalMachine -StoreName My

Thumbprint Subject

---------- -------

A1CECD6C1B0A61F3D784EC29C60BD0D8F2FA46BD

7A42B8E32FABF3B85B1BAEF5D6FF6F29EB03C58E CN=Root CA, DC=rivendell, DC=com

3B0D348B90D663293067F8C481075927016F56CE CN=dc1.rivendell.com

10E740A73045250E9AF0778C7DAABB2E1F22C6D9 CN=dc1, OU=Scripting, O=Prox, L=Bellevue, S=NE, C=US

211E73160B9E67F8C0EEBAB7007CF68589F392D1 CN=boe-laptop

That is it for today. I hope you enjoyed my guest blog on querying for certificates using the .NET classes, and I would also like to thank Ed for allowing me this great opportunity to be a guest blogger for today.

 

Boe, I want to thank you for sharing with us and for being our guest blogger. Guest Blogger Week will continue tomorrow when Doug Finke will join us.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • If you want to do more certificate work in powershell check out my post about the powershell certificate helper library @ ig2600.blogspot.com/.../better-certificate-management-in.html. It lets you do more certificate manipulation within powershell; without having to resort to the .net types directly.

  • This is pretty cool. So How could one build off this to say Delete all Certificate in the "Address Book or Other People" store issued by a specific CA

  • Really good resource Ed. I'm a complete ps beginner and need to collect SSL info from multiple servers that are not in a domain and all have different credentials. If anyone has any pointers on how I could do that I'd be very grateful.

    Thanks!

  • @John VI

    Thanks for the comment! Something like this should work to remove all certificates issued by a specific CA. Just change CA_Name to the CA you are looking for.

    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("AddressBook","LocalMachine")

    $store.Open("ReadWrite")

    $store.Certificates | Where {

       $_.Issuer -like "*<CA Name>*"

    } | ForEach {

       Write-Verbose ("Removing {0}" -f $_.Subject) -Verbose

       $Store.Remove($_)

    }

  • @John IV

    Almost forgot, this would also work as well...

    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("AddressBook","LocalMachine")

    $store.Open("ReadWrite")

    $certs = $store.Certificates | Where {

       $_.Issuer -like "*<CA_Name>*"

    }

    $Store.RemoveRange($certs)

  • thanks this is cool! could you please alter the script so that i can run this in one server and it will fetch expiry date of other few servers and trigger an email to my inbox

  • @Ajoy Something like this would work if you are using the script I wrote on gallery.technet.microsoft.com/.../a2a500e5-1dd2-4898-9721-ed677399679c:

    $BadCerts = Get-PKICertificates -Computer (Get-Content servers.txt) -StoreLocation LocalMachine -StoreName My -ExpiresIn 14

    If ($BadCerts) {

       $BadCerts | Export-Csv -NoTypeInformation (Join-Path $Pwd ExpiringCerts.csv)

       Send-MailMessage -To <users> -From <account@domain.com> -Subject 'Expiring Certs' -SmtpServer <servername> -Attachments (Join-Path $Pwd ExpiringCerts.csv)

    }

    Just update the parameters in the Send-MailMessage cmdlet to reflect your environment.

  • PB,

    You know I still get Access is denied when I call the Open Method on the store with LocalMachine for a remotecomputer.

    I have admin privileges on both remote and local system

    $store.Open("ReadOnly")

    Exception calling "Open" with "1" argument(s): "Access is denied.

    "

    At line:1 char:12

    + $store.Open <<<< ("ReadOnly")

       + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException

       + FullyQualifiedErrorId : DotNetMethodException

  • I am trying to use this to scan all the servers in our servers OU to look for expiring certs.  Here is the code I am using.

    #Number of days to look for expiring certificates

    $threshold = 30

    #Set deadline date

    $deadline = (Get-Date).AddDays($threshold)

    ForEach ($Member in (get-adcomputer -Filter * -Searchbase "OU=servers,DC=xxxxx,dc=yy,dc=zzzz,dc=com"))

           {

    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("\\$Member.Name\My","LocalMachine")

    $store.open("ReadOnly")

    $store.certificates | % {

    If ($_.NotAfter -lt $deadline) {

    $_ | Select Issuer, Subject, NotAfter, @{Label="ExpiresIn"; Expression={($_.NotAfter - (Get-Date)).Days}}

    }

    }

    }

    I am getting the following error in response.

    Exception calling "Open" with "1" argument(s): "The network path was not found.

    "

    At H:\scripts\LookForExpiredCerts.ps1:12 char:12

    + $store.open <<<< ("ReadOnly")

       + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException

       + FullyQualifiedErrorId : DotNetMethodException

    I know my problem is in the ("\\$Member.Name\My","LocalMachine") part of the code.  I have tried numerous different ways to code this, putting quotes in different places and such, but no luck.

    Can anyone tell me what I am doing wrong here? I am pretty new to scripting.  I know I am close, but just can't quite get it.  Any help would be appreciated.  

    Thank  you in advance.

  • Great article!  I really enjoyed the clear, well thought out explanation of the .Net class

  • Great article!  I really enjoyed the clear, well thought out explanation of the .Net class

  • Could this script be used against user accounts in Active Directory to remove expired user certificates?  If so, how would it be done?

  • Very informative! Question for you. I would like to Export the expired certificates to .pfx format. How can I export the expired certificates? I’m currently using the following to export all certs that I have a private key, and are marked “Exportable,” but it does not include ‘expired certificates’ that have the Property “Archived” set to TRUE. powershell -command "dir cert:\currentuser\my | Where-Object { $_.HasPrivateKey -and $_.PrivateKey.CspKeyContainerInfo.Exportable } | Foreach-Object { [system.IO.file]::WriteAllBytes( (''+$env:USERPROFILE+'\documents\certs\'+$_.thumbprint+'.pfx') , ($_.Export('PFX', $env:certpassword)) ) }" Currently posted here: http://social.technet.microsoft.com/Forums/en-US/357ec347-f41a-4e69-9c9e-9d272aaff1fd/exporting-certs-via-powershell-how-to-include-archived-certificates?forum=winserverpowershell

  • This article supplies working solution of the problem. It will help for many users. Thank you very much.

    http://www.arx.com/information/digital-certificate">digital certificate

  • Awesome.