Premium migration service with no migration projects
An Azure Database Migration Service instance on the Premium tier with no migration projects, which bills for provisioned compute every hour while having nothing to migrate.
Why it happens
Database Migration Service exists to finish. It is provisioned for a specific move, and when that move completes nothing deletes it, because the project that created it has ended and no one is left watching its line item. The Premium tier bills hourly on provisioned compute whether or not a migration is running: $224.84 a month for 4 vCores, $449.68 for 8 and $899.36 for 16, verified against the live retail API. An instance with no migration projects has nothing to perform, so the compute buys nothing at all. Only the Premium tier is flagged, because Microsoft publishes the Standard tier as free, and a finding against a free resource would be an invented saving.
Paste this into Resource Graph Explorer in the Azure portal.
resources
| where type =~ 'microsoft.datamigration/services'
// Scope below matches CloudFinOpsPro policy DEFAULTS: exclusions.protectTag, environment.tagKeys,
// environment.allowedTagValues, and allowUntagged false so an untagged resource counts as production.
// If your policy.yaml differs, edit the three lines below or this will disagree with your own agent.
| where tolower(tostring(coalesce(tags['cfop:protect'], ''))) !in ('true', 'yes', '1')
| extend cfopEnv = tolower(trim(' ', tostring(coalesce(tags['Environment'], tags['environment'], tags['env'], ''))))
| where cfopEnv in ('dev', 'development', 'test', 'qa', 'sandbox', 'nonprod', 'non-prod', 'staging')
| where tostring(sku.name) startswith 'Premium'
| extend serviceId = tolower(id)
| join kind=leftouter (
resources
| where type =~ 'microsoft.datamigration/services/projects'
| extend serviceId = substring(tolower(id), 0, indexof(tolower(id), '/projects'))
| summarize projects = count() by serviceId
) on serviceId
| extend projects = coalesce(projects, 0)
| where projects == 0
| project name, resourceGroup, location, sku = tostring(sku.name), projects, environment = cfopEnvA migration service with no projects has nothing left to perform, so counting the projects is the whole check and a query that merely listed services would report every one, including the one running a migration right now. microsoft.DataMigration/services/projects is indexed by Resource Graph, verified against the generated supported-types reference rather than assumed, so the count can be done here; the parent id is derived by cutting each project id at /projects, lower-cased on both sides because resource ids do not agree on casing. The sku filter keeps only the Premium tier, because Microsoft publishes the Standard tier as free and an idle free service is not a saving. This is not a threshold: a service either has projects or it does not. What is not proven is the selection on real migration services, because the reference subscription owns none, and the vCore count in the sku name decides the rate rather than this query.
Applies to microsoft.datamigration/servicesWhat the agent does about it
An Azure Database Migration Service instance on the Premium tier with no migration projects, which bills for provisioned compute every hour while having nothing to migrate.
It will not fix this one
It reports this and takes no action. A check only earns the right to change something once the action behind it can re-read live state before it fires and knows the way back.
Related checks
HDInsight worker nodes without autoscale
A Spark, Hadoop or Interactive Query cluster whose worker role has no autoscale configured, so it pays for peak worker capacity every hour of every day.
SQL Managed Instance without Azure Hybrid Benefit
A SQL Managed Instance paying the licence-included rate, which a customer holding SQL Server licences with Software Assurance can lower without downtime.
Premium App Configuration store on a non-production store
An App Configuration store on the Premium tier while tagged non-production.