Azure App Registrations are a secure way to provide Credentials and Access to the Azure World. The Amount of Azure App Registrations in a Tenant is increasing very fast, so it is not that easy to control and monitor all those Secrets, Zert, and SPN’s Expire Date.
So I have written a PowerShell Script to send an Email, when an Azure App Secret or Zert, or an Azure SPN is expiring in the near future. This Script can be executed in your OnPrem data center or as an Azure Automation Runbook.
We added an ExcludeList with Wildcards, so you an add some App Names, where there will be no notification
Prerequisites
First, we need to create an Azure App to provide all the permissions required. Yes, that one will also be monitored by the Script 🙂
MS Graph References:
- List applications – Microsoft Graph v1.0 | Microsoft Docs
- List servicePrincipals – Microsoft Graph v1.0 | Microsoft Docs
Create an Azure App Registration and add the following GRAPH API Application Permissions
- Application.Read.All
- Application.ReadWrite.All
- Directory.Read.All
- User.Read
Create a Secret and copy the Value
If your are not familiar with Azur eapp Regs, and how als this work together, see my Blogs Post for Details:
To learn more from Microsoft GRAPH API, see my Blog Series:
Part 1 – Authentication and Azure App – Use Microsoft Graph API with PowerShell – Part 1 » TechGuy
Part 2 – Oauth2.0 – Use Microsoft Graph API with PowerShell – Part 2 » TechGuy
Part 3 – First Powershell Script to get a Teams Lis and Walkthrough – Use Microsoft Graph API with PowerShell – Part 3 » TechGuy
Part 4 – this one – Use Microsoft Graph API with PowerShell – Part 4 » TechGuy
Second, we are sending an Email using Graph API, so see my previous Blog Post for Details and Enter the GRAPH Details in the Script: Send Mail with PowerShell and Microsoft Graph API – TechGuy
With all this information, we can take a look at the Script
The Script
Please fill in Settings and the Azure App Details til the Line “#STOP HERE”
The Script will send you an email for each Azure App or SP, which will expire within the next 90 Days ($TimeSpanInDays).
Already expired ones will not trigger an Email
The Variable $ExcludedList can be used to add Names with Wildcards, those App will be ignored
#Settings
$TimeSpanInDays = 90
$MailSender = "Mail Sender Mail"
$MailRecipient = "Mail Recipient Mail"
#Azure App Credentials to get Apps and SP
$EXPIRE_AppId = "your EXPIRE APP Client ID"
$EXPIRE_secret = "your EXPIRE APP Secret"
$tenantID = "Azure Tenant ID"
#Azure App Credentials to send the Mail
$MAIL_AppId = "your Mail Client ID"
$MAIL_secret = "your Mail Secret"
#ExcludeList
$ExcludedList = "*(Power Virtual Agents);*(Microsoft Copilot Studio);RSC-CAM-Einfahrt"
$ExcludedListArray = $ExcludedList -split ";"
#STOP HERE!
#Connect to GRAPH API with EXPIRE credentials
$EXPIRE_tokenBody = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $EXPIRE_AppId
Client_Secret = $EXPIRE_secret
}
$EXPIRE_tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $EXPIRE_tokenBody
$EXPIRE_headers = @{
"Authorization" = "Bearer $($EXPIRE_tokenResponse.access_token)"
"Content-type" = "application/json"
}
#Connect to GRAPH API with MAIL Credentials
$MAIL_tokenBody = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $MAIL_AppId
Client_Secret = $MAIL_secret
}
$MAIL_tokenResponse = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token" -Method POST -Body $MAIL_tokenBody
$MAIL_headers = @{
"Authorization" = "Bearer $($MAIL_tokenResponse.access_token)"
"Content-type" = "application/json"
}
#functions
function Get-AzureResourcePaging {
param (
$URL,
$AuthHeader
)
# List Get all Apps from Azure
$Response = Invoke-RestMethod -Method GET -Uri $URL -Headers $AuthHeader
$Resources = $Response.value
$ResponseNextLink = $Response."@odata.nextLink"
while ($ResponseNextLink -ne $null) {
$Response = (Invoke-RestMethod -Uri $ResponseNextLink -Headers $AuthHeader -Method Get)
$ResponseNextLink = $Response."@odata.nextLink"
$Resources += $Response.value
}
return $Resources
}
#Build Array to store PSCustomObject
$Array = @()
# List Get all Apps from Azure
$URLGetApps = "https://graph.microsoft.com/v1.0/applications"
$AllApps = Get-AzureResourcePaging -URL $URLGetApps -AuthHeader $EXPIRE_headers
#Go through each App and add to our Array
foreach ($App in $AllApps) {
$URLGetApp = "https://graph.microsoft.com/v1.0/applications/$($App.ID)"
$App = Invoke-RestMethod -Method GET -Uri $URLGetApp -Headers $EXPIRE_headers
if ($App.passwordCredentials) {
foreach ($item in $App.passwordCredentials) {
$Array += [PSCustomObject]@{
"Type" = "AZAPP"
"displayName" = $app.displayName
"ID" = $App.ID
"AppID" = $app.appId
"SecType" = "Secret"
"Secret" = $item.displayName
"Secret-EndDate" = (Get-date $item.endDateTime)
}
}
}
if ($App.keyCredentials) {
foreach ($item in $App.keyCredentials) {
$Array += [PSCustomObject]@{
'Type' = "AZAPP"
'displayName' = $app.displayName
'ID' = $App.ID
'AppID' = $app.appId
'SecType' = "Zert"
'Secret' = $item.displayName
'Secret-EndDate' = (Get-date $item.endDateTime)
}
}
}
}
#Get all Service Principals
$servicePrincipals = "https://graph.microsoft.com/v1.0/servicePrincipals"
$SPList = Get-AzureResourcePaging -URL $servicePrincipals -AuthHeader $EXPIRE_headers
#Go through each SP and add to our Array
foreach ($SAML in $SPList) {
if ($Saml.passwordCredentials) {
foreach ($PW in $Saml.passwordCredentials) {
$Array += [PSCustomObject]@{
'Type' = "SP"
'displayName' = $SAML.displayName
'ID' = $SAML.id
'AppID' = $Saml.appId
'SecType' = "Secret"
'Secret' = $PW.displayName
'Secret-EndDate' = (Get-date $PW.endDateTime)
}
}
}
}
$ExpireringZerts = $Array | Where-Object -Property Secret-EndDate -Value (Get-Date).AddDays($TimeSpanInDays) -lt | Where-Object -Property Secret-EndDate -Value (Get-Date) -gt
foreach ($Zert in $ExpireringZerts) {
$Exclude = $False
foreach ($Entry in $ExcludedListArray) {
if ($Zert.displayName -like $Entry) {
$Exclude = $True
}
}
if ($Exclude) {
#do nothing
}
else {
$HTML = $Zert | Convertto-HTML -Fragment -As List
$URLsend = "https://graph.microsoft.com/v1.0/users/$MailSender/sendMail"
$BodyJsonsend = @"
{
"message": {
"subject": "Azure App or SPN will expire soon $($Zert.displayName)",
"body": {
"contentType": "HTML",
"content": "$HTML
<br>
Michael Seidl (au2mator)
<br>
"
},
"toRecipients": [
{
"emailAddress": {
"address": "$MailRecipient"
}
}
]
},
"saveToSentItems": "false"
}
"@
Invoke-RestMethod -Method POST -Uri $URLsend -Headers $MAIL_headers -Body $BodyJsonsend
}
}
GitHub Repo
Here you can find the GitHub Repo: Seidlm/Microsoft-Azure: Azure Rest API Examples (github.com) with the Script
Michael Seidl aka Techguy
au2mate everything