Azure Auto-Tagging Function
A PowerShell-based Azure Function App script designed to automatically tag newly created Azure resources, ensuring consistent governance and cost tracking.
🔍 Problem Overview
In enterprise Azure environments, resources are often created by multiple users, automation systems, or DevOps pipelines. However, tracking who deployed what, when it was deployed, and why it exists becomes increasingly difficult—especially when resources are modified later or deployed outside Infrastructure as Code (IaC) workflows.
Without consistent tagging:
- ❌Accountability is lost - No way to identify who created resources
- ❌Cost tracking becomes unreliable - Cannot allocate costs to teams or projects
- ❌Governance and compliance suffer - Difficult to enforce organizational policies
- ❌Manual audits become necessary - Time-consuming and error-prone
Manual tagging is inconsistent and often forgotten, while policy-based enforcement can block deployments and frustrate users. An automated, event-driven approach provides the best balance of governance and user experience.
🎯 Solution Objectives
The Azure Auto-Tagging Function is designed to solve these challenges through an event-driven approach that:
Automatically tags resources at creation
No user intervention required
Derives values dynamically
From claims, subscription name, resource group name
Avoids overwriting tags during updates
Preserves existing tag values
Ensures coverage across all Azure resources
Works with any resource emitting ResourceWriteSuccess events
By meeting these objectives, the solution provides a robust foundation for resource governance, cost management, and operational accountability without disrupting user workflows.
🏗️ Solution Architecture
Components
- Azure Function App
PowerShell-based function that processes resource creation events and applies tags
- Event Grid Subscription
Listens for ResourceWriteSuccess events across the subscription
- Managed Identity
Securely authenticates the function to Azure resources without credentials
- PowerShell Script
Core logic that processes events and applies appropriate tags
How It Works
- A resource is created or updated in Azure
- Azure Event Grid captures the ResourceWriteSuccess event
- The event is routed to the Azure Function
- The PowerShell function extracts resource details from the event
- The function determines appropriate tag values based on context
- Tags are applied to the resource using the Azure Resource Manager API
- The function logs the operation for auditing purposes
Key Benefits of This Architecture
- Serverless - No infrastructure to manage
- Event-driven - Real-time response to resource changes
- Scalable - Handles enterprise-level resource creation
- Non-blocking - Doesn't interfere with deployments
- Secure - Uses managed identities instead of credentials
🛠️ Implementation Guide
Prerequisites
- Azure subscription with Contributor access
- Azure CLI or PowerShell installed locally
- Visual Studio Code (recommended for script editing)
Step 1: Create the Function App
# Create a resource group for the function
az group create --name rg-auto-tagging --location eastus
# Create a storage account for the function
az storage account create --name stautotag[unique] \
--resource-group rg-auto-tagging \
--location eastus \
--sku Standard_LRS
# Create the function app with PowerShell runtime
az functionapp create --name func-auto-tagging \
--resource-group rg-auto-tagging \
--storage-account stautotag[unique] \
--consumption-plan-location eastus \
--runtime powershell \
--functions-version 4 \
--os-type Windows
Step 2: Enable System-Assigned Managed Identity
# Enable system-assigned managed identity
az functionapp identity assign \
--name func-auto-tagging \
--resource-group rg-auto-tagging
# Assign Contributor role to the function app's managed identity
# Get the principal ID first
principalId=$(az functionapp identity show \
--name func-auto-tagging \
--resource-group rg-auto-tagging \
--query principalId -o tsv)
# Assign role at subscription level
az role assignment create \
--assignee $principalId \
--role "Contributor" \
--scope "/subscriptions/$(az account show --query id -o tsv)"
Step 3: Create the Function
Create a new function in the Azure portal or using Azure Functions Core Tools:
- Navigate to your function app in the Azure portal
- Click on "Functions" in the left menu
- Click "Add" to create a new function
- Select "Azure Event Grid trigger"
- Name the function "AutoTagResources"
- Click "Create"
📜 PowerShell Script
Replace the default function code with the following PowerShell script:
# Input bindings are passed in via param block
param($eventGridEvent, $TriggerMetadata)
# Define tag schema
$tagSchema = @{
"Creator" = $null
"CreatedDate" = $null
"Environment" = $null
"Project" = $null
"Department" = $null
"IDSApplicationOwner-Symphony" = $null
}
# Initialize Azure context
Connect-AzAccount -Identity
# Process the event
try {
# Extract resource information from the event
$resourceId = $eventGridEvent.data.resourceUri
$operationName = $eventGridEvent.data.operationName
$status = $eventGridEvent.data.status
$timestamp = $eventGridEvent.eventTime
$claims = $eventGridEvent.data.claims
# Log event information
Write-Host "Processing event for resource: $resourceId"
Write-Host "Operation: $operationName"
Write-Host "Status: $status"
# Only process resource creation events
if ($operationName -like "*/write" -and $status -eq "Succeeded") {
# Extract resource details
$resourceGroup = ($resourceId -split '/')[4]
$resourceType = ($resourceId -split '/')[6..7] -join '/'
$resourceName = ($resourceId -split '/')[-1]
# Get existing resource and its tags
$resource = Get-AzResource -ResourceId $resourceId
$existingTags = $resource.Tags
if ($null -eq $existingTags) {
$existingTags = @{}
}
# Prepare new tags
$newTags = $existingTags.Clone()
# Set Creator tag if not exists
if (-not $newTags.ContainsKey("Creator")) {
# Try to get creator from claims
if ($claims.ContainsKey("name")) {
$creator = $claims["name"]
} elseif ($claims.ContainsKey("appid")) {
$creator = "Service Principal: " + $claims["appid"]
} else {
$creator = "Unknown"
}
$newTags["Creator"] = $creator
}
# Set CreatedDate tag if not exists
if (-not $newTags.ContainsKey("CreatedDate")) {
$newTags["CreatedDate"] = (Get-Date $timestamp).ToString("yyyy-MM-dd")
}
# Try to determine Environment from resource group name
if (-not $newTags.ContainsKey("Environment")) {
if ($resourceGroup -match "prod|production") {
$newTags["Environment"] = "Production"
} elseif ($resourceGroup -match "dev|development") {
$newTags["Environment"] = "Development"
} elseif ($resourceGroup -match "test|testing|qa") {
$newTags["Environment"] = "Test"
} elseif ($resourceGroup -match "stage|staging") {
$newTags["Environment"] = "Staging"
} else {
$newTags["Environment"] = "Unknown"
}
}
# Try to determine Project from resource group name
if (-not $newTags.ContainsKey("Project")) {
# Extract project code if resource group follows naming convention
if ($resourceGroup -match "rg-([a-zA-Z0-9]+)") {
$newTags["Project"] = $matches[1]
} else {
$newTags["Project"] = "Unknown"
}
}
# Set Department tag if not exists
if (-not $newTags.ContainsKey("Department")) {
# Try to determine from subscription
$subscription = (Get-AzContext).Subscription.Name
if ($subscription -match "finance|accounting") {
$newTags["Department"] = "Finance"
} elseif ($subscription -match "it|infra") {
$newTags["Department"] = "IT"
} elseif ($subscription -match "hr|human") {
$newTags["Department"] = "HR"
} elseif ($subscription -match "sales|marketing") {
$newTags["Department"] = "Sales"
} else {
$newTags["Department"] = "Unknown"
}
}
# Set IDSApplicationOwner-Symphony tag if not exists
if (-not $newTags.ContainsKey("IDSApplicationOwner-Symphony")) {
if ($claims.ContainsKey("name")) {
$newTags["IDSApplicationOwner-Symphony"] = $claims["name"]
} else {
$newTags["IDSApplicationOwner-Symphony"] = "Unknown"
}
}
# Apply tags if they've changed
if (($newTags.Count -gt $existingTags.Count) -or
($newTags.Keys | Where-Object { $newTags[$_] -ne $existingTags[$_] })) {
Write-Host "Applying tags to resource: $resourceName"
# Update resource tags
Update-AzTag -ResourceId $resourceId -Tag $newTags -Operation Merge
Write-Host "Tags applied successfully"
} else {
Write-Host "No new tags to apply"
}
} else {
Write-Host "Skipping event - not a successful resource creation"
}
} catch {
Write-Error "Error processing event: $_"
throw $_
}
Key Script Features
- Tag Schema Definition - Defines the standard tags to be applied
- Managed Identity Authentication - Uses system-assigned identity for secure access
- Intelligent Tag Derivation - Extracts values from context when possible
- Non-Destructive Updates - Preserves existing tags using merge operation
- Error Handling - Robust error handling with detailed logging
🏷️ Tag Schema
The solution applies the following standard tags to resources:
Tag Name | Description | Source | Example |
---|---|---|---|
Creator | Person or system that created the resource | Event claims | "John Doe" or "Service Principal: 12345678-1234-1234-1234-123456789012" |
CreatedDate | Date when the resource was created | Event timestamp | "2025-06-01" |
Environment | Deployment environment | Resource group name pattern | "Production", "Development", "Test", "Staging" |
Project | Project or application identifier | Resource group name pattern | "FinApp", "CRM", "DataLake" |
Department | Organizational department | Subscription name pattern | "Finance", "IT", "HR", "Sales" |
IDSApplicationOwner-Symphony | Application owner identifier | Event claims | "John Doe" |
Customizing the Tag Schema
To customize the tag schema for your organization:
- Modify the
$tagSchema
variable in the PowerShell script - Add or remove tag definitions as needed
- Update the tag derivation logic in the script
- Test thoroughly before deploying to production
📡 Event Grid Setup
To connect your function to Azure Event Grid:
Step 1: Create an Event Grid Subscription
# Get the function URL and key
functionUrl=$(az functionapp function show \
--name func-auto-tagging \
--resource-group rg-auto-tagging \
--function-name AutoTagResources \
--query invokeUrlTemplate -o tsv)
functionKey=$(az functionapp function keys list \
--name func-auto-tagging \
--resource-group rg-auto-tagging \
--function-name AutoTagResources \
--query default -o tsv)
# Create the event grid subscription
az eventgrid event-subscription create \
--name "resource-auto-tagging" \
--source-resource-id "/subscriptions/$(az account show --query id -o tsv)" \
--endpoint $functionUrl \
--endpoint-type azurefunction \
--included-event-types Microsoft.Resources.ResourceWriteSuccess \
--subject-begins-with "/subscriptions/" \
--advanced-filter data.operationName StringContains "Microsoft.Resources/deployments/write" "Microsoft.Resources/deployments/validate/action"
Step 2: Verify Event Grid Subscription
# List event grid subscriptions
az eventgrid event-subscription list \
--source-resource-id "/subscriptions/$(az account show --query id -o tsv)" \
--query "[?name=='resource-auto-tagging']"
Important Note
The Event Grid subscription will trigger the function for all resource creation events in the subscription. For large environments, consider filtering by resource group or resource type to reduce function executions.
🧪 Testing & Validation
Test Plan
To verify the auto-tagging function is working correctly:
- Create a test resource
# Create a test storage account az storage account create \ --name sttest[unique] \ --resource-group rg-auto-tagging \ --location eastus \ --sku Standard_LRS
- Check function logs
# View function logs az functionapp log tail \ --name func-auto-tagging \ --resource-group rg-auto-tagging
- Verify tags were applied
# Check resource tags az resource show \ --name sttest[unique] \ --resource-group rg-auto-tagging \ --resource-type "Microsoft.Storage/storageAccounts" \ --query tags
Validation Scenarios
Test User-Created Resources
Create resources through the portal to verify user identity is captured
Test Service Principal Resources
Deploy resources via CI/CD to verify service principal tagging
Test Tag Preservation
Update resources with existing tags to verify they're preserved
Test Various Resource Types
Create different resource types to ensure broad compatibility
🔧 Troubleshooting
Common Issues
Function Not Triggering
Symptoms: Resources are created but no tags are applied, no function logs
Possible Causes:
- Event Grid subscription not properly configured
- Function app is stopped or in error state
- Event types don't match what's being generated
Solution: Verify Event Grid subscription, check function app status, review event filtering
Permission Errors
Symptoms: Function triggers but fails with permission errors
Possible Causes:
- Managed identity not configured correctly
- Insufficient role assignments
- Resource locks preventing tag updates
Solution: Verify managed identity, check role assignments, look for resource locks
Script Errors
Symptoms: Function triggers but fails with script errors
Possible Causes:
- PowerShell module version incompatibilities
- Syntax errors in the script
- Unexpected event payload format
Solution: Check function logs for detailed error messages, test script locally
Diagnostic Commands
# Check function app status
az functionapp show --name func-auto-tagging --resource-group rg-auto-tagging --query state
# View detailed function logs
az functionapp log tail --name func-auto-tagging --resource-group rg-auto-tagging
# Check managed identity configuration
az functionapp identity show --name func-auto-tagging --resource-group rg-auto-tagging
# Verify role assignments
principalId=$(az functionapp identity show --name func-auto-tagging --resource-group rg-auto-tagging --query principalId -o tsv)
az role assignment list --assignee $principalId
# Check Event Grid subscription
az eventgrid event-subscription show --name "resource-auto-tagging" --source-resource-id "/subscriptions/$(az account show --query id -o tsv)"
✨ Best Practices
Resource Naming Conventions
Establish consistent naming conventions for resources and resource groups to improve tag value derivation.
- Resource Groups: rg-[project]-[environment]
- Subscriptions: [department]-[purpose]
- Resources: [type]-[project]-[environment]
Function App Configuration
Optimize your function app for reliability and performance.
- Enable Application Insights for monitoring
- Configure appropriate timeout settings
- Set up alerts for function failures
- Use consumption plan for cost efficiency
Security Considerations
Ensure your auto-tagging solution follows security best practices.
- Use least-privilege permissions for managed identity
- Enable diagnostic settings for audit logs
- Implement IP restrictions for function app
- Regularly review and rotate keys
Operational Excellence
Maintain and improve your auto-tagging solution over time.
- Document tag schema and derivation logic
- Implement version control for script changes
- Set up regular tag compliance reporting
- Periodically review and update tag schema
🚀 Advanced Scenarios
Multi-Subscription Deployment
For organizations with multiple subscriptions, consider these approaches:
- Centralized Function: Deploy a single function app with multiple Event Grid subscriptions
- Distributed Functions: Deploy separate function apps in each subscription
- Management Group Level: Configure Event Grid at the management group level
Integration with ServiceNow
For organizations using ServiceNow for change management:
# Add to PowerShell script to create ServiceNow change requests for remediation
if ($requiresRemediation) {
$snowParams = @{
Uri = "https://your-instance.service-now.com/api/now/table/change_request"
Method = "POST"
Headers = @{
"Accept" = "application/json"
"Content-Type" = "application/json"
"Authorization" = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("username:password"))
}
Body = @{
short_description = "Tag remediation for $resourceName"
description = "Resource $resourceId requires tag remediation"
category = "Other"
priority = "4"
} | ConvertTo-Json
}
Invoke-RestMethod @snowParams
}
Tag Compliance Reporting
Implement regular tag compliance reporting to identify resources that need attention:
# Sample PowerShell script for tag compliance reporting
$resources = Get-AzResource
$report = @()
foreach ($resource in $resources) {
$compliance = @{
ResourceId = $resource.ResourceId
ResourceName = $resource.Name
ResourceType = $resource.ResourceType
ResourceGroup = $resource.ResourceGroupName
MissingTags = @()
IncompleteValues = @()
}
# Check for required tags
foreach ($tag in @("Creator", "CreatedDate", "Environment", "Project", "Department", "IDSApplicationOwner-Symphony")) {
if (-not $resource.Tags -or -not $resource.Tags.ContainsKey($tag)) {
$compliance.MissingTags += $tag
} elseif ($resource.Tags[$tag] -eq "Unknown" -or [string]::IsNullOrEmpty($resource.Tags[$tag])) {
$compliance.IncompleteValues += $tag
}
}
if ($compliance.MissingTags.Count -gt 0 -or $compliance.IncompleteValues.Count -gt 0) {
$report += [PSCustomObject]$compliance
}
}
$report | Export-Csv -Path "TagComplianceReport.csv" -NoTypeInformation
Cost Allocation Dashboards
Use the tags applied by this solution to create powerful cost allocation dashboards in Azure Cost Management:
- Navigate to Azure Cost Management in the Azure portal
- Create a new view with grouping by your tags (e.g., Department, Project, Environment)
- Save the view and share with stakeholders
- Schedule regular exports to CSV or Power BI
Get the Azure Auto-Tagging Function
Ready to implement automatic resource tagging in your Azure environment? Download the Azure Auto-Tagging Function script and start improving your cloud governance today.