PowerShell Pipeline

How To Scan for Expiring Certificates in PowerShell

PowerShell remoting will allow you to locate the expiring certs before they cause your Web site to go down.

I'm sure we have all been there before in our career as a Windows administrator: One day your Web site is handling requests through https and the next day no one can access the site. What could have happened to cause this sudden inaccessibility to the Web page? An expired certificate can certainly cause this to happen.

Whether it is a Web server that is listening on port 443 for https or a Domain Controller certificate that is used to support LDAPS traffic or handle smart card logons, a certificate can spell a great low stress day or trouble in paradise when it suddenly has expired, leaving you running around trying to issue another one, either through a local Certificate Authority (CA) or through an online request form.

The bottom line is that a certificate has expired, leaving an outage that could have been avoidable. If only there was some remote approach to scanning your servers for certificates that were about to expire...

Enter PowerShell to the rescue! If you have PowerShell remoting enabled on all of your servers in your environment, the solution becomes very simple: remotely check the certificates on each server and report back which ones are close to an expiration date, such as 14 days out. The code below will look at a specified system and use PowerShell remoting to locate certificates that are expiring in 14 days or already expired.

Invoke-Command -ComputerName  'boe-pc' -ScriptBlock  {Get-ChildItem Cert:\LocalMachine\My  | 
Where {$_.NotAfter -lt (Get-Date).AddDays(14)}} | ForEach {
[pscustomobject]@{
Computername = $_.PSComputername
Subject = $_.Subject
ExpiresOn = $_.NotAfter
DaysUntilExpired = Switch ((New-TimeSpan -End $_.NotAfter).Days) {
{$_ -gt 0} {$_}
Default {'Expired'}
}
}
}

In order to locate the certificates, I have to look in the LocalMachine store location and then in the My store name. There are three certificates which have fallen into the 14 day criteria with one of those 3 having already been expired as shown in Fig.1

Figure 1. Results returned from PowerShell remoting showing expired and expiring certificates.

As I said earlier, this is great when you have PowerShell remoting in your environment, but what if you do not have this ready to go? Do we just give up hope or do we find another way to reach the end goal of finding those certificates on remote systems?

Of course we find a way to accomplish this! In this case, we are going to dip our toe into some .Net types and use the System.Security.Cryptography.X509Certificates.X509Store which will allow us to connect to a remote system and locate certificates. But before we can just use this, we need to know the parameters that are required for this object to be created. I can run the following code to figure out what I need in order for this to work.

[System.Security.Cryptography.X509Certificates.X509Store].GetConstructors() | ForEach {
($_.GetParameters() | ForEach {$_.ToString()}) -Join ", "

The results that I get back show multiple possibilities:

System.String storeName
System.Security.Cryptography.X509Certificates.StoreName storeName
System.Security.Cryptography.X509Certificates.StoreLocation storeLocation
System.Security.Cryptography.X509Certificates.StoreName storeName, System.Security.Cryptography.X509Certificates.StoreLocation storeLocation
System.String storeName, System.Security.Cryptography.X509Certificates.StoreLocation storeLocation
IntPtr storeHandle

I am most interested in using the System.Security.Cryptography.X509Certificates.StoreName and System.Security.Cryptography.X509Certificates.StoreLocation parameters to complete the build of this object and connect to the remote system. Fortunately, these are both Enums which means that they have values already defined. All I need to do is pick the correct value that meets my specifications. That can be accomplished using the GetNames() method of [Enum].

First we will look at the StoreName enum and view the values that are shown in Fig.2.

[Enum]::GetNames('System.Security.Cryptography.X509Certificates.StoreName')
Figure 2. Values for System.Security.Cryptography.X509Certificates.StoreNa

Next we can check out the StoreLocation and see what our options are which are shown in Fig.3.

[Enum]::GetNames('System.Security.Cryptography.X509Certificates.StoreLocation') 
Figure 3. System.Security.Cryptography.X509Certificates.StoreLocation

Much like my remoting example, I will be going with the LocalMachine store location and the My store name to search for my certificates. With that out of the way, the next step is to build out the object using my parameters. There is a slight change that I need to do in order to make this useful for a remote connection.

$Computername = "boe-pc"
$CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store -ArgumentList "\\$($Computername)\My", "LocalMachine"
$CertStore | Select-Object -Property *

It is important to note that while it appears that I am connected in Fig.4, I actually have not yet made the remote connection. This will be more evident when I use the Open() method and attempt to read the certificates.

Figure 4. X509 object returned

Speaking of Open(), I can now make my connection attempt and view the certificates on the My store name by using Open() with an OpenFlag. Fig.5 shows the available flag values that I can use with this method:

 

[Enum]::GetNames('System.Security.Cryptography.X509Certificates.OpenFlags') 
Figure 5. Available values of System.Security.Cryptography.X509Certificates.OpenFlags

In this case, I am going with ReadOnly as I only care to view the certificates and not actually perform any sort of action against this certificate store.
$CertStore.Open('ReadOnly')

Assuming that we don't have a connection issue or lack the necessary rights on the remote system, we will not see a return value of any kind. What this means is that we can now view the certificates in the store. Fig. 6 shows that we now have values under the StoreHandle and Certificates properties.

Figure 6. X509 object with updated properties

From here, I can apply similar code that I used with my remoting example to search for certificates that are expiring or have already expired.

$CertStore.Certificates  |
Where {$_.NotAfter -lt (Get-Date).AddDays(14)}  | ForEach {
[pscustomobject]@{
Computername = $Computername
Subject = $_.Subject
ExpiresOn = $_.NotAfter
DaysUntilExpired = Switch ((New-TimeSpan -End $_.NotAfter).Days) {
{$_ -gt 0} {$_}
Default {'Expired'}
}
}
}
[Click on image for larger view.]  Figure 7. Results from the remote connection

I have a function called Get-Certificate available that you can use to make this process a little easier to search remote certificate stores when PowerShell remoting is not available.

Function Get-Certificate  {
<#
.SYNOPSIS
Retrieves certificates from a local or remote system.

        .DESCRIPTION
Retrieves certificates from a local or remote system.

        .PARAMETER Computername
A single or list of computernames to perform search against

        .PARAMETER StoreName
The name of the certificate store name that you want to search

        .PARAMETER StoreLocation
The location of the certificate store.

        .NOTES
Name: Get-Certificate
Author: Boe Prox
Version History:
1.0 - Initial Version

        .EXAMPLE
Get-Certificate -Computername 'boe-pc' -StoreName My -StoreLocation LocalMachine

            Thumbprint                                Subject                             
----------                                -------                             
F29B6CB248E3395B2EB45FCA6EA15005F64F2B4E  CN=SomeCert                         
B93BA840652FB8273CCB1ABD804B2A035AA39877  CN=YetAnotherCert                   
B1FF5E183E5C4F03559E80B49C2546BBB14CCB18  CN=BOE                               
65F5A012F0FE3DF8AC6B5D6E07817F05D2DF5104  CN=SomeOtherCert                    
63BD74490E182A341405B033DFE6768E00ECF21B  CN=www.example.com

            Description
-----------
Lists all certificates

        .EXAMPLE
Get-Certificate -Computername 'boe-pc' -StoreName My -StoreLocation LocalMachine -DaysUntilExpired 14 |
Select Subject, DaysUntilExpired,NotAfter

            Subject                              DaysUntilExpired NotAfter                
-------                              ---------------- --------                
CN=SomeCert                                        12 10/22/2014 12:00:00 AM  
CN=SomeOtherCert                                    4 10/14/2014 12:00:00 AM  
CN=www.example.com                            Expired 12/21/2011 11:00:00 PM

            Description
-----------
Lists all certificates that Expire in 14 days or has already expired

        .EXAMPLE
Get-Certificate -Computername 'boe-pc' -StoreName My -StoreLocation LocalMachine -DaysUntilExpired 14 -HideExpired |
Select Subject, DaysUntilExpired,NotAfter

            Subject                              DaysUntilExpired NotAfter                
-------                              ---------------- --------                
CN=SomeCert                                        12 10/22/2014 12:00:00 AM  
CN=SomeOtherCert                                    4 10/14/2014 12:00:00 AM

            Description
-----------
Lists all certificates that Expire in 14 days and hides certificates that have expired

    #>
[cmdletbinding(
DefaultParameterSetName = 'All'
)]
Param (
[parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('PSComputername','__Server','IPAddress')]
[string[]]$Computername = $env:COMPUTERNAME,
[System.Security.Cryptography.X509Certificates.StoreName]$StoreName = 'My',
[System.Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = 'LocalMachine',
[parameter(ParameterSetName='Expire')]
[Int]$DaysUntilExpired,
[parameter(ParameterSetName='Expire')]
[Switch]$HideExpired
)
Process {
ForEach ($Computer in $Computername) {
Try {
Write-Verbose ("Connecting to {0}\{1}" -f "\\$($Computername)\$($StoreName)",$StoreLocation)
$CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store -ArgumentList "\\$($Computername)\$($StoreName)", $StoreLocation
$CertStore.Open('ReadOnly')
Write-Verbose "ParameterSetName: $($PSCmdlet.ParameterSetName)"
Switch ($PSCmdlet.ParameterSetName) {
'All' {
$CertStore.Certificates
}
'Expire' {
$CertStore.Certificates | Where {
$_.NotAfter -lt (Get-Date).AddDays($DaysUntilExpired)
} | ForEach {
$Days = Switch ((New-TimeSpan -End $_.NotAfter).Days) {
{$_ -gt 0} {$_}
Default {'Expired'}
}
$Cert = $_ | Add-Member -MemberType NoteProperty -Name DaysUntilExpired -Value $Days -PassThru
If ($HideExpired -AND $_.DaysUntilExpired -ne 'Expired') {
$Cert
} ElseIf (-Not $HideExpired) {
$Cert
}
}
}
}
} Catch {
Write-Warning "$($Computer): $_"
}
}
}
}

With this function, we can find all certificates…

 

Get-Certificate -Computername 'boe-pc'  -StoreName My  -StoreLocation LocalMachine 

…or look at certificates which expire in 14 days…

Get-Certificate -Computername 'boe-pc'  -StoreName My  -StoreLocation LocalMachine  -DaysUntilExpired 14  |
Select Subject, DaysUntilExpired,NotAfter

…and even hide expired certificates and only show those that are expiring soon…

Get-Certificate -Computername 'boe-pc'  -StoreName My  -StoreLocation LocalMachine  -DaysUntilExpired 14  -HideExpired |
Select Subject, DaysUntilExpired,NotAfter

Feel free to take it for a spin and start monitoring the certificates in your environment!

 

About the Author

Boe Prox is a Microsoft MVP in Windows PowerShell and a Senior Windows System Administrator. He has worked in the IT field since 2003, and he supports a variety of different platforms. He is a contributing author in PowerShell Deep Dives with chapters about WSUS and TCP communication. He is a moderator on the Hey, Scripting Guy! forum, and he has been a judge for the Scripting Games. He has presented talks on the topics of WSUS and PowerShell as well as runspaces to PowerShell user groups. He is an Honorary Scripting Guy, and he has submitted a number of posts as a to Microsoft's Hey, Scripting Guy! He also has a number of open source projects available on Codeplex and GitHub. His personal blog is at http://learn-powershell.net.

comments powered by Disqus
Most   Popular