Get-CertificateHealth.ps1

<#
.Synopsis
   Get certificates from the filesystem or certificate store and display their health
   for expiration, pending expiration, and deprecated signature algorithms.
.DESCRIPTION
   Get certificates from the filesystem or certificate store and display their health
   for expiration, pending expiration, and deprecated signature algorithms.
 
   The function outputs custom objects that include basic certificate properties as
   well as the certificate's expiration date, how many days are left, and the name of
   the signature algorithm used to generate the certificate.
 
   Depending on the provided warning and critical algorithm parameters, a certificate
   will be marked as OK, Warning, or Critical. By default the script considers sha1RSA
   certificates to be warning (deprecated) since vendors are beginning to consider these
   certificates to use weak encryption. The md5 signature algorithm has already been
   designated as vulnerable and will be marked as critical. Microsoft already blocks
   these certificates.
 
   The certificate validity period is evaluated to determine if the certificate has
   expired (Critical) or will be expiring soon. Use the WarningDays and CriticalDays
   parameters to denote certificates with pending expiration.
 
   The certificate key size is also an indicator of health. Key sizes less than
   1024 bits are no longer supported, and now it is recommended to use at least 2048 bits.
 
   Requires Get-CertificateFile function from module CertificateHealth to evaluate
   certificate files from the filesystem.
.NOTES
   Created by: Jason Wasser @wasserja
   Modified: 10/12/2015 10:56:31 AM
 
   Changelog:
   Version 1.2
    * Added key size health properties
   Version 1.1
    * Added PowerShell 2.0 compatibility.
   Version 1.0
    * Initial Release
 
.PARAMETER Path
   Enter a path or paths containing certificates to be checked.
.PARAMETER WarningDays
   Specify the amount of days before the certificate expiration should be in a
   warning state.
.PARAMETER CriticalDays
   Specify the amount of days before the certificate expiration should be in a
   critical state.
.PARAMETER ExcludedThumbprint
   Array of thumbprints of certificates that should be excluded from being checked.
   This would be used if there is a certificate that you wish to ignore from health
   checks.
.PARAMETER WarningAlgorithm
   Array of algorithms that are deprecated.
.PARAMETER CriticalAlgorithm
   Array of algorithms with known vulnerabilities.
.PARAMETER CritialKeySize
   Certificates with key size less than this value will be considered critical.
.PARAMETER WarningKeySize
   Certificates with key size less than this value and greater than the CriticalKeySize
   will be considered warning.
.PARAMETER CertUtilPath
   Path to the certutil.exe.
.PARAMETER CertificateFileType
   Array of certificate file types that need to be checked.
.PARAMETER Recurse
   Recurse through subdirectories of specified path(s).
.EXAMPLE
   Get-CertificateHealth
   Gets all the certificates in the local machine personal certificate store (cert:\LocalMachine\My)
   and shows their basic information and health.
.EXAMPLE
   Get-CertificateHealth -Path C:\Website\Certificates
   Gets all the certificates in the c:\Website\Certificates folder and shows their basic
   information and health.
.EXAMPLE
   Get-CertificateHealth -Path 'Cert:\LocalMachine\My','C:\SSL' -Recurse
   Gets all the certificates in the local machine personal certificate store (cert:\LocalMachine\My)
   and C:\SSL including subfolders and shows their basic information and health.
.LINK
   https://gallery.technet.microsoft.com/scriptcenter/Certificate-Health-b646aeff
#>

#requires -Version 2.0
function Get-CertificateHealth
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$false,
                    ValueFromPipelineByPropertyName=$true,
                    Position=0)]
        [string[]]$Path = 'Cert:\LocalMachine\My',
        [int]$WarningDays = 60,
        [int]$CriticalDays = 30,
        [string[]]$ExcludedThumbprint,#=@('DF16240B462E80151BBCD7529D4C557A8CE1671C'),
        [string[]]$WarningAlgorithm=('sha1RSA'),
        [string[]]$CriticalAlgorithm=('md5RSA'),
        [int]$CriticalKeySize=1024,
        [int]$WarningKeySize=2048,
        [string]$CertUtilPath = 'C:\Windows\System32\certutil.exe',
        [string[]]$CertificateFileType = ('*.cer','*.crt','*.p7b'),
        [switch]$Recurse = $false
    )

    Begin
    {
    }
    Process
    {
        #region Certificate Check
        foreach ($CertPath in $Path) {
            # Gather certificates from the $CertPath
            # If we are looking in the Certificate Providor - Cert:\
            if ($CertPath -like 'cert:\*') {
                Write-Verbose "Getting certificates from $CertPath"
                $Certificates = Get-ChildItem -Path $CertPath -Recurse:([bool]$Recurse.IsPresent) -Exclude $ExcludedThumbprint
                }
            # Otherwise we need to use the certutil.exe to get certificate information.
            else {
                Write-Verbose "Getting certificates from $CertPath"
                $Certificates = Get-CertificateFile -Path $CertPath -CertificateFileType $CertificateFileType -Recurse:([bool]$Recurse.IsPresent) | Where-Object -FilterScript {$_.Thumbprint -notcontains $ExcludedThumbprint}
                }
            if ($Certificates) {
                #region Check individual certificate
                foreach ($Certificate in $Certificates) {
                                
                    # I first need to convert the properties so that I can use a similar process on either file or store certificates.

                    if ($Certificate.PSPath) {
                            if ($PSVersionTable.PSVersion.Major -lt 3) {
                                $CertificateProperties = @{
                                    FileName = $Certificate.PSPath
                                    Subject = $Certificate.Subject
                                    SignatureAlgorithm = $Certificate.SignatureAlgorithm.FriendlyName
                                    NotBefore = $Certificate.NotBefore
                                    NotAfter = $Certificate.NotAfter
                                    Days = ($Certificate.NotAfter - (Get-Date)).Days
                                    Thumbprint = $Certificate.Thumbprint
                                    KeySize = $Certificate.PublicKey.Key.KeySize
                                    }
                                }
                            else {
                                $CertificateProperties = [ordered]@{
                                    FileName = $Certificate.PSPath
                                    Subject = $Certificate.Subject
                                    SignatureAlgorithm = $Certificate.SignatureAlgorithm.FriendlyName
                                    NotBefore = $Certificate.NotBefore
                                    NotAfter = $Certificate.NotAfter
                                    Days = ($Certificate.NotAfter - (Get-Date)).Days
                                    Thumbprint = $Certificate.Thumbprint
                                    KeySize = $Certificate.PublicKey.Key.KeySize
                                    }
                                }
                            $Certificate = New-Object -TypeName PSObject -Property $CertificateProperties
                            }
                        elseif ($Certificate.FileName) {
                            # Nothing to do here yet.
                            }
                        else {
                            # Nothing to do here yet.
                            }
                    #region Check certificate expiration
                    
                    # Check certificate is within $WarningDays
                    if ($Certificate.NotAfter -le (Get-Date).AddDays($WarningDays) -and $Certificate.NotAfter -gt (Get-Date).AddDays($CriticalDays)) {
                        Write-Verbose "Certificate is expiring within $WarningDays days."
                        $ValidityPeriodStatus = 'Warning'
                        $ValidityPeriodStatusMessage = "Certificate expiring in $($Certificate.Days) days."
                        }
                    # Check certificate is within $CriticalDays
                    elseif ($Certificate.NotAfter -le (Get-Date).AddDays($CriticalDays) -and $Certificate.NotAfter -gt (Get-Date)) {
                        Write-Verbose "Certificate is expiring within $CriticalDays days."
                        $ValidityPeriodStatus = 'Critical'
                        $ValidityPeriodStatusMessage = "Certificate expiring in $($Certificate.Days) days."
                        }
                    # Check certificate is expired
                    elseif ($Certificate.NotAfter -le (Get-Date)) {
                        Write-Verbose "Certificate is expiring within $CriticalDays"
                        $ValidityPeriodStatus = 'Critical'
                        $ValidityPeriodStatusMessage = "Certificate expired: $($Certificate.Days) days."
                        }
                    # Certificate validity period is healthy.
                    else {
                        Write-Verbose "Certificate is within validity period."
                        $ValidityPeriodStatus = 'OK'
                        $ValidityPeriodStatusMessage = "Certificate expires in $($Certificate.Days) days."
                        }
                    #endregion

                    #region Check certificate algorithm
                    if ($CriticalAlgorithm -contains $Certificate.SignatureAlgorithm) {
                        Write-Verbose "Certificate uses critical algorithm."
                        $AlgorithmStatus = 'Critical'
                        $AlgorithmStatusMessage = "Certificate uses a vulnerable algorithm $($Certificate.SignatureAlgorithm)."
                        }
                    elseif ($WarningAlgorithm -contains $Certificate.SignatureAlgorithm) {
                        Write-Verbose "Certificate uses warning algorithm."
                        $AlgorithmStatus = 'Warning'
                        $AlgorithmStatusMessage = "Certificate uses the deprecated algorithm $($Certificate.SignatureAlgorithm)."
                        }
                    else {
                        Write-Verbose "Certificate uses acceptable algorithm."
                        $AlgorithmStatus = 'OK'
                        $AlgorithmStatusMessage = "Certificate uses valid algorithm $($Certificate.SignatureAlgorithm)."
                        }
                    #endregion

                    #region Check MinimumKeySize
                    Write-Verbose 'Checking minimum key length.'
                    if ($Certificate.KeySize -lt $CriticalKeySize) {
                        # Key Size is critical
                        Write-Verbose 'Certificate key length is critical.'
                        $KeySizeStatus = 'Critical'
                        $KeySizeStatusMessage = "Certificate key size $($Certificate.KeySize) is less than $CriticalKeySize."
                        }
                    elseif ($Certificate.KeySize -lt $WarningKeySize -and $Certificate.KeySize -ge $CriticalKeySize) {
                        # Key Size is warning
                        Write-Verbose 'Certificate key length is warning.'
                        $KeySizeStatus = 'Warning'
                        $KeySizeStatusMessage = "Certificate key size $($Certificate.KeySize) is less than $WarningKeySize."
                        }
                    elseif ($Certificate.KeySize -ge $WarningKeySize) {
                        # Key Size is OK
                        Write-Verbose 'Certificate key length is OK.'
                        $KeySizeStatus = 'OK'
                        $KeySizeStatusMessage = "Certificate key size $($Certificate.KeySize) is greater than or equal to $WarningKeySize."
                        }
                    else {
                        # Key Size is OK
                        Write-Verbose 'Certificate key length is Unknown.'
                        $KeySizeStatus = 'Unknown'
                        $KeySizeStatusMessage = "Certificate key size is unknown."
                        }
                    #endregion

                    Write-Verbose 'Adding additional properties to the certificate object.'
                    if ($PSVersionTable.PSVersion.Major -lt 3) {
                        $CertificateProperties = @{
                            FileName = $Certificate.Filename
                            Subject = $Certificate.Subject
                            SignatureAlgorithm = $Certificate.SignatureAlgorithm
                            NotBefore = $Certificate.NotBefore
                            NotAfter = $Certificate.NotAfter
                            Days = $Certificate.Days
                            Thumbprint = $Certificate.Thumbprint
                            ValidityPeriodStatus = $ValidityPeriodStatus
                            ValidityPeriodStatusMessage = $ValidityPeriodStatusMessage
                            AlgorithmStatus = $AlgorithmStatus
                            AlgorithmStatusMessage = $AlgorithmStatusMessage
                            KeySize = $Certificate.KeySize
                            KeySizeStatus = $KeySizeStatus
                            KeySizeStatusMessage = $KeySizeStatusMessage
                            }
                        }
                    else {
                        $CertificateProperties = [ordered]@{
                            FileName = $Certificate.Filename
                            Subject = $Certificate.Subject
                            SignatureAlgorithm = $Certificate.SignatureAlgorithm
                            NotBefore = $Certificate.NotBefore
                            NotAfter = $Certificate.NotAfter
                            Days = $Certificate.Days
                            Thumbprint = $Certificate.Thumbprint
                            ValidityPeriodStatus = $ValidityPeriodStatus
                            ValidityPeriodStatusMessage = $ValidityPeriodStatusMessage
                            AlgorithmStatus = $AlgorithmStatus
                            AlgorithmStatusMessage = $AlgorithmStatusMessage
                            KeySize = $Certificate.KeySize
                            KeySizeStatus = $KeySizeStatus
                            KeySizeStatusMessage = $KeySizeStatusMessage
                            }
                        }
                    
                    $Certificate = New-Object -TypeName PSObject -Property $CertificateProperties
                    if ($PSVersionTable.PSVersion.Major -lt 3) {
                        $Certificate | Select-Object FileName,Subject,SignatureAlgorithm,NotBefore,NotAfter,Days,Thumbprint,ValidityPeriodStatus,ValidityPeriodStatusMessage,AlgorithmStatus,AlgorithmStatusMessage,KeySize,KeySizeStatus,KeySizeStatusMessage
                        }
                    else {
                        $Certificate
                        }
                    }
                #endregion
                }
            else {
                Write-Verbose "No Certificates found in $CertPath"
                }
            }
        #endregion
    }
    End
    {
    }
}