From a1a97af7e503a4761875b5874c7f8511f16be0e8 Mon Sep 17 00:00:00 2001
From: Danny de Kooker
Date: Mon, 17 Feb 2025 16:27:07 +0100
Subject: [PATCH 1/4] initial version
---
Azure/Azure-App-Expiration.ps1 | 254 +++++++++++++++++++++++++++++++++
1 file changed, 254 insertions(+)
create mode 100644 Azure/Azure-App-Expiration.ps1
diff --git a/Azure/Azure-App-Expiration.ps1 b/Azure/Azure-App-Expiration.ps1
new file mode 100644
index 0000000..e839f1b
--- /dev/null
+++ b/Azure/Azure-App-Expiration.ps1
@@ -0,0 +1,254 @@
+<#
+.SYNOPSIS
+ This script generates an report with all the App Secrets of the Azure applications
+
+.DESCRIPTION
+ This PowerShell script connects to the Microsoft Graph API, retreives all the Applications that are configured in the Azure Tenant and make use of an secret
+ with a expiration date. Finaly the script will create a report based on the information collected by The GraphAPI.
+
+.PARAMETER None
+ This script does not require any parameters.
+
+.EXAMPLE
+ .\Azure-App-Expiration.ps1
+
+.NOTES
+ This script is intended for use in a test or production environment. Make sure to test the script in a non-production environment before running it in production.
+ Author: D.de Kooker - info@dcomputers.nl
+
+ DISCLAIMER: Use scripts at your own risk, if there is anything I can help you with I will try but I do not take responsibility for the way that anyone else uses my scripts.
+ Sharing is caring. Share your knowledge with the world so that everybody can learn from it.
+
+.LINK
+ The latest version can Always be found on my GIT page on the link below:
+ https://git.dcomputers.nl/Dcomputers/PowershellScripts
+#>
+
+#region Global script settings and variables
+ #General
+ $Version = "v0.1"
+ $logfilelocation = "$($MyInvocation.MyCommand.Path | Split-Path -Parent)\Logs"
+ $logfilename = "$(Get-Date -Format yyyyMMddHHmmss)-Azure-App-Expiration-Report.log"
+ $summaryfilename = "$(Get-Date -Format yyyyMMddHHmmss)-Azure-App-Expiration-Summary.txt"
+ $WarningDays = "31" #Specify the amount of days for a warning status
+
+ #Azure Enterprise app configuration
+ $STR_TenantID = ""
+ $STR_AppID = ""
+ $STR_ClientSecret = ""
+
+ #Email report settings
+ $STR_SMTPServer = ""
+ $STR_SMTPServerPort = ""
+ $STR_SMTPUsername = ""
+ $STR_SMTPPassword = ""
+ $STR_EmailSubject= "Azure App expiration report - $(Get-Date -Format "dd-MM-yyyy")"
+ $STR_SMTPFromaddress = "Servicedesk ICT "
+ $STR_Receivers = "servicedesk@contoso.com,systemengineer1@contoso.com" #List of commaseperated emailaddresses
+#endregion
+
+#region functions
+ function SendMailv2 ($To,$Subject,$Body){
+ $SMTPClient = New-Object Net.Mail.SmtpClient($STR_SMTPServer, $STR_SMTPServerPort)
+ # $SMTPClient.EnableSsl = $true
+ $SMTPClient.Credentials = New-Object System.Net.NetworkCredential($STR_SMTPUsername, $STR_SMTPPassword);
+ $SMTPMessage = New-Object System.Net.Mail.MailMessage($STR_SMTPFromaddress,$To,$Subject,$Body)
+ $SMTPMessage.IsBodyHTML = $true
+ $SMTPClient.Send($SMTPMessage)
+ }
+ function Initiate-Log {
+ # Get current user and session information
+ $username = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
+ $computerName = $env:COMPUTERNAME
+ $sessionID = $pid
+ $date = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
+
+ # Write log header
+ $logHeader = "[$date] Log initiated by $username on $computerName (Session ID: $sessionID)"
+ Add-Content -Path $logfilelocation\$logfilename -Value "**********************"
+ Add-Content -Path $logfilelocation\$logfilename -Value "LogFile initiation"
+ Add-Content -Path $logfilelocation\$logfilename -Value "Start time: $date"
+ Add-Content -Path $logfilelocation\$logfilename -Value "Username: $username"
+ Add-Content -Path $logfilelocation\$logfilename -Value "Machine: $computerName"
+ Add-Content -Path $logfilelocation\$logfilename -Value "Process ID: $sessionID"
+ Add-Content -Path $logfilelocation\$logfilename -Value "Script Version: $Version"
+ Add-Content -Path $logfilelocation\$logfilename -Value "Script Source: https://git.dcomputers.nl/Dcomputers/PowershellScripts"
+ Add-Content -Path $logfilelocation\$logfilename -Value "**********************"
+ }
+
+ function Write-Log {
+ [CmdletBinding()]
+ Param (
+ [Parameter(Mandatory=$true)]
+ [string]$Message,
+ [Parameter(Mandatory=$false)]
+ [ValidateSet("INFO", "WARNING", "ERROR")]
+ [string]$Level = "INFO"
+ )
+ $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
+ $logmessage = "[$timestamp] [$Level] $Message"
+ Add-Content -Path $logfilelocation\$logfilename -Value $logmessage
+ }
+
+ function Write-Summary {
+ [CmdletBinding()]
+ Param (
+ [Parameter(Mandatory=$true)]
+ [string]$Message
+ )
+ Add-Content -Path $logfilelocation\$summaryfilename -Value $Message
+ }
+#endregion
+
+#region prerequisites check
+ #Create log directory if not present and initiate logfile
+ if (!(test-path $logfilelocation)) {mkdir $logfilelocation}
+ Initiate-Log
+
+ #Check if the required Powershell Modules are available
+ $modules = @("Microsoft.Graph")
+ foreach ($module in $modules) {
+ if (!(Get-Module -Name $module -ListAvailable)) {
+ Write-Host "The $module module is not installed. Please install it and try again."
+ Write-Log -Message "The $module module is not installed. Please install it and try again." -Level ERROR
+ exit 1
+ }
+ }
+
+ #Setup MSGraph connection
+ $ClientSecretPass = ConvertTo-SecureString -String $STR_ClientSecret -AsPlainText -Force
+ $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $STR_AppID, $ClientSecretPass
+ Connect-MgGraph -TenantId $STR_TenantID -ClientSecretCredential $ClientSecretCredential -NoWelcome
+ Write-Log -Message "Connected to MsGraph API" -Level INFO
+#endregion
+
+#region execute script
+ #Collect all app information
+ $AzureADApps = Get-MgApplication | Sort-Object DisplayName
+ $AppCertificateDetails = @() #Initiate the array to store the collected information
+ $AppClientSecretsDetails = @() #Initiate the array to store the collected information
+
+ foreach ($App in $AzureADApps) {
+ #Script should be extended to also include Certificates, preparations are already made.
+ #Collect Client Secret details if available
+ if ($null -ne $App.PasswordCredentials) {
+ foreach ($PasswordCredential in $App.PasswordCredentials) {
+ #Calculate remaining days
+ $RemainingDays = New-TimeSpan -Start $(Get-Date) -End $PasswordCredential.EndDateTime
+ $DaysRemaining = $RemainingDays.Days
+ switch ($DaysRemaining) {
+ {$_ -le '0'} {$CalculatedStatus = "ERROR"}
+ {$_ -le $WarningDays} {$CalculatedStatus = "WARNING"}
+ Default {$CalculatedStatus = "OK"}
+ }
+
+ $AppClientSecretsDetails += [PSCustomObject]@{
+ AppDisplayName = $App.DisplayName
+ SecretName = $PasswordCredential.Displayname
+ Enddate = $PasswordCredential.EndDateTime
+ DaysRemaining = $DaysRemaining
+ Status = $CalculatedStatus
+ }
+ }
+ }
+ }
+#endregion
+
+# Create an HTML report
+$htmlReport = @"
+
+
+
+
+
+
+ Azure App expiration report - $(Get-Date -Format "dd-MM-yyyy - HH:mm")
+ Script version: $Version
+"@
+ if ("" -ne $AppClientSecretsDetails) { $htmlReport += @"
+ Application Secrets Overview
+
+
+ | App DisplayName |
+ Secret Name |
+ Enddate |
+ Days Remaining |
+ Status |
+
+"@
+ foreach ($AppClientSecretsDetail in $AppClientSecretsDetails) {
+ $htmlReport += @"
+
+
+ | $($AppClientSecretsDetail.AppDisplayName) |
+ $($AppClientSecretsDetail.SecretName) |
+ $($AppClientSecretsDetail.Enddate) |
+ $($AppClientSecretsDetail.DaysRemaining) |
+ $(switch ($AppClientSecretsDetail.Status) {
+ 'ERROR' {"$($AppClientSecretsDetail.Status) | "}
+ 'WARNING' {"$($AppClientSecretsDetail.Status) | "}
+ default {"$($AppClientSecretsDetail.Status) | "}
+ } )
+
+"@
+ }
+ $htmlReport += "
"
+ }
+ $htmlReport += @"
+ This is an automated report.
+
+
+
+"@
+#endregion
+
+#region send reports and generate summary report
+ # Send the report via email
+ $Status = "OK"
+ switch ($licenseUsageData | Select-Object -ExpandProperty LicenseStatus) {
+ { $_ -contains "ERROR" } { $Status = "ERROR"; break }
+ { $_ -contains "WARNING" } { $Status = "WARNING"; break }
+ }
+ if ($Status -ne "OK") {
+ $subject = "$($Status): $STR_EmailSubject"
+ }
+ else {
+ $subject = "$STR_EmailSubject"
+ }
+ $body = $htmlReport
+ SendMailv2 -To $STR_Receivers -Subject $subject -Body $body
+
+ # write summary
+ Write-Summary "Azure App Secrets expiration report Summary:"
+ Write-Summary "---------------------------"
+ Write-Summary "Report date: $(Get-Date -Format "dd-MM-yyy HH:mm:ss")"
+ if ("" -ne $AppClientSecretsDetails) {
+ Write-Summary "App Client Secrets"
+ foreach ($AppClientSecretsDetail in $AppClientSecretsDetails) {
+ Write-Summary "******************"
+ Write-Summary "App DisplayName: $($AppClientSecretsDetail.AppDisplayName)"
+ Write-Summary "Secret Name: $($AppClientSecretsDetail.SecretName)"
+ Write-Summary "Enddate: $($AppClientSecretsDetail.Enddate)"
+ Write-Summary "Days Remaining: $($AppClientSecretsDetail.DaysRemaining)"
+ Write-Summary "Status: $($AppClientSecretsDetail.Status)"
+ }
+ }
+ Write-Summary "---------------------------"
+#endregion
\ No newline at end of file
From 81504cde36eeb24a655185131024fe4e4c993d3c Mon Sep 17 00:00:00 2001
From: Danny de Kooker
Date: Mon, 17 Feb 2025 16:44:39 +0100
Subject: [PATCH 2/4] Update to v1.0 after testing
---
Azure/Azure-App-Expiration.ps1 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Azure/Azure-App-Expiration.ps1 b/Azure/Azure-App-Expiration.ps1
index e839f1b..ffbf7c0 100644
--- a/Azure/Azure-App-Expiration.ps1
+++ b/Azure/Azure-App-Expiration.ps1
@@ -26,7 +26,7 @@
#region Global script settings and variables
#General
- $Version = "v0.1"
+ $Version = "v1.0"
$logfilelocation = "$($MyInvocation.MyCommand.Path | Split-Path -Parent)\Logs"
$logfilename = "$(Get-Date -Format yyyyMMddHHmmss)-Azure-App-Expiration-Report.log"
$summaryfilename = "$(Get-Date -Format yyyyMMddHHmmss)-Azure-App-Expiration-Summary.txt"
From 503041fa0bc9ef44831ecf3e887588e15a022fd1 Mon Sep 17 00:00:00 2001
From: Danny de Kooker
Date: Mon, 17 Feb 2025 18:08:50 +0100
Subject: [PATCH 3/4] Extended script with app Registration certificates
---
Azure/Azure-App-Expiration.ps1 | 71 +++++++++++++++++++++++++++++++---
1 file changed, 65 insertions(+), 6 deletions(-)
diff --git a/Azure/Azure-App-Expiration.ps1 b/Azure/Azure-App-Expiration.ps1
index ffbf7c0..bcc99ec 100644
--- a/Azure/Azure-App-Expiration.ps1
+++ b/Azure/Azure-App-Expiration.ps1
@@ -26,7 +26,7 @@
#region Global script settings and variables
#General
- $Version = "v1.0"
+ $Version = "v1.1"
$logfilelocation = "$($MyInvocation.MyCommand.Path | Split-Path -Parent)\Logs"
$logfilename = "$(Get-Date -Format yyyyMMddHHmmss)-Azure-App-Expiration-Report.log"
$summaryfilename = "$(Get-Date -Format yyyyMMddHHmmss)-Azure-App-Expiration-Summary.txt"
@@ -123,10 +123,10 @@
#endregion
#region execute script
- #Collect all app information
- $AzureADApps = Get-MgApplication | Sort-Object DisplayName
- $AppCertificateDetails = @() #Initiate the array to store the collected information
+ #Collect all app Registration information
+ $AzureADApps = Get-MgApplication -all | Sort-Object DisplayName
$AppClientSecretsDetails = @() #Initiate the array to store the collected information
+ $AppCertificateDetails = @() #Initiate the array to store the collected information
foreach ($App in $AzureADApps) {
#Script should be extended to also include Certificates, preparations are already made.
@@ -151,6 +151,27 @@
}
}
}
+ #Collect Client Secret details if available
+ if ($null -ne $App.KeyCredentials) {
+ foreach ($KeyCredential in $App.KeyCredentials) {
+ #Calculate remaining days
+ $RemainingDays = New-TimeSpan -Start $(Get-Date) -End $KeyCredential.EndDateTime
+ $DaysRemaining = $RemainingDays.Days
+ switch ($DaysRemaining) {
+ {$_ -le '0'} {$CalculatedStatus = "ERROR"}
+ {$_ -le $WarningDays} {$CalculatedStatus = "WARNING"}
+ Default {$CalculatedStatus = "OK"}
+ }
+
+ $AppCertificateDetails += [PSCustomObject]@{
+ AppDisplayName = $App.DisplayName
+ CertificateName = $KeyCredential.Displayname
+ Enddate = $KeyCredential.EndDateTime
+ DaysRemaining = $DaysRemaining
+ Status = $CalculatedStatus
+ }
+ }
+ }
}
#endregion
@@ -183,7 +204,7 @@ $htmlReport = @"
Script version: $Version
"@
if ("" -ne $AppClientSecretsDetails) { $htmlReport += @"
- Application Secrets Overview
+ App Registration Secrets Overview
| App DisplayName |
@@ -195,7 +216,6 @@ $htmlReport = @"
"@
foreach ($AppClientSecretsDetail in $AppClientSecretsDetails) {
$htmlReport += @"
-
| $($AppClientSecretsDetail.AppDisplayName) |
$($AppClientSecretsDetail.SecretName) |
@@ -207,6 +227,34 @@ $htmlReport = @"
default {"$($AppClientSecretsDetail.Status) | "}
} )
+"@
+ }
+ $htmlReport += "
"
+ }
+ if ("" -ne $AppCertificateDetails) { $htmlReport += @"
+ App Registration Certificates Overview
+
+
+ | App DisplayName |
+ Certificate Name |
+ Enddate |
+ Days Remaining |
+ Status |
+
+"@
+ foreach ($AppCertificateDetail in $AppCertificateDetails) {
+ $htmlReport += @"
+
+ | $($AppCertificateDetail.AppDisplayName) |
+ $($AppCertificateDetail.CertificateName) |
+ $($AppCertificateDetail.Enddate) |
+ $($AppCertificateDetail.DaysRemaining) |
+ $(switch ($AppCertificateDetail.Status) {
+ 'ERROR' {"$($AppCertificateDetail.Status) | "}
+ 'WARNING' {"$($AppCertificateDetail.Status) | "}
+ default {"$($AppCertificateDetail.Status) | "}
+ } )
+
"@
}
$htmlReport += "
"
@@ -250,5 +298,16 @@ $htmlReport = @"
Write-Summary "Status: $($AppClientSecretsDetail.Status)"
}
}
+ if ("" -ne $AppCertificateDetails) {
+ Write-Summary "App Client Secrets"
+ foreach ($AppCertificateDetail in $AppCertificateDetails) {
+ Write-Summary "******************"
+ Write-Summary "App DisplayName: $($AppCertificateDetail.AppDisplayName)"
+ Write-Summary "Certificate Name: $($AppCertificateDetail.CertificateName)"
+ Write-Summary "Enddate: $($AppCertificateDetail.Enddate)"
+ Write-Summary "Days Remaining: $($AppCertificateDetail.DaysRemaining)"
+ Write-Summary "Status: $($AppCertificateDetail.Status)"
+ }
+ }
Write-Summary "---------------------------"
#endregion
\ No newline at end of file
From 1875a6083e411be9f8901e97a6899c956e88c4d0 Mon Sep 17 00:00:00 2001
From: Danny de Kooker
Date: Mon, 17 Feb 2025 18:17:51 +0100
Subject: [PATCH 4/4] Fix Email subject calculation
---
Azure/Azure-App-Expiration.ps1 | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Azure/Azure-App-Expiration.ps1 b/Azure/Azure-App-Expiration.ps1
index bcc99ec..117b112 100644
--- a/Azure/Azure-App-Expiration.ps1
+++ b/Azure/Azure-App-Expiration.ps1
@@ -270,7 +270,7 @@ $htmlReport = @"
#region send reports and generate summary report
# Send the report via email
$Status = "OK"
- switch ($licenseUsageData | Select-Object -ExpandProperty LicenseStatus) {
+ switch ($AppClientSecretsDetails.status + $AppCertificateDetails.status) {
{ $_ -contains "ERROR" } { $Status = "ERROR"; break }
{ $_ -contains "WARNING" } { $Status = "WARNING"; break }
}