Azure waste

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.
The agent reports this
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 = cfopEnv

A 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/services
What 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.