Tooling and strategy for safely cleaning up a long-lived Azure dev tenant with years of accumulated, unmanaged resources.
- Tenant has been active for 10+ years
- Many resources are orphaned (creators no longer with the company)
- No consistent tagging or ownership metadata
- Some old resources are still actively used
- Manual review is impractical at scale
Automated queries to build a complete picture of what exists, who created it, when it was last touched, and what it costs.
Tools: Azure Resource Graph (KQL), Activity Logs, Cost Management
Categorize every resource into actionable buckets: dead, dormant, orphaned, active-unmanaged, or active-managed.
Tools: PowerShell scripts cross-referencing Resource Graph, Entra ID, and Activity Logs
Put guardrails in place so the problem doesn't recur. Enforce tagging, budgets, and expiry policies.
Tools: Azure Policy definitions, budget alerts
Staged deletion with grace periods, notifications, and export-before-delete safeguards.
Tools: PowerShell cleanup scripts, Azure Automation / Logic Apps
discovery/ # 20 KQL queries + orchestration scripts for tenant inventory
cleanup/ # 10 PowerShell scripts for safe resource cleanup + orchestration
reporting/ # Cost projection, baselines, Excel export, comparison tools
policies/ # Azure Policy definitions (JSON) for governance
automation/ # GitHub Actions workflows + pre-flight validation
tests/ # Pester test suites (8 test files, 27+ tests)
docs/ # Strategy, prerequisites, troubleshooting, operational runbook
For the full step-by-step operational workflow, see the Operational Runbook.
./discovery/Invoke-TenantDiscovery.ps1./cleanup/Invoke-CleanupDryRun.ps1./reporting/Get-CostSavingsProjection.ps1 -DryRunCsv ./cleanup-dryrun-{timestamp}/cleanup-dryrun.csv./cleanup/Invoke-CleanupExecution.ps1- PowerShell 7.x with Az modules (see docs/prerequisites.md)
- Permissions: Reader across all subscriptions for discovery, Contributor for cleanup
- Access to Entra ID (Azure AD) for owner cross-referencing