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

+ + + + + + + + +"@ + foreach ($AppClientSecretsDetail in $AppClientSecretsDetails) { + $htmlReport += @" + + + + + + + $(switch ($AppClientSecretsDetail.Status) { + 'ERROR' {""} + 'WARNING' {""} + default {""} + } ) + +"@ + } + $htmlReport += "
App DisplayNameSecret NameEnddateDays RemainingStatus
$($AppClientSecretsDetail.AppDisplayName)$($AppClientSecretsDetail.SecretName)$($AppClientSecretsDetail.Enddate)$($AppClientSecretsDetail.DaysRemaining)$($AppClientSecretsDetail.Status)$($AppClientSecretsDetail.Status)$($AppClientSecretsDetail.Status)
" + } + $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

@@ -195,7 +216,6 @@ $htmlReport = @" "@ foreach ($AppClientSecretsDetail in $AppClientSecretsDetails) { $htmlReport += @" - @@ -207,6 +227,34 @@ $htmlReport = @" default {""} } ) +"@ + } + $htmlReport += "
App DisplayName
$($AppClientSecretsDetail.AppDisplayName) $($AppClientSecretsDetail.SecretName)$($AppClientSecretsDetail.Status)
" + } + if ("" -ne $AppCertificateDetails) { $htmlReport += @" +

App Registration Certificates Overview

+ + + + + + + + +"@ + foreach ($AppCertificateDetail in $AppCertificateDetails) { + $htmlReport += @" + + + + + + $(switch ($AppCertificateDetail.Status) { + 'ERROR' {""} + 'WARNING' {""} + default {""} + } ) + "@ } $htmlReport += "
App DisplayNameCertificate NameEnddateDays RemainingStatus
$($AppCertificateDetail.AppDisplayName)$($AppCertificateDetail.CertificateName)$($AppCertificateDetail.Enddate)$($AppCertificateDetail.DaysRemaining)$($AppCertificateDetail.Status)$($AppCertificateDetail.Status)$($AppCertificateDetail.Status)
" @@ -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 } }