Azure waste

IoT Hub nobody is sending to

A billed IoT Hub that received no device telemetry at all across the window, while its units bill every month.
The agent reports this
Why it happens

An IoT Hub bills per provisioned unit per month whatever the message volume, so a hub kept after a pilot ended costs exactly what one carrying production telemetry costs: $25 a month for an S1 unit, $250 for an S2 and $2,500 for an S3, multiplied by the number of units. The rule reads the total of d2c.telemetry.ingress.success across the window, which Microsoft classifies as a single-point metric, meaning Azure registers a 1 for each message and only the sum carries information. Reading the average would be useless here, because Microsoft documents that the average, minimum and maximum of a single-point metric are always 1 whether the hub carried one message or ten million. The free tier is excluded because it bills nothing. Connected device count travels as evidence rather than as the test, since it is a snapshot metric with no sum available. What this cannot see travels on every finding: a hub provisioned ahead of a rollout, or one used only for cloud-to-device commands and direct methods, sends no telemetry and looks exactly like an abandoned one, which is why this reports and a person decides. Deleting a hub destroys its device identity registry, and every device would need re-provisioning with new credentials.

Paste this into Resource Graph Explorer in the Azure portal.

resources
| where type =~ 'microsoft.devices/iothubs'
// 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')
// The free tier bills nothing, so it is never a saving.
| extend hubTier = tostring(coalesce(sku.name, properties.sku.name))
| where hubTier !startswith 'F'
// Units multiply the monthly meter: an S2 with 3 units is 3 x $250.
| extend hubUnits = toint(coalesce(sku.capacity, properties.sku.capacity, 1))
| project name, resourceGroup, location, tier = hubTier, units = hubUnits, environment = cfopEnv

This query lists the hubs the check considers, and it is NOT the check. Resource Graph holds no metrics, so it cannot tell you which of these received no telemetry. The rule decides on the TOTAL of the d2c.telemetry.ingress.success metric across the window, and total is the only aggregation that carries information: Microsoft documents that the average, minimum and maximum of this metric are always 1, whether the hub carried one message or ten million. Every hub this query returns is a candidate, not a finding.

Applies to microsoft.devices/iothubs
What the agent does about it

A billed IoT Hub that received no device telemetry at all across the window, while its units bill every month.

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.