Use at your own risk. Test in a non-production environment first. The author is not responsible for any unintended outcomes.
Overview & Architecture
Azure App Registrations use client secrets and certificates to authenticate. Both expire — and when they do, every application relying on them breaks silently. This Runbook scans every App Registration in your tenant and flags expired or expiring-soon credentials directly in the Automation job output, with no email service or Office 365 licence needed.
Authentication is handled entirely by a System-Assigned Managed Identity — zero credentials stored anywhere.
RunbookPowerShell 7.2
(System-Assigned)No secrets needed
API/v1.0/applications
OutputJob Logs
| Component | Purpose | Cost |
|---|---|---|
| Azure Automation Account | Hosts and schedules the Runbook | Free (500 min/month) |
| System-Assigned Managed Identity | Authenticates to Graph API — no credentials stored | Free |
| Microsoft Graph API | Reads all App Registration credential metadata | Free |
Prerequisites
- An active Azure subscription
- Global Administrator or Privileged Role Administrator in Azure AD
- Access to Azure Cloud Shell (used for the permission assignment)
- PowerShell 5.1+ or PowerShell 7 (Runbook runtime)
No Office 365 licence required. Results are written to the Automation job console only — no email, no Exchange, no licensed mailbox needed.
Create the Azure Automation Account
Skip this step if you already have an Automation Account.
Navigate to Automation Accounts
In the Azure Portal search bar type Automation Accounts and open the service.
Click + Create
Choose your Subscription, Resource Group, give it a name (e.g. automatewithravi), pick a Region and click Review + Create.
Enable System-Assigned Managed Identity
Enable the Managed Identity so the Runbook can authenticate to Microsoft Graph without storing any credentials.
Open your Automation Account → Identity
In the left menu under Account Settings, click Identity.
Toggle Status to On and Save
On the System assigned tab, toggle Status to On, click Save and confirm.
Copy the Object (principal) ID
After saving, the Object (principal) ID GUID appears. Copy it — you need it in Step 3.
A system assigned managed identity is restricted to one per resource and is tied to the lifecycle of this resource. The identity is deleted when this resource is deleted. You can grant permissions for the managed identity using Azure role-based access control (RBAC). The managed identity is authenticated with Azure Active Directory (AD), so you don't have to store any credentials in code.
Copy the Object (principal) ID from this page — you will paste it as $MI_OBJECT_ID in Step 3.
Assign the Graph API Permission
The Managed Identity needs the Application.Read.All Microsoft Graph application role. The Azure Portal UI does not support this assignment for managed identities — use Azure Cloud Shell instead.
Global Administrator or Privileged Role Administrator required to run these commands.
Azure Cloud Shell — PowerShell
# Replace with your Managed Identity Object ID (copied from Step 2) $MI_OBJECT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Microsoft Graph app ID (same in every tenant) $GRAPH_APP_ID = "00000003-0000-0000-c000-000000000000" # Get the Graph service principal in your tenant $graphSp = Get-AzADServicePrincipal -ApplicationId $GRAPH_APP_ID # Find the Application.Read.All role definition ID $roleId = ($graphSp.AppRole | Where-Object { $_.Value -eq "Application.Read.All" }).Id # Assign the role to the Managed Identity New-AzADServicePrincipalAppRoleAssignment ` -ServicePrincipalId $MI_OBJECT_ID ` -PrincipalId $MI_OBJECT_ID ` -ResourceId $graphSp.Id ` -AppRoleId $roleId Write-Host "Application.Read.All assigned successfully." -ForegroundColor Green
Azure Cloud Shell — Bash / CLI (alternative)
# Replace with your Managed Identity Object ID MI_OBJECT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" GRAPH_SP_ID=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv) ROLE_ID=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 \ --query "appRoles[?value=='Application.Read.All'].id" -o tsv) az rest --method POST \ --uri "https://graph.microsoft.com/v1.0/servicePrincipals/$MI_OBJECT_ID/appRoleAssignments" \ --body "{\"principalId\":\"$MI_OBJECT_ID\",\"resourceId\":\"$GRAPH_SP_ID\",\"appRoleId\":\"$ROLE_ID\"}" echo "Done."
Verify Permission via Cloud Shell
Run the following command in Azure Cloud Shell to confirm the role was assigned correctly. This is the most reliable way to verify — quicker and more accurate than the Portal UI.
# Replace with your Managed Identity Object ID $MI_OBJECT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" $assignments = Get-AzADServicePrincipalAppRoleAssignment -ServicePrincipalId $MI_OBJECT_ID foreach ($a in $assignments) { $sp = Get-AzADServicePrincipal -ObjectId $a.ResourceId $role = $sp.AppRole | Where-Object { $_.Id -eq $a.AppRoleId } [PSCustomObject]@{ Resource = $sp.DisplayName Permission = $role.Value AssignedOn = $a.CreatedAt } } | Format-Table -AutoSize
If you see Application.Read.All under Microsoft Graph the permission is correctly assigned and you can proceed to Step 5.
Create & Publish the Runbook
Open Runbooks
In your Automation Account, click Runbooks → + Create a runbook.
Configure
Name: Automatewithravi_App-registration · Type: PowerShell · Runtime: 7.2. Click Create.
Paste the script
Clear the editor, paste the script from Full Script below (or view it on GitHub), and click Save.
Publish
Click Publish and confirm. The Runbook is now ready to run.
Run & Review Console Output
Start the Runbook
Click Start. In the parameters dialog set ExpiryThresholdDays to 30 (or your preferred value) and click OK.
Open the Output tab
Wait for Status: Completed, then click the Output tab to see results.
The job completed successfully. PowerShell_Connecti... has a secret New_secret expiring on 21 Mar 2026 — 13 days away. Rotate it before it expires to avoid service interruption.
Schedule the Runbook
Open Schedules
From your Runbook, click Schedules → + Add a schedule.
Create a schedule
Name it (e.g. weekly-monday-9am), set recurrence to Weekly, day to Monday, time to 09:00.
Set parameters & Save
Click Configure parameters, set ExpiryThresholdDays to 30, click OK then OK again to save.
View past scan results anytime under Automation Account → Jobs. Every job run preserves the full output log.
Full Script Reference
The complete script is hosted on GitHub. Click the button below to view, copy, or download the .ps1 file directly from the repository.