Complete 366 Swiss Azure Knife Toolbox

Complete 366
Blog

Occasionally, we delve here into captivating topics in Azure

A.S.S. - Azure Shenanigans Summary

A.S.S. - Azure Shenanigans Summary

As a Cloud Platform Engineer, FinOps whiz, or DevOps ninja, you’re probably dreaming in code and waking up to the mystery of what your Azure environment got up to while you were off in dreamland. Fear not! We’ll serve up the scoop on your cloud’s nocturnal shenanigans alongside your morning brew. Learn the art of having a report cozy up to your cup of joe. ☕📊

TL;DR version

Create Azure Logic App with the flow:

  • The trigger starts the flow based on the defined schedule.
  • The KQL query is run to list results from Azure Monitor Logs.
  • Variables are initialized to store various pieces of data like the result count, list of production subscriptions, recipients, and the current date.
  • The HTML table is created from the query results and then styled with CSS.
  • Finally, an email is sent with the styled HTML table content as its body to the specified recipients.

Logic App Flow

Detailed explanation

First, create a new log analytics workspace where logs will be stored. Let’s call it log-shared-01. Then run this beautiful PowerShell command for all your subscriptions:

New-AzOperationalInsightsAzureActivityLogDataSource -ResourceGroupName $rg -WorkspaceName 'log-shared-01' -Name $sub -SubscriptionId $subId

Why can’t I just use Export Activity Log feature in Azure Monitor, I hear you asking. Because Microsoft changed the schema for activity logs (thanks Microsoft) and removed the human readable field OperationName from the logs. Check the data structure changes and take a minute to appreciate the consistency:

Data structure changes

We are going to use the names from the second column. Don’t worry about the “older deprecated” phrase, it still works and will continue to work till the 15th of Sept 2026. By that time your job will be replaced by AI anyway. Alternatively, you can call REST API to get the activity logs, I’ll leave it to you to improve the tool though.

Then create a blank Logic App, use consumption mode (Best for entry-level. Pay only as much as your workflow runs). Open “Logic app designer” and create the flow pictured above:

  1. Recurrence Trigger: The Logic App starts with a “Recurrence” trigger that runs daily at specific time.

Recurrence Trigger

  1. Run query and list results (Azure Monitor Log): An API connection action is used to execute a Kusto query against an Azure Monitor Log Analytics workspace. It fetches activity logs for the past 24 hours and processes them with certain filters and projections.

KQL of Azure Monitor Logs

The KQL query:

let subs = datatable (SubscriptionId: string, Subscription: string)
// define your sub names and Ids
[
"2277d0e9-22ea-4ab2-b351-80d730f7fd59","Enterprise Essentials",
"00756e79-072f-4fed-8af4-ddca58ed4953","Corporate Connect",
"93c035ae-8d17-40f9-aaab-7739fc859093","Business Boost",
"be1b14f9-9436-46c7-9b18-1b035e978724","Professional Plus",
"7942e91e-d5ba-46c6-9136-df8f80182b33","Premium Pro"
]
AzureActivity
// only interested in human initiated changes
| where ActivityStatus == "Started" and Caller contains "@"
// exclude noise
| where OperationNameValue <> "Microsoft.Authorization/policies/auditIfNotExists/action"
| where OperationNameValue <> "Microsoft.Resources/deployments/validate/action"
| where OperationName <> "Deployment What-If"
| where OperationName <> "Get Network Interface Effective Route Table"
| where OperationName <> "Gets Virtual Hub effective routes"
| where OperationNameValue <> "Microsoft.Storage/storageAccounts/listKeys/action"
// add more providers as needed
| where
ResourceProvider == "Microsoft.Compute" or
ResourceProvider == "Microsoft.Authorization" or
ResourceProvider == "Microsoft.Storage" or
ResourceProvider == "Microsoft.RecoveryServices" or
ResourceProvider == "Microsoft.Network"or
ResourceProvider == "Microsoft.KeyVault" or
ResourceProvider == "Microsoft.ContainerInstance" or
ResourceProvider == "Microsoft.AlertsManagement" or
ResourceProvider == "Microsoft Resources"
| extend DateTime = format_datetime(TimeGenerated, "dd/MM/yyyy HH:mm:ss")
// remove email domain from admin UPNs
| extend Admin = tolower(replace_string(Caller,"@{INSERT YOUR EMAIL DOMAIN}",""))
// create Url for affected resource
| extend ResourceUrl = strcat("https://portal.azure.com/#@{INSERT YOUR TENAND DOMAIN}/resource",ResourceId)
| lookup kind = leftouter subs on SubscriptionId
| project DateTime, OperationName, Resource, ResourceGroup, ResourceProvider, Subscription,  ResourceUrl, Admin, CallerIpAddress
| sort by DateTime
  1. Initialize resultCount: This action initializes a variable called “resultCount” with the number of results returned by the query.

Initialize resultCount

  1. Initialize prodSubs array: Initializes an array variable named “prodSubs” that contains a list of strings representing production or important subscriptions. We will use this array later to highlight these subscriptions in red colour in the report.

Initialize prodSubs array

  1. Create HTML table: Takes the results from the “Run query and list results” action and creates an HTML table. The table includes headers like DateTime, OperationName, Resource, etc., and formats certain data, like converting ResourceGroup to lower case and linking the Resource URL.

Create HTML table

Resource code:

concat('<a href=',item()?['ResourceUrl'],'>',item()?['Resource'],'</a>')

Subscription code:

if(contains(variables('prodSubs'),item()?['Subscription']),concat('<font color = red>',item()?['Subscription'],'</font>'),item()?['Subscription'])
  1. Replace to proper html tags: This action initializes a variable named “htmlTable” where it replaces encoded HTML characters with proper HTML tags in the string generated by “Create HTML table”.

Replace to proper html tags

Code:

replace(replace(body('Create_HTML_table'),'&lt;', '<'),'&gt;', '>')
  1. Add CSS styling: A “Compose” action that adds CSS styling to the HTML table stored in the “htmlTable” variable. Add or modified styles to match your company brand.

Add CSS styling

  1. Initialize recipients: Initializes a variable named “recipients” that contains a list of strings representing email addresses of recipients.

Initialize recipients

  1. Today: Initializes a string variable named “Today” with the current date in the format “dd/MM/yyyy” or any other to your taste.

Today

  1. la-sendemail-01: This is a workflow action that sends an email to the list of recipients defined in the “Recipients” action. The email includes the styled HTML content from the “Add css styling” action and has a subject line indicating the number of changes found in the Azure Activity Log for the current date.

send email

You would need to create this basic logic app that sends an email yourself, it will be easy for you if you managed to follow this far:

send email Logic App

And the Final Result as email

Final Result

That’s all. Only 10 pages in Word and then 2 hours spent on this post.

Thanks for reading and have a look at AppInva – your Entra ID App navigator.

Any questions or feedback - please use the “Contact Us” form on the main page.