diff --git a/Notify Entra App Credential Expiry/AppCertsAndSecretsExpiryNotification.ps1 b/Notify Entra App Credential Expiry/AppCertsAndSecretsExpiryNotification.ps1
new file mode 100644
index 0000000..1abb366
--- /dev/null
+++ b/Notify Entra App Credential Expiry/AppCertsAndSecretsExpiryNotification.ps1
@@ -0,0 +1,238 @@
+<#
+=============================================================================================
+Name: Send Microsoft Entra App Credentials Expiry Notifications
+Version: 1.0
+Website: o365reports.com
+
+Script Highlights:
+~~~~~~~~~~~~~~~~~
+1. Sends app credential expiry notifications to specific users.
+2. Sends notifications for expiring certificates alone, client secrets alone, or both.
+3. Exports a list of apps with expiring credentials within the specified days in CSV format.
+4. Allows sending emails on behalf of other users.
+5. Automatically install the Microsoft Graph PowerShell module (if not installed already) upon your confirmation.
+6. The script can be executed with an MFA-enabled account too.
+7. It can be executed with certificate-based authentication (CBA) too.
+8. The script is scheduler-friendly.
+
+
+For detailed Script execution: https://o365reports.com/2025/04/29/send-entra-app-credential-expiry-notifications
+============================================================================================
+#>
+
+Param
+(
+ [Parameter(Mandatory = $True)]
+ [int]$SoonToExpireInDays,
+ [Parameter(Mandatory = $True)]
+ [string]$Recipients,
+ [string]$FromAddress,
+ [Switch]$ClientSecretsOnly,
+ [Switch]$CertificatesOnly,
+ [Switch]$StoreReportLocally,
+ [string]$TenantId,
+ [string]$ClientId,
+ [string]$CertificateThumbprint
+)
+
+
+$Date = Get-Date
+$CSVFilePath ="$(Get-Location)\AppCertsAndSecretsExpiryNotificationSummary_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv"
+
+
+# Function to connect to Microsoft Graph
+function Connect_ToMgGraph {
+ # Check if Microsoft Graph module is installed
+ $MsGraphModule = Get-Module Microsoft.Graph -ListAvailable
+ if ($MsGraphModule -eq $null) {
+ Write-Host "`nImportant: Microsoft Graph module is unavailable. It is mandatory to have this module installed in the system to run the script successfully."
+ $confirm = Read-Host "Are you sure you want to install Microsoft Graph module? [Y] Yes [N] No"
+ if ($confirm -match "[yY]") {
+ Write-Host "Installing Microsoft Graph module..."
+ Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber
+ Write-Host "Microsoft Graph module is installed in the machine successfully" -ForegroundColor Magenta
+ } else {
+ Write-Host "Exiting. `nNote: Microsoft Graph module must be available in your system to run the script" -ForegroundColor Red
+ Exit
+ }
+ }
+
+ Write-Host "`nConnecting to Microsoft Graph..."
+
+ if (($TenantId -ne "") -and ($ClientId -ne "") -and ($CertificateThumbprint -ne "")) {
+ # Use certificate-based authentication if TenantId, ClientId, and CertificateThumbprint are provided
+ Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -NoWelcome
+ } else {
+ # Use delegated permissions (Scopes) if credentials are not provided
+ Connect-MgGraph -Scopes "Application.Read.All", "Mail.Send.Shared", "User.Read.All" -NoWelcome
+ }
+
+ # Verify connection
+ if ((Get-MgContext) -ne $null) {
+ if ((Get-MgContext).Account -ne $null) {
+ Write-Host "Connected to Microsoft Graph PowerShell using account: $((Get-MgContext).Account)"
+ }
+ else {
+ Write-Host "Connected to Microsoft Graph PowerShell using certificate-based authentication."
+ }
+ } else {
+ Write-Host "Failed to connect to Microsoft Graph." -ForegroundColor Red
+ Exit
+ }
+}
+
+
+# Function to Send Email
+function SendEmail {
+ $Script:TableContent += ""
+ $TableStyle = ""
+
+ $MailContent = "$($TableStyle)
+
Hello Admin,
+ These application credentials are soon to expire:
+ $($Script:TableContent)
+ To prevent authentication failures and service disruptions, please renew the expiring secret or certificate via the App registrations in Microsoft Entra admin center.
+ If you have any questions, feel free to contact IT support.
+ Best regards,
IT Admin Team
"
+
+ $params = @{
+ message = @{
+ subject = "Entra App Registration Credentials Expiry Notification"
+ body = @{
+ contentType = "HTML"
+ content = $MailContent
+ }
+ toRecipients = $toRecipients
+ }
+ }
+ Send-MgUserMail -UserId $FromAddress -BodyParameter $params
+}
+
+
+Connect_ToMgGraph
+$LoggedInAccount = (Get-MgContext).Account
+if ($LoggedInAccount -ne $null){
+ if ([string]::IsNullOrEmpty($FromAddress)) {
+ $FromAddress = $LoggedInAccount
+ }
+} else {
+ if ([string]::IsNullOrEmpty($FromAddress)) {
+ Write-Host "`nError: FromAddress is required when using certificate-based authentication." -ForegroundColor Red
+ Exit
+ }
+}
+
+
+$ExportResult = $null
+$AppCount = 0
+$Script:ProcessedCount = 0
+$RequiredProperties=@('DisplayName','AppId','Id','KeyCredentials','PasswordCredentials','CreatedDateTime','SigninAudience')
+
+
+if(($CertificatesOnly.IsPresent) -or ($ClientSecretsOnly.IsPresent) -or ($SoonToExpireInDays -ne "")) {
+ $SwitchPresent=$True
+}
+else {
+ $SwitchPresent=$false
+}
+
+
+# Create an HTML table with data
+$Script:TableContent = ""
+$Script:TableContent += "| App Name | App Creation Time | Credential Type | Credential Name | Creation Time | Expiry Date | Friendly Expiry Date |
"
+
+
+Get-MgApplication -All -Property $RequiredProperties | ForEach-Object {
+ $AppCount++
+ $AppName=$_.DisplayName
+ Write-Progress -Activity "`n Processed App registration: $AppCount - $AppName"
+ $AppId=$_.Id
+ $Secrets=$_.PasswordCredentials
+ $Certificates=$_.KeyCredentials
+ $AppCreationDate=$_.CreatedDateTime
+ $SigninAudience=$_.SignInAudience
+ $AppOwners=(Get-MgApplicationOwner -ApplicationId $AppId).AdditionalProperties.userPrincipalName
+ $Owners=$AppOwners -join ","
+
+ if($owners -eq "") { $Owners="-" }
+
+ $EmailAddresses = ($Recipients -split ",").Trim()
+ $toRecipients = @()
+ foreach ($Email in $EmailAddresses) {
+ $toRecipients += @{
+ emailAddress = @{
+ address = $Email
+ }
+ }
+ }
+
+ #Process through Secret keys
+ if(!($CertificatesOnly.IsPresent) -or ($SwitchPresent -eq $false)) {
+ foreach($Secret in $Secrets) {
+ $CredentialType="Client Secret"
+ $DisplayName=$Secret.DisplayName
+ $Id=$Secret.KeyId
+ $CreatedTime=$Secret.StartDateTime
+ $ExpiryDate=$Secret.EndDateTime
+ $ExpiryStatusCalculation=(New-TimeSpan -Start (Get-Date).Date -End $ExpiryDate).Days
+ $FriendlyExpiryTime="Expires in $ExpiryStatusCalculation days"
+
+ if (($ExpiryStatusCalculation -gt 0) -and ($ExpiryStatusCalculation -le $SoonToExpireInDays)) {
+ $Script:ProcessedCount++
+ if ($StoreReportLocally.IsPresent) {
+ $ExportResult = [PSCustomObject]@{'App Name'=$AppName;'App Owners'=$Owners;'App Creation Time'=$AppCreationDate;'Credential Type'=$CredentialType;'Credential Name'=$DisplayName;'Credential Id'=$Id;'Creation Time'=$CreatedTime;'Expiry Date'=$ExpiryDate;'Days to Expiry'=$ExpiryStatusCalculation;'Friendly Expiry Date'=$FriendlyExpiryTime;'App Id'=$AppId}
+ $ExportResult | Export-Csv -Path $CSVFilePath -Notype -Append
+ }
+ $Script:TableContent += "| $($AppName) | $($AppCreationDate) | $($CredentialType) | $($DisplayName) | $($CreatedTime) | $($ExpiryDate) | $($FriendlyExpiryTime) |
"
+ }
+ }
+ }
+
+ #Process through Certificates
+ if(!($ClientSecretsOnly.IsPresent) -or ($SwitchPresent -eq $false)){
+ foreach ($Certificate in $Certificates) {
+ $CredentialType="Certificate"
+ $DisplayName=$Certificate.DisplayName
+ $Id=$Certificate.KeyId
+ $CreatedTime=$Certificate.StartDateTime
+ $ExpiryDate=$Certificate.EndDateTime
+ $ExpiryStatusCalculation=(New-TimeSpan -Start (Get-Date).Date -End $ExpiryDate).Days
+ $FriendlyExpiryTime="Expires in $ExpiryStatusCalculation days"
+
+ if (($ExpiryStatusCalculation -gt 0) -and ($ExpiryStatusCalculation -le $SoonToExpireInDays)) {
+ $Script:ProcessedCount++
+ if ($StoreReportLocally.IsPresent) {
+ $ExportResult = [PSCustomObject]@{'App Name'=$AppName;'App Owners'=$Owners;'App Creation Time'=$AppCreationDate;'Credential Type'=$CredentialType;'Credential Name'=$DisplayName;'Credential Id'=$Id;'Creation Time'=$CreatedTime;'Expiry Date'=$ExpiryDate;'Days to Expiry'=$ExpiryStatusCalculation;'Friendly Expiry Date'=$FriendlyExpiryTime;'App Id'=$AppId}
+ $ExportResult | Export-Csv -Path $CSVFilePath -Notype -Append
+ }
+ $Script:TableContent += "| $($AppName) | $($AppCreationDate) | $($CredentialType) | $($DisplayName) | $($CreatedTime) | $($ExpiryDate) | $($FriendlyExpiryTime) |
"
+ }
+ }
+ }
+}
+
+
+
+if ($Script:ProcessedCount -ne 0) {
+ SendEmail
+ Write-Host `n"$Script:ProcessedCount app(s) credential is about to expire in $SoonToExpireInDays days." -ForegroundColor Yellow
+ Write-Host `n"Email has been sent successfully." -ForegroundColor Yellow
+
+ if ((Test-Path -Path $CSVFilePath) -eq "True") {
+ Write-Host `n"The output file saved in: " -NoNewline -ForegroundColor Yellow
+ Write-Host $CSVFilePath
+ }
+} else{
+ Write-Host `n"No app(s) found with secrets or certificates expiring in the next $SoonToExpireInDays days." -ForegroundColor Yellow
+}
+
+
+# Disconnect from Microsoft Graph
+Disconnect-MgGraph | Out-Null
+
+Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green
+Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1900+ Microsoft 365 reports. ~~" -ForegroundColor Green
\ No newline at end of file