Azure functiona to fetch orphan users in azure and send email notification
I am trying to use azure function app to fetch details of orphan accounts in azure subscriptions and send email notification with details. Can you please help me how to create function app and query. I have a query which I scheduled that has no invocation and logs.
Azure Functions
-
Ranashekar Guda • 2,670 Reputation points • Microsoft External Staff • Moderator
2025-05-27T20:44:06.4166667+00:00 Hello @B Satyanarayana,
To achieve your goal of using an Azure Function App to fetch details of orphan accounts in Azure subscriptions and send email notifications with the details, you can follow these steps:Step 1: Create an Azure Function App
- Create an Azure Function App: You can create an Azure Function App in the Azure portal. Make sure to select the appropriate runtime stack and plan that suits your requirements.
- Set up the Function: Once the Function App is created, you can create a new function within the app. You can choose an HTTP trigger or any other trigger based on your requirements.
Step 2: Query Orphan Accounts
- Query Azure Subscriptions: You can use Azure Management Libraries or Azure PowerShell to query your Azure subscriptions for orphan accounts. Orphan accounts are those accounts that do not have any associated resources.
- Identify Orphan Accounts: Write a query or script that identifies orphan accounts based on your criteria. This query should return the details of these orphan accounts.
Step 3: Send Email Notifications
- Integrate Email Service: You can use Azure SendGrid or any other email service to send email notifications. You will need to set up the email service in your Azure environment.
- Send Email Notifications: Within your Azure Function, write code to send email notifications. You can include the details of the orphan accounts in the email content.
Step 4: Schedule the Function
- Set up a Timer Trigger: You can schedule your Azure Function to run at specific intervals using a Timer Trigger. This will ensure that your function runs automatically without manual invocation.
Additional Tips:
- Logging: Make sure to include logging in your Azure Function to track the execution and any errors that might occur.
- Error Handling: Implement error handling in your function to manage exceptions gracefully.
- Security: Ensure that your function has the necessary permissions to query Azure subscriptions and send emails securely.
Kindly refer below links:
Create a function in the Azure portal that runs on a schedule
Use Azure Functions to connect to an Azure SQL Database
Hope this helps. Do let us know if you any further queries.
-
B Satyanarayana • 0 Reputation points
2025-05-27T20:50:08.5366667+00:00 Hi, My requirement actually was to know how to write query to fetch orphan users and my function app doesn't have any invocation details or logs when I run it. How can I enable invocation details and logs for my function app.
-
Ranashekar Guda • 2,670 Reputation points • Microsoft External Staff • Moderator
2025-05-27T21:06:09.13+00:00 Hello @B Satyanarayana,
Thank you for the clarification. To fetch orphan users in Azure, you'll need to define what you consider an "orphan", this typically includes users who are not part of any groups, don't have roles assigned, or show no activity. You can identify such users using either Microsoft Graph API or Azure AD PowerShell. For example, with Graph API, you can retrieve all users and then check their group memberships and roles. Alternatively, using AzureAD PowerShell, you can loop through users and check if they belong to any groups.
Regarding your Function App not showing any invocation details or logs, this is usually due to logging or monitoring not being properly configured. First, ensure Application Insights is enabled for your Function App, as this will allow you to see invocation history and logs in the Azure Portal. Also, check that your trigger (such as a Timer) is correctly set up and that the Function App is running.
You can monitor the function by going to the "Monitor" tab inside your function in the portal. Additionally, include logging statements in your function code (like
log.LogInformation("Function started")
) to trace execution. If your function still doesn't trigger or log anything, verify the CRON expression in the Timer trigger and ensure the function has the right permissions to access Azure resources. -
Satya • 0 Reputation points
2025-05-28T01:32:43.7166667+00:00 I am using below query for the function for Orphan accounts and Email alert. Can you please let me know if it is correct:
param($Timer)
$ErrorActionPreference = "Stop"
Write-Output "$(Get-Date -Format o): Function triggered."
$kvName = "knh-KeyVault-Prod"
$secretName = "-OrphanAccAuto"
$logicAppUrl = (Get-AzKeyVaultSecret -VaultName $kvName -Name $secretName).SecretValueText
List of Subscription IDs to process
$subscriptionIds = @(
"15439ec7-9c47-4f9b-a38f", "0ad84034-d2b0-45cf-b3ae", "d6471423-d073-4857-afef", "a02ea292-f7d2-49d8-b931", "2a154c54-e095-4708-af58", "c4413058-6954-4448-b775", "54fb5ca7-0946-421c-9035" ```) foreach ($subId in $subscriptionIds) { ```powershell Write-Output "Processing subscription: $subId" Set-AzContext -SubscriptionId $subId $assignments = Get-AzRoleAssignment foreach ($assignment in $assignments) { if ([string]::IsNullOrEmpty($assignment.DisplayName) -or $assignment.DisplayName -eq "Unknown" -or [string]::IsNullOrEmpty($assignment.ObjectId) -or $assignment.ObjectId -eq "00000000-0000-0000-0000-000000000000") { $timeRemoved = Get-Date -Format o Write-Output "Removing invalid assignment: $($assignment.RoleAssignmentId)" try { Remove-AzRoleAssignment -RoleAssignmentId $assignment.RoleAssignmentId -ErrorAction Stop Write-Output "Removed assignment at $timeRemoved." $details = @{ SubscriptionId = $subId Id = $assignment.RoleAssignmentId Scope = $assignment.Scope Role = $assignment.RoleDefinitionName ObjectId = $assignment.ObjectId TimeRemoved = $timeRemoved } $emailBody = @{ EmailTo = "******@domain.com" Subject = "Removed Invalid Role Assignment in $subId" HtmlContent = "<p>Removed assignment: $($assignment.RoleAssignmentId)<br>Time: $timeRemoved</p>" } | ConvertTo-Json -Depth 5 Invoke-RestMethod -Uri $logicAppUrl -Method POST -Body $emailBody -ContentType "application/json" } catch { Write-Error "Failed to remove assignment in $subId: $_" } } } ```} Write-Output "$(Get-Date -Format o): Function completed."
-
B Satyanarayana • 0 Reputation points
2025-05-29T07:40:22.5+00:00 Hi @Ranashekar Guda : Any suggestions on the script i shared in the comments . Want to fetch details of orphan accounts with function app. Unable to see invocation or logs . Can you please help what changes can I make to the script for my requirement
-
Ranashekar Guda • 2,670 Reputation points • Microsoft External Staff • Moderator
2025-05-29T20:01:25.7933333+00:00 Hello @ B Satyanarayana,
Your script is a solid foundation for detecting and removing invalid (potentially orphaned) role assignments across multiple Azure subscriptions and sending notifications via a Logic App. There are several issues and opportunities for improvement to help you debug the lack of logs and ensure reliable execution.
Summary of Issues & Recommendations :
- 1. PowerShell Comment Syntax
- Incorrect:
List of Subscription IDs to process
- Fix:
# List of Subscription IDs to process
- Issues: Missing quotes, commas, and the array is not closed.
- Fix:
$subscriptionIds = @( "15439ec7-9c47-4f9b-a38f", "0ad84034-d2b0-45cf-b3ae", "d6471423-d073-4857-afef", "a02ea292-f7d2-49d8-b931", "2a154c54-e095-4708-af58", "c4413058-6954-4448-b775", "54fb5ca7-0946-421c-9035"
- Incorrect:
) ```
3. Stray Markdown/Code Block
- Remove any stray fragments like:
C++
4. Azure Function Logging Best Practices
- Use
Write-Host
orWrite-Information
instead ofWrite-Output
/Write-Error
to ensure logs appear in Application Insights. - Example:
Write-Host "$(Get-Date -Format o): Function triggered."
5. No Logs Appearing?
- Ensure Application Insights is enabled and linked.
- Check the Timer trigger schedule and manually invoke the function for testing (via Azure Portal or Postman).
- Replace output methods to enhance visibility.
6. Improved Logic App Payload Formatting
- PowerShell's
ConvertTo-Json
may not handle nested hashtables well without setting depth. - Recommended format:
```powershell $emailBody = @{ EmailTo = "******@domain.com" Subject = "Removed Invalid Role Assignment in $subId" HtmlContent = "<p>Removed assignment: $($assignment.RoleAssignmentId)<br>Time: $timeRemoved</p>"
} Invoke-RestMethod -Uri $logicAppUrl -Method Post -Body ($emailBody | ConvertTo-Json -Depth 3) -ContentType "application/json" ```
7. Identity & Permission Requirements
- Ensure the Function App (via Managed Identity) has:
- Reader + User Access Administrator role for each subscription.
- Access to Key Vault for retrieving Logic App URL.
8. Proper Azure Function Structure
- Timer-triggered PowerShell functions should include
param($Timer)
Write-Host "Timer trigger function executed at: $(Get-Date)" ```
- Ensure:
- The script is in the correct path (e.g.,
FunctionName/run.ps1
). -
function.json
defines the trigger properly.
- The script is in the correct path (e.g.,
Optional: Add Diagnostics
- Log important actions (e.g., removals) to Azure Table Storage or Application Insights for traceability.
✅ Cleaned & Updated Script
param($Timer) $ErrorActionPreference = "Stop" Write-Host "$(Get-Date -Format o): Function triggered." $kvName = "knh-KeyVault-Prod" $secretName = "-OrphanAccAuto" $logicAppUrl = (Get-AzKeyVaultSecret -VaultName $kvName -Name $secretName).SecretValueText # List of Subscription IDs to process $subscriptionIds = @( "15439ec7-9c47-4f9b-a38f", "0ad84034-d2b0-45cf-b3ae", "d6471423-d073-4857-afef", "a02ea292-f7d2-49d8-b931", "2a154c54-e095-4708-af58", "c4413058-6954-4448-b775", "54fb5ca7-0946-421c-9035" ) foreach ($subId in $subscriptionIds) { Write-Host "Processing subscription: $subId" Set-AzContext -SubscriptionId $subId $assignments = Get-AzRoleAssignment foreach ($assignment in $assignments) { if ([string]::IsNullOrEmpty($assignment.DisplayName) -or $assignment.DisplayName -eq "Unknown" -or [string]::IsNullOrEmpty($assignment.ObjectId) -or $assignment.ObjectId -eq "00000000-0000-0000-0000-000000000000") { $timeRemoved = Get-Date -Format o Write-Host "Removing invalid assignment: $($assignment.RoleAssignmentId)" try { Remove-AzRoleAssignment -RoleAssignmentId $assignment.RoleAssignmentId -ErrorAction Stop Write-Host "Removed assignment at $timeRemoved." $emailBody = @{ EmailTo = "******@domain.com" Subject = "Removed Invalid Role Assignment in $subId" HtmlContent = "<p>Removed assignment: $($assignment.RoleAssignmentId)<br>Time: $timeRemoved</p>" } Invoke-RestMethod -Uri $logicAppUrl -Method POST -Body ($emailBody | ConvertTo-Json -Depth 3) -ContentType "application/json" } catch { Write-Host "Error removing assignment in $subId: $_" } } } } Write-Host "$(Get-Date -Format o): Function completed."
- 1. PowerShell Comment Syntax
-
Ranashekar Guda • 2,670 Reputation points • Microsoft External Staff • Moderator
2025-05-30T18:05:29.1366667+00:00 Hello @B Satyanarayana,
Just checking in to see if above information was helpful. If you have any further updates on this issue, please feel free to post back. -
Ranashekar Guda • 2,670 Reputation points • Microsoft External Staff • Moderator
2025-06-03T16:26:09.23+00:00 Hello @B Satyanarayana,
We haven't heard back from you yet, and I wanted to ensure everything is on track and see if there's anything more, we can assist you with.
Sign in to comment