Use PowerShell and .NET to Find Expired Certificates

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 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")



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")



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.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.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

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


7A42B8E32FABF3B85B1BAEF5D6FF6F29EB03C58E CN=Root CA, DC=rivendell, DC=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

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


7A42B8E32FABF3B85B1BAEF5D6FF6F29EB03C58E CN=Root CA, DC=rivendell, DC=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, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Comments (22)

  1. BrianYx2 says:

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

  2. Anonymous says:

    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:currentusermy | Where-Object { $_.HasPrivateKey -and $_.PrivateKey.CspKeyContainerInfo.Exportable } | Foreach-Object { [system.IO.file]::WriteAllBytes( (”+$env:USERPROFILE+’documentscerts’+$_.thumbprint+’.pfx’) , ($_.Export(‘PFX’, $env:certpassword)) ) }”
    Currently posted here:

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

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

  5. Boe Prox says:

    @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.Certificates | Where {

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

    } | ForEach {

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



  6. Anonymous says:

    This is a great script, but I just have two questions:

    1) When using a list of computers from a txt file, how can we output it to show the computer name?
    2) When I try to pipe the results of the information, I get an error message saying ’empty pipe’ not allowed?

    Any ideas?


  7. Boe Prox says:

    @Ajoy Something like this would work if you are using the script I wrote on…/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 <> -Subject ‘Expiring Certs’ -SmtpServer <servername> -Attachments (Join-Path $Pwd ExpiringCerts.csv)


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

  8. Boe Prox says:

    @John IV

    Almost forgot, this would also work as well…

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


    $certs = $store.Certificates | Where {

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



  9. Igor Dvorkin says:

    If you want to do more certificate work in powershell check out my post about the powershell certificate helper library @…/better-certificate-management-in.html. It lets you do more certificate manipulation within powershell; without having to resort to the .net types directly.

  10. John VI says:

    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

  11. W Keith says:

    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.


  12. Ajoy says:

    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

  13. Kambui says:


    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


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


    At line:1 char:12

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

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

       + FullyQualifiedErrorId : DotNetMethodException

  14. Bob S says:

    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.NameMy","LocalMachine")


    $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:scriptsLookForExpiredCerts.ps1:12 char:12

    + $ <<<< ("ReadOnly")

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

       + FullyQualifiedErrorId : DotNetMethodException

    I know my problem is in the ("\$Member.NameMy","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.

  15. digital certificate says:

    This article supplies working solution of the problem. It will help for many users. Thank you very much.">digital certificate

  16. Hi, thanks for the info. A note to those troubling like I did first: Remote registry service must be running on remote and local machine, and use an account with enough rights to read registry on remote computer. When used against local machine, start
    PS shell as administrator or you will get access denied for $store.Open("ReadWrite") for sure. Good luck 🙂

  17. Mark says:

    This is a very useful script–thank you! Can you suggest a way to echo the name of each machine in the "Select Issuer…" statement? We have 100s of servers, and many SAN or wildcard certs, so its not always apparent from the name where the cert resides.
    Thanks again.

  18. Servaas G says:

    @Bob S: The error message "The network path was not found" also occured in my situation. I solved it by starting the "Remote Registry" service on the target machine. I also checked the registry permissions (
    but it appeared that nothing needed to be changed there.

  19. pankaj says:

    i want to get certificates from all stores via a single script
    How we can achive it

  20. Barry Radeka says:

    Trying to delete certs from multiple computer by the Friendly Name, any have a script that I can modify?

  21. walter bush says:

    Hey this is a great page! Thanks A lot! also if you want some tips on expired listings & FSBO’s check out this short video!

Skip to main content