diff --git a/Get-ExpiringCertificateReport.ps1 b/Get-ExpiringCertificateReport.ps1
index cfeeece..f65e2cf 100644
--- a/Get-ExpiringCertificateReport.ps1
+++ b/Get-ExpiringCertificateReport.ps1
@@ -1,73 +1,78 @@
-<#
- .SYNOPSIS
- Generate a report of exiring certificates from an Active Directory Certificate Services Certificate Authority.
-
- .DESCRIPTION
- This script checks ADCS Certificate Authorities for issued certificate requests that are expiring in the next 45 days.
- Specify a list of template names to include, and it will translate that to their OIDs, find expiring certs using those
- templates, and then send a report as directed.
-
- .PARAMETER Recipients
- To-Do: Add a function parameter to send an email to specific recipients.
-
- .PARAMETER Output
- To-Do: Add a function parameter to choose email, HTML, CSV, JSON, or console output.
-
- .INPUTS
- None. You cannot pipe objects to this script.
-
- .OUTPUTS
- Email
- HTML, CSV, JSON, or XML file
- Console
-
- .NOTES
- Author: Sam Erde
- Modified: 2023/07/21
-
- Depends on the PSPKI module at https://www.powershellgallery.com/packages/PSPKI and the AD Certificate Services RSAT feature.
-
- To Do: Add checks for prerequisites, turn into function(s), take parameters for recipients and report output type,
- get CAs in all domains in AD forest, error handling, show all template names (and optionally use Out-GridView/Out-
- ConsoleGridView to select desired templates), use OGV to generate a text file containing templates and then use
- that file as list of monitored certificate templates for expiring certificates report.
-#>
-$Version = "2023.07.21"
-$Header = @"
-
-███╗ ██╗ ██████╗ ██████╗███████╗██████╗ ████████╗
-████╗ ██║██╔═══██╗ ██╔════╝██╔════╝██╔══██╗╚══██╔══╝
-██╔██╗ ██║██║ ██║ ██║ █████╗ ██████╔╝ ██║
-██║╚██╗██║██║ ██║ ██║ ██╔══╝ ██╔══██╗ ██║
-██║ ╚████║╚██████╔╝ ╚██████╗███████╗██║ ██║ ██║
-╚═╝ ╚═══╝ ╚═════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═╝
-
-██╗ ███████╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗███╗ ██╗██████╗
+function Get-ExpiringCertificateReport {
+ <#
+ .SYNOPSIS
+ Generate a report of exiring certificates from an Active Directory Certificate Services Certificate Authority.
+
+ .DESCRIPTION
+ This script checks ADCS Certificate Authorities for issued certificate requests that are expiring in the next 45 days.
+ Specify a list of template names to include, and it will translate that to their OIDs, find expiring certs using those
+ templates, and then send a report as directed.
+
+ .PARAMETER Recipients
+ To-Do: Add a function parameter to send an email to specific recipients.
+
+ .PARAMETER Output
+ To-Do: Add a function parameter to choose email, HTML, CSV, JSON, or console output.
+
+ .INPUTS
+ None. You cannot pipe objects to this script.
+
+ .OUTPUTS
+ Email
+ HTML, CSV, JSON, or XML file
+ Console
+
+ .NOTES
+ Author: Sam Erde
+ Modified: 2023/07/21
+
+ Depends on the PSPKI module at https://www.powershellgallery.com/packages/PSPKI and the AD Certificate Services RSAT feature.
+
+ To Do: Add checks for prerequisites, turn into function(s), take parameters for recipients and report output type,
+ get CAs in all domains in AD forest, error handling, show all template names (and optionally use Out-GridView/Out-
+ ConsoleGridView to select desired templates), use OGV to generate a text file containing templates and then use
+ that file as list of monitored certificate templates for expiring certificates report.
+ #>
+ [CmdletBinding()]
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost')]
+ param ()
+
+ $Version = '2023.07.21'
+ $Header = @"
+
+███╗ ██╗ ██████╗ ██████╗███████╗██████╗ ████████╗
+████╗ ██║██╔═══██╗ ██╔════╝██╔════╝██╔══██╗╚══██╔══╝
+██╔██╗ ██║██║ ██║ ██║ █████╗ ██████╔╝ ██║
+██║╚██╗██║██║ ██║ ██║ ██╔══╝ ██╔══██╗ ██║
+██║ ╚████║╚██████╔╝ ╚██████╗███████╗██║ ██║ ██║
+╚═╝ ╚═══╝ ╚═════╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═╝
+
+██╗ ███████╗███████╗████████╗ ██████╗ ███████╗██╗ ██╗██╗███╗ ██╗██████╗
██║ ██╔════╝██╔════╝╚══██╔══╝ ██╔══██╗██╔════╝██║ ██║██║████╗ ██║██╔══██╗
██║ █████╗ █████╗ ██║ ██████╔╝█████╗ ███████║██║██╔██╗ ██║██║ ██║
██║ ██╔══╝ ██╔══╝ ██║ ██╔══██╗██╔══╝ ██╔══██║██║██║╚██╗██║██║ ██║
███████╗███████╗██║ ██║ ██████╔╝███████╗██║ ██║██║██║ ╚████║██████╔╝
-╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝
+╚══════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝
+
+v$Version
-v$Version
-
"@
-Write-Host -ForegroundColor Cyan -BackgroundColor Black $Header
+ Write-Host -ForegroundColor Cyan -BackgroundColor Black $Header
-# ════════════════════════════════════════════════════════╗
-# Modify these variables to suit your environment: ║
-# To, From, SMTPServer, DaysLeft, TemplateNamesIncluded ║
+ # ════════════════════════════════════════════════════════╗
+ # Modify these variables to suit your environment: ║
+ # To, From, SMTPServer, DaysLeft, TemplateNamesIncluded ║
-$To = @('recipient1@example.com','recipient2@example.com')
-$From = 'NoCertLeftBehind@example.com'
-$SMTPServer = 'smtp.example.com'
+ $To = @('recipient1@example.com', 'recipient2@example.com')
+ $From = 'NoCertLeftBehind@example.com'
+ $SMTPServer = 'smtp.example.com'
-# Change "DaysLeft" to whatever lead time you want for the notification of expiring certificates.
-$DaysLeft = 30
+ # Change "DaysLeft" to whatever lead time you want for the notification of expiring certificates.
+ $DaysLeft = 30
-# List the display names of the certificate templates that you want to monitor using a multi-line here-string that is converted to an array.
-# This is simply easier than typing every name in quotes and separating them with a comma.
-$TemplateNamesIncluded = (@"
+ # List the display names of the certificate templates that you want to monitor using a multi-line here-string that is converted to an array.
+ # This is simply easier than typing every name in quotes and separating them with a comma.
+ $TemplateNamesIncluded = (@'
Root Certification Authority
CA Exchange
CEP Encryption
@@ -91,137 +96,132 @@ Signature with Key Encipherment
Subordinate Certification Authority
Web Server
WSUS Signing Certificate
-"@).Split([Environment]::NewLine)
-
-# End of Customizations ║
-# ══════════════════════╝
-
-# Import required modules and Windows features
-if (Get-Module -Name 'PSPKI' -ListAvailable) {
- Write-Information 'The PSPKI module is installed.'
-} else {
- Write-Information 'The PSPKI module is not installed. Attempting installation...'
- try {
- Install-Module -Name PSPKI -AllowClobber -Scope CurrentUser -Force
- }
- catch {
- Write-Error 'PSPKI module installation failed.'
- }
-}
-if ( (Get-WindowsCapability -Online -Name 'Rsat.CertificateServices.Tools~~~~0.0.1.0').Stated -eq 'Installed') {
- Write-Information 'The Certificate Services RSAT feature is installed.'
-}
-else {
- try {
- Get-WindowsCapability -Online -Name 'Rsat.CertificateServices.Tools~~~~0.0.1.0' | Add-WindowsCapability -Online
+'@).Split([Environment]::NewLine)
+
+ # End of Customizations ║
+ # ══════════════════════╝
+
+ # Import required modules and Windows features
+ if (Get-Module -Name 'PSPKI' -ListAvailable) {
+ Write-Information 'The PSPKI module is installed.'
+ } else {
+ Write-Information 'The PSPKI module is not installed. Attempting installation...'
+ try {
+ Install-Module -Name PSPKI -AllowClobber -Scope CurrentUser -Force
+ } catch {
+ Write-Error 'PSPKI module installation failed.'
+ }
}
- catch {
- Write-Error 'Failed to install the Certificate Services RSAT feature. Please do so manually.'
+ if ( (Get-WindowsCapability -Online -Name 'Rsat.CertificateServices.Tools~~~~0.0.1.0').Stated -eq 'Installed') {
+ Write-Information 'The Certificate Services RSAT feature is installed.'
+ } else {
+ try {
+ Get-WindowsCapability -Online -Name 'Rsat.CertificateServices.Tools~~~~0.0.1.0' | Add-WindowsCapability -Online
+ } catch {
+ Write-Error 'Failed to install the Certificate Services RSAT feature. Please do so manually.'
+ }
}
-}
-# End of module installation check
+ # End of module installation check
-# ═══════════════════════════════════════════════════════════════════════════════════════╗
-# Shortcut (code snippet) variables: these make the following code simpler to work with: ║
+ # ═══════════════════════════════════════════════════════════════════════════════════════╗
+ # Shortcut (code snippet) variables: these make the following code simpler to work with: ║
-# Extract just the certificate authority's hostname from its configuration name. Used within Get-CertificateRequests below.
-$CaName = @{ Name="CA"; Expression = {$_.ConfigString.Split("\")[1]} }
+ # Extract just the certificate authority's hostname from its configuration name. Used within Get-CertificateRequests below.
+ $CaName = @{ Name = 'CA'; Expression = { $_.ConfigString.Split('\')[1] } }
-# Certificate age filter statement. Used within Get-CertificateRequests below.
-$CertAgeFilter = "NotAfter -ge $(Get-Date)", "NotAfter -le $((Get-Date).AddDays($DaysLeft))"
+ # Certificate age filter statement. Used within Get-CertificateRequests below.
+ $CertAgeFilter = "NotAfter -ge $(Get-Date)", "NotAfter -le $((Get-Date).AddDays($DaysLeft))"
-# Translate a certificate template OID to its readable display name. Used within Get-CertificateRequests below.
-$CertTemplateName = @{ Name="TemplateName"; Expression = {
- if ($_.CertificateTemplate -like "1*") {
- (Get-CertificateTemplate -OID $_.CertificateTemplate).DisplayName
- }
- else {
- $_.CertificateTemplate
+ # Translate a certificate template OID to its readable display name. Used within Get-CertificateRequests below.
+ $CertTemplateName = @{ Name = 'TemplateName'; Expression = {
+ if ($_.CertificateTemplate -like '1*') {
+ (Get-CertificateTemplate -OID $_.CertificateTemplate).DisplayName
+ } else {
+ $_.CertificateTemplate
+ }
}
- }
-} # End CertTemplateName
-
-$Domain = $([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)
+ } # End CertTemplateName
-# End of shortcut (code snippet) variables ║
-# ═════════════════════════════════════════╝
+ $Domain = $([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)
+ # End of shortcut (code snippet) variables ║
+ # ═════════════════════════════════════════╝
-# ══════════════════╗
-# Collect the data: ║
-# Find certificate authorities in the domain and get their hostname[s]
-Write-Information "Finding certificate authorities in $Domain..."
-$CANames = (Get-CA | Select-Object Computername).Computername
-if ($CANames.Count -lt 1) {
- Write-Warning "No certificate authorities were found in Active Directory."
- Break
-}
-Write-Information "Found: $CANames `n"
+ # ══════════════════╗
+ # Collect the data: ║
-# Get OIDs of all certificate templates that you want to monitor. The script takes MUCH longer when querying by template name.
-Write-Information "Getting OIDs for $($TemplateNamesIncluded.Count) certificate templates..."
-$TemplateOidsIncluded = foreach ($item in $TemplateNamesIncluded) {
- try {
- (Get-CertificateTemplate -DisplayName $item).Oid.Value
- }
- catch {
- Write-Warning "Unable to get the certificate templates. Please review the error and try again."
- Write-Error $error
+ # Find certificate authorities in the domain and get their hostname[s]
+ Write-Information "Finding certificate authorities in $Domain..."
+ $CANames = (Get-CA | Select-Object Computername).Computername
+ if ($CANames.Count -lt 1) {
+ Write-Warning 'No certificate authorities were found in Active Directory.'
Break
}
-}
+ Write-Information "Found: $CANames `n"
+
+ # Get OIDs of all certificate templates that you want to monitor. The script takes MUCH longer when querying by template name.
+ Write-Information "Getting OIDs for $($TemplateNamesIncluded.Count) certificate templates..."
+ $TemplateOidsIncluded = foreach ($item in $TemplateNamesIncluded) {
+ try {
+ (Get-CertificateTemplate -DisplayName $item).Oid.Value
+ } catch {
+ Write-Warning 'Unable to get the certificate templates. Please review the error and try again.'
+ Write-Error $error
+ Break
+ }
+ }
-# Get all relevant certificates that are expiring within the next 45 days.
-Write-Information `n"Getting certifictes that are expiring in the next $DaysLeft days from $CANames..."
-$Certificates = ( Get-IssuedRequest -CertificationAuthority $CANames -Property [Request.RequesterName], CertificateHash -Filter $CertAgeFilter ).Where(
- { $TemplateOidsIncluded -imatch $_.CertificateTemplate } ) |
- Select-Object *, $CaName, $CertTemplateName, @{Name="Thumbprint"; Expression = { $_.CertificateHash -Replace (' ','') } }
+ # Get all relevant certificates that are expiring within the next 45 days.
+ Write-Information `n"Getting certifictes that are expiring in the next $DaysLeft days from $CANames..."
+ $Certificates = ( Get-IssuedRequest -CertificationAuthority $CANames -Property [Request.RequesterName], CertificateHash -Filter $CertAgeFilter ).Where(
+ { $TemplateOidsIncluded -imatch $_.CertificateTemplate } ) |
+ Select-Object *, $CaName, $CertTemplateName, @{Name = 'Thumbprint'; Expression = { $_.CertificateHash -Replace (' ', '') } }
# In the above line, $CaName and $CertTemplateName are "shortcut snippet variables" like a function that formats or translates the desired output.
-if ($certificates.Length -eq 0) {
- Write-Information "No certificates were found to report on."
- Break
-}
-else {
- Write-Information "Found $($Certificates.Count) certificates that expire within $DaysLeft days..."
-}
+ if ($certificates.Length -eq 0) {
+ Write-Information 'No certificates were found to report on.'
+ Break
+ } else {
+ Write-Information "Found $($Certificates.Count) certificates that expire within $DaysLeft days..."
+ }
-# Done getting the certificates. ║
-# ═══════════════════════════════╝
-
-
-# ═══════════════════════════╗
-# Build and send the report: ║
-
-# Create a structured table with certificate details. Designed specifically for an HTML-based email.
-$table = [System.Data.DataTable]::New("CertificatesTable")
-@(
- "Name"
- "Expiration"
- "Identifiers"
- "Requester"
- "CA"
-) | ForEach-Object { $table.Columns.Add($_) | Out-Null }
-
-# Add each certificate to the table
-foreach ($cert in $certificates) {
- $certRow = $table.NewRow()
- $certRow.Name = "$($cert.CommonName)╗($($cert.Templatename))"
- $certRow.Expiration = $cert.NotAfter
- $certRow.Requester = $cert."Request.RequesterName"
- $certRow.Identifiers = "Serial: $($cert.SerialNumber)╗Thumbprint: $($cert.Thumbprint)╗Request ID: $($cert.RequestID)"
- $certRow.CA = $cert.CA
- $table.Rows.Add($CertRow)
-}
+ # Done getting the certificates. ║
+ # ═══════════════════════════════╝
+
+
+ # ═══════════════════════════╗
+ # Build and send the report: ║
+
+ # Create a structured table with certificate details. Designed specifically for an HTML-based email.
+ $table = [System.Data.DataTable]::New('CertificatesTable')
+ @(
+ 'Name'
+ 'Expiration'
+ 'Identifiers'
+ 'Requester'
+ 'CA'
+ ) | ForEach-Object { $table.Columns.Add($_) | Out-Null }
+
+ # Add each certificate to the table
+ foreach ($cert in $certificates) {
+ $certRow = $table.NewRow()
+ $certRow.Name = "$($cert.CommonName)╗($($cert.Templatename))"
+ $certRow.Expiration = $cert.NotAfter
+ $certRow.Requester = $cert.'Request.RequesterName'
+ $certRow.Identifiers = "Serial: $($cert.SerialNumber)╗Thumbprint: $($cert.Thumbprint)╗Request ID: $($cert.RequestID)"
+ $certRow.CA = $cert.CA
+ $table.Rows.Add($CertRow)
+ }
-$HtmlHeader = @"
+ $HtmlHeader = @'
-"@
-$PreContent = "Internally-issued certificates that will expire in the next $DaysLeft days: ╗╗"
-$EmailHtml = ($table | ConvertTo-Html -Head $HtmlHeader -PreContent $PreContent -Property Name,Expiration,Identifiers,Requester,CA | Out-String).Replace('╗','
')
-$Subject = "$Domain Certificate Expiration Report"
-Send-MailMessage -To $To -From $From -SmtpServer $SMTPServer -Subject $Subject -Body $EmailHtml -BodyAsHTML
+'@
+ $PreContent = "Internally-issued certificates that will expire in the next $DaysLeft days: ╗╗"
+ $EmailHtml = ($table | ConvertTo-Html -Head $HtmlHeader -PreContent $PreContent -Property Name, Expiration, Identifiers, Requester, CA | Out-String).Replace('╗', '
')
+ $Subject = "$Domain Certificate Expiration Report"
+ Send-MailMessage -To $To -From $From -SmtpServer $SMTPServer -Subject $Subject -Body $EmailHtml -BodyAsHtml
+}