mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
added documetation
This commit is contained in:
@@ -1,78 +1,357 @@
|
||||
#Connect-AzAccount
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Inventories and monitors Azure App Service certificates across all enabled subscriptions.
|
||||
|
||||
$fileName = ".\2020-12-23 azure_appservice_certificates (3).csv"
|
||||
.DESCRIPTION
|
||||
This script performs a comprehensive audit of all Azure App Service certificates across all
|
||||
enabled subscriptions in your Azure tenant. It extracts certificate details including expiration
|
||||
dates, thumbprints, subject names, and calculates the remaining days until expiration.
|
||||
|
||||
The script is designed for:
|
||||
- Certificate lifecycle management and monitoring
|
||||
- Proactive identification of expiring certificates
|
||||
- Compliance auditing and reporting
|
||||
- Security assessments of certificate inventory
|
||||
- Planning certificate renewal activities
|
||||
|
||||
The script processes all enabled subscriptions automatically and exports results to a timestamped
|
||||
CSV file, making it suitable for automated monitoring and reporting workflows.
|
||||
|
||||
Key features:
|
||||
- Multi-subscription certificate discovery
|
||||
- Expiration date calculation with days remaining
|
||||
- Error handling for inaccessible or invalid certificates
|
||||
- Detailed logging and progress reporting
|
||||
- CSV export for further analysis and alerting
|
||||
|
||||
$subscriptions = Get-AzSubscription | Where-Object State -eq "Enabled"
|
||||
.PARAMETER None
|
||||
This script does not accept parameters and processes all enabled subscriptions automatically.
|
||||
|
||||
.EXAMPLE
|
||||
.\Certificates.ps1
|
||||
|
||||
Runs the certificate inventory across all enabled subscriptions and exports results to a
|
||||
timestamped CSV file in the current directory.
|
||||
|
||||
.EXAMPLE
|
||||
# Schedule for automated monitoring
|
||||
$scriptPath = "C:\Scripts\Certificates.ps1"
|
||||
& $scriptPath
|
||||
|
||||
Executes the script from a scheduled task or automation workflow for regular certificate monitoring.
|
||||
|
||||
.EXAMPLE
|
||||
# Run and immediately view results
|
||||
.\Certificates.ps1
|
||||
Get-Content ".\$(Get-Date -Format 'yyyy-MM-dd HHmm') azure_appservice_certificates.csv"
|
||||
|
||||
Runs the script and displays the generated CSV content for immediate review.
|
||||
|
||||
.NOTES
|
||||
Author: Cloud Engineering Team
|
||||
Version: 1.0
|
||||
|
||||
Prerequisites:
|
||||
- Azure PowerShell module (Az) must be installed
|
||||
- User must be authenticated to Azure (Connect-AzAccount)
|
||||
- User must have at least 'Reader' permissions across target subscriptions
|
||||
- Access to Microsoft.Web/certificates resources
|
||||
|
||||
Required Permissions:
|
||||
- Reader access to subscriptions containing App Service certificates
|
||||
- Web App Certificate Reader or App Service Certificate Reader permissions
|
||||
- Resource Group Reader permissions for certificate resource groups
|
||||
|
||||
Output File:
|
||||
- Format: "YYYY-MM-DD HHMM azure_appservice_certificates.csv"
|
||||
- Location: Current directory
|
||||
- Content: Certificate inventory with expiration analysis
|
||||
|
||||
Certificate Status Analysis:
|
||||
- TotalDays > 30: Certificate is healthy
|
||||
- TotalDays 7-30: Certificate expires soon (warning)
|
||||
- TotalDays < 7: Certificate expires very soon (critical)
|
||||
- TotalDays < 0: Certificate has already expired (urgent action required)
|
||||
|
||||
Performance Considerations:
|
||||
- Processing time depends on the number of subscriptions and certificates
|
||||
- Large tenants with many certificates may require extended execution time
|
||||
- Network latency affects certificate detail retrieval
|
||||
|
||||
Security and Compliance:
|
||||
- Certificate thumbprints and subject names are included in output
|
||||
- Ensure proper access controls on generated CSV files
|
||||
- Consider encryption for sensitive certificate inventory data
|
||||
- Regular execution recommended for proactive certificate management
|
||||
|
||||
Common Use Cases:
|
||||
- Monthly certificate expiration reports
|
||||
- Pre-renewal planning and notifications
|
||||
- Compliance audits requiring certificate inventory
|
||||
- Security assessments of certificate lifecycle management
|
||||
|
||||
.LINK
|
||||
https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate
|
||||
https://docs.microsoft.com/en-us/powershell/module/az.websites/
|
||||
#>
|
||||
|
||||
# Ensure user is authenticated to Azure
|
||||
# Uncomment the following line if authentication is needed:
|
||||
# Connect-AzAccount
|
||||
|
||||
# Display script header and configuration
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Azure App Service Certificate Inventory and Monitoring"
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Starting certificate discovery across all enabled subscriptions..."
|
||||
Write-Host "Script execution started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
Write-Host ""
|
||||
|
||||
# Generate timestamped filename for export
|
||||
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
|
||||
$fileName = ".\$date azure_appservice_certificates.csv"
|
||||
Write-Host "Export file: $fileName"
|
||||
Write-Host ""
|
||||
|
||||
# Get all enabled subscriptions for processing
|
||||
Write-Host "Retrieving enabled subscriptions..."
|
||||
try {
|
||||
$subscriptions = Get-AzSubscription | Where-Object State -eq "Enabled"
|
||||
Write-Host "✓ Found $($subscriptions.Count) enabled subscription(s) to process:"
|
||||
foreach ($sub in $subscriptions) {
|
||||
Write-Host " - $($sub.Name) ($($sub.Id))"
|
||||
}
|
||||
} catch {
|
||||
Write-Error "Failed to retrieve Azure subscriptions. Please ensure you are authenticated (Connect-AzAccount)"
|
||||
throw $_
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Define certificate information class for structured data collection
|
||||
class CertificateCheck {
|
||||
# Azure subscription identifier containing the certificate
|
||||
[string] $SubscriptionId = ""
|
||||
|
||||
# Full Azure resource ID of the certificate
|
||||
[string] $CertificateId = ""
|
||||
|
||||
# Resource group name where the certificate is deployed
|
||||
[string] $ResourceGroupName = ""
|
||||
|
||||
# Certificate subject name (Common Name and additional fields)
|
||||
[string] $SubjectName = ""
|
||||
|
||||
# Certificate thumbprint (SHA-1 hash identifier)
|
||||
[string] $ThumbPrint = ""
|
||||
|
||||
# Certificate expiration date and time
|
||||
[DateTime] $ExpirationDate
|
||||
|
||||
# Number of days remaining until expiration (negative if expired)
|
||||
[double] $TotalDays
|
||||
|
||||
# Certificate health status (Expired, Critical, Warning, Healthy, Error)
|
||||
[string] $Health = ""
|
||||
|
||||
# Error messages or status comments for problematic certificates
|
||||
[string] $Comment = ""
|
||||
}
|
||||
|
||||
# Initialize result collection and processing variables
|
||||
[CertificateCheck[]]$Result = @()
|
||||
$StartDate = (Get-Date)
|
||||
$totalCertificates = 0
|
||||
$processedSubscriptions = 0
|
||||
$certificatesWithIssues = 0
|
||||
|
||||
$StartDate=(GET-DATE)
|
||||
[CertificateCheck[]]$Result = @()
|
||||
foreach ($subscription in $subscriptions)
|
||||
{
|
||||
Set-AzContext -SubscriptionId $subscription.Id
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Processing Certificates by Subscription"
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
|
||||
$certs = Get-AzResource -ResourceType Microsoft.Web/certificates -ExpandProperties -ApiVersion 2018-02-01 | Select * -Expand Properties
|
||||
foreach ($cert in $certs)
|
||||
{
|
||||
$id = $cert.Id
|
||||
# Process each enabled subscription
|
||||
foreach ($subscription in $subscriptions) {
|
||||
Write-Host ""
|
||||
Write-Host "Processing subscription: $($subscription.Name) ($($subscription.Id))"
|
||||
|
||||
try {
|
||||
# Set Azure context to current subscription
|
||||
Set-AzContext -SubscriptionId $subscription.Id -ErrorAction Stop | Out-Null
|
||||
Write-Host "✓ Successfully connected to subscription"
|
||||
|
||||
[CertificateCheck] $certificateCheck = [CertificateCheck]::new()
|
||||
|
||||
$certificateCheck.SubscriptionId = $subscription.Id
|
||||
$certificateCheck.CertificateId = $id
|
||||
$certificateCheck.ThumbPrint = $cert.Properties.thumbprint
|
||||
$certificateCheck.ResourceGroupName = $cert.ResourceGroupName
|
||||
|
||||
try
|
||||
{
|
||||
$thumbprint = $certificateCheck.ThumbPrint
|
||||
|
||||
$certificate = Get-AzWebAppCertificate -ResourceGroupName $certificateCheck.ResourceGroupName -Thumbprint $thumbprint -debug -verbose
|
||||
|
||||
if ($null -eq $certificate)
|
||||
{
|
||||
$certificateCheck.Comment = "Could not find certificate"
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
$subjectname = $certificate.SubjectName
|
||||
$certificateCheck.SubjectName = $subjectname
|
||||
|
||||
Write-Host "Subject name: $subjectname"
|
||||
|
||||
$EndDate=[datetime]$certificate.ExpirationDate
|
||||
$certificateCheck.ExpirationDate = $EndDate
|
||||
$span = NEW-TIMESPAN –Start $StartDate –End $EndDate
|
||||
$certificateCheck.TotalDays = $span.TotalDays
|
||||
}
|
||||
catch {
|
||||
$certificateCheck.Comment = "Could not find expiry for certificate"
|
||||
# Retrieve all App Service certificates in the subscription
|
||||
Write-Host "Discovering App Service certificates..."
|
||||
$certs = Get-AzResource -ResourceType Microsoft.Web/certificates -ApiVersion "2018-02-01" -ExpandProperties | Select-Object * -ExpandProperty Properties
|
||||
|
||||
if ($certs) {
|
||||
Write-Host "✓ Found $($certs.Count) certificate(s) in subscription"
|
||||
$subscriptionCertCount = 0
|
||||
|
||||
# Process each certificate found
|
||||
foreach ($cert in $certs) {
|
||||
$id = $cert.Id
|
||||
Write-Host " Processing certificate: $($cert.Name)"
|
||||
|
||||
# Create new certificate check instance
|
||||
[CertificateCheck] $certificateCheck = [CertificateCheck]::new()
|
||||
|
||||
# Populate basic certificate information
|
||||
$certificateCheck.SubscriptionId = $subscription.Id
|
||||
$certificateCheck.CertificateId = $id
|
||||
$certificateCheck.ThumbPrint = $cert.Properties.thumbprint
|
||||
$certificateCheck.ResourceGroupName = $cert.ResourceGroupName
|
||||
|
||||
try {
|
||||
$thumbprint = $certificateCheck.ThumbPrint
|
||||
|
||||
# Retrieve detailed certificate information
|
||||
$certificate = Get-AzWebAppCertificate -ResourceGroupName $certificateCheck.ResourceGroupName -Thumbprint $thumbprint -ErrorAction Stop
|
||||
|
||||
if ($null -eq $certificate) {
|
||||
$certificateCheck.Health = "Error"
|
||||
$certificateCheck.Comment = "Could not find certificate details"
|
||||
$certificatesWithIssues++
|
||||
Write-Host " ⚠ Warning: Certificate details not accessible"
|
||||
} else {
|
||||
try {
|
||||
# Extract certificate subject name and expiration details
|
||||
$subjectname = $certificate.SubjectName
|
||||
$certificateCheck.SubjectName = $subjectname
|
||||
|
||||
Write-Host " ✓ Subject: $subjectname"
|
||||
|
||||
# Calculate expiration and days remaining
|
||||
$EndDate = [datetime]$certificate.ExpirationDate
|
||||
$certificateCheck.ExpirationDate = $EndDate
|
||||
$span = New-TimeSpan -Start $StartDate -End $EndDate
|
||||
$certificateCheck.TotalDays = [Math]::Round($span.TotalDays, 1)
|
||||
|
||||
# Determine and assign health status based on expiration
|
||||
if ($certificateCheck.TotalDays -lt 0) {
|
||||
$certificateCheck.Health = "Expired"
|
||||
Write-Host " 🔴 EXPIRED: $([Math]::Abs($certificateCheck.TotalDays)) days ago" -ForegroundColor Red
|
||||
$certificatesWithIssues++
|
||||
} elseif ($certificateCheck.TotalDays -lt 7) {
|
||||
$certificateCheck.Health = "Critical"
|
||||
Write-Host " 🟠 CRITICAL: Expires in $($certificateCheck.TotalDays) days" -ForegroundColor Yellow
|
||||
$certificatesWithIssues++
|
||||
} elseif ($certificateCheck.TotalDays -lt 30) {
|
||||
$certificateCheck.Health = "Warning"
|
||||
Write-Host " 🟡 WARNING: Expires in $($certificateCheck.TotalDays) days" -ForegroundColor Yellow
|
||||
} else {
|
||||
$certificateCheck.Health = "Healthy"
|
||||
Write-Host " ✓ Healthy: Expires in $($certificateCheck.TotalDays) days"
|
||||
}
|
||||
} catch {
|
||||
$certificateCheck.Health = "Error"
|
||||
$certificateCheck.Comment = "Could not determine expiration date"
|
||||
$certificatesWithIssues++
|
||||
Write-Host " ⚠ Warning: Could not determine expiration date"
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
$certificateCheck.Health = "Error"
|
||||
$certificateCheck.Comment = "Could not load certificate details"
|
||||
$certificatesWithIssues++
|
||||
Write-Host " ❌ Error: Could not load certificate details"
|
||||
}
|
||||
|
||||
# Add certificate to results collection
|
||||
$Result += $certificateCheck
|
||||
$totalCertificates++
|
||||
$subscriptionCertCount++
|
||||
}
|
||||
|
||||
Write-Host " ✓ Processed $subscriptionCertCount certificate(s) in subscription"
|
||||
} else {
|
||||
Write-Host " ℹ No App Service certificates found in this subscription"
|
||||
}
|
||||
catch
|
||||
{
|
||||
$certificateCheck.Comment = "Could not load certificate"
|
||||
}
|
||||
|
||||
$Result += $certificateCheck
|
||||
|
||||
$processedSubscriptions++
|
||||
|
||||
} catch {
|
||||
Write-Host " ❌ Error processing subscription: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host " Please verify permissions and subscription access"
|
||||
}
|
||||
}
|
||||
|
||||
$Result | Export-Csv -Path $fileName -NoTypeInformation -Force
|
||||
Write-Host ""
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Exporting Results and Analysis"
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
|
||||
$Result | ft
|
||||
# Export results to CSV file
|
||||
Write-Host "Exporting certificate inventory to CSV file..."
|
||||
try {
|
||||
$Result | Export-Csv -Path $fileName -NoTypeInformation -Force
|
||||
Write-Host "✓ Successfully exported $($Result.Count) certificate records to: $fileName"
|
||||
} catch {
|
||||
Write-Error "Failed to export results to CSV file: $($_.Exception.Message)"
|
||||
throw $_
|
||||
}
|
||||
|
||||
# Display results summary table
|
||||
Write-Host ""
|
||||
Write-Host "Certificate Inventory Summary:"
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
$Result | Format-Table -AutoSize
|
||||
|
||||
# Generate detailed analysis and statistics
|
||||
Write-Host ""
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Certificate Analysis Summary"
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Execution completed: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
|
||||
Write-Host ""
|
||||
Write-Host "Processing Statistics:"
|
||||
Write-Host " Subscriptions processed: $processedSubscriptions"
|
||||
Write-Host " Total certificates discovered: $totalCertificates"
|
||||
Write-Host " Certificates with issues: $certificatesWithIssues"
|
||||
Write-Host ""
|
||||
|
||||
# Analyze certificate expiration status using Health property
|
||||
if ($Result.Count -gt 0) {
|
||||
$expiredCerts = $Result | Where-Object { $_.Health -eq "Expired" }
|
||||
$criticalCerts = $Result | Where-Object { $_.Health -eq "Critical" }
|
||||
$warnCerts = $Result | Where-Object { $_.Health -eq "Warning" }
|
||||
$healthyCerts = $Result | Where-Object { $_.Health -eq "Healthy" }
|
||||
$errorCerts = $Result | Where-Object { $_.Health -eq "Error" }
|
||||
|
||||
Write-Host "Certificate Status Analysis:"
|
||||
Write-Host " 🔴 Expired certificates: $($expiredCerts.Count)"
|
||||
Write-Host " 🟠 Critical (< 7 days): $($criticalCerts.Count)"
|
||||
Write-Host " 🟡 Warning (7-30 days): $($warnCerts.Count)"
|
||||
Write-Host " ✓ Healthy (> 30 days): $($healthyCerts.Count)"
|
||||
Write-Host " ❌ Error/Inaccessible: $($errorCerts.Count)"
|
||||
Write-Host ""
|
||||
|
||||
# Display urgent action items
|
||||
if ($expiredCerts.Count -gt 0 -or $criticalCerts.Count -gt 0) {
|
||||
Write-Host "🚨 URGENT ACTION REQUIRED:"
|
||||
if ($expiredCerts.Count -gt 0) {
|
||||
Write-Host " - $($expiredCerts.Count) certificate(s) have already expired"
|
||||
}
|
||||
if ($criticalCerts.Count -gt 0) {
|
||||
Write-Host " - $($criticalCerts.Count) certificate(s) expire within 7 days"
|
||||
}
|
||||
Write-Host " Review the CSV file for detailed certificate information"
|
||||
}
|
||||
|
||||
if ($warnCerts.Count -gt 0) {
|
||||
Write-Host "⚠ RENEWAL PLANNING NEEDED:"
|
||||
Write-Host " - $($warnCerts.Count) certificate(s) expire within 30 days"
|
||||
}
|
||||
} else {
|
||||
Write-Host "ℹ No certificates found across all processed subscriptions"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Output File Information:"
|
||||
Write-Host " File Path: $fileName"
|
||||
Write-Host " File Size: $([Math]::Round((Get-Item $fileName).Length / 1KB, 2)) KB"
|
||||
Write-Host ""
|
||||
Write-Host "Recommendations:"
|
||||
Write-Host " - Schedule regular execution for proactive certificate monitoring"
|
||||
Write-Host " - Set up alerts for certificates expiring within 30 days"
|
||||
Write-Host " - Implement automated renewal processes where possible"
|
||||
Write-Host " - Review and resolve any certificates with error status"
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Reference in New Issue
Block a user