What it observes
What is assigned in a tenant, and whether the usage reports are permitted to name the people who hold it.
m365-governance collect licensing \
--tenant-url <tenant>.onmicrosoft.com \
--period D30 \
--output ./evidence/licensing/The address is the same --tenant-url every tenant slice takes. This collector
opens no SharePoint session with it: it reads Graph, and the address only says
which directory the evidence came from.
| Mode | Licensing |
| Needs | --tenant-url; --period for any usage read |
| Acquisition | Microsoft Graph, not PnP |
| Produces findings | No |
| Consumed by | Any viewer. No rule reads it. |
A different acquisition surface from every other collector
Every other collector here reads SharePoint through PnP. This reads the directory and the reporting endpoints through Microsoft Graph, with different modules, different permissions and a different sign-in.
Reports.Read.All is the least privileged permission Microsoft documents for
the usage reports, delegated or application; Organization.Read.All and
User.Read.All are the directory half. A delegated call also requires the
caller to hold an appropriate administrator role.
The modules are named by m365-governance doctor, with the acquisition surface
each one serves, so a machine that cannot run this says which install is
missing rather than reporting an empty tenant.
Four coverage areas, and none of them implies another
This is the whole shape of the collector, and the reason it produces no findings.
| Area | What it means |
|---|---|
assignment | Subscribed SKUs and per-user assignments, read from the directory. |
usage_identity | Whether the usage reports may name people. |
usage | A usage report, over a period the caller asked for. |
dependency | What a capability is required for. Nothing collects this. |
assignment = completed does not imply usage = completed, and neither implies
dependency = completed. A tenant can have every licence read and no usage
report; it can have the usage report and no permission to attribute it; and it
has never had dependency evidence, because this product collects none.
Concealed is an answer, not an absence
usage_identity is its own area because the three situations underneath it are
three different facts:
- completed: the setting was read and the reports may name people.
- partial: the setting was read and says they may not. Microsoft conceals identifiable user information by default, in the admin centre reports, the Microsoft 365 usage reports in Graph and Power BI, and the Teams admin centre reports.
- missing: nobody read the setting.
A report of ten thousand rows whose principal names are concealed supports no
statement about any person in it, so usage_rows and
usage_rows_naming_a_principal are two separate counts.
What the evidence carries
| Field | What it is |
|---|---|
population | entra-licensed-user-assignments |
acquisition_method | enumerated |
populations_not_observed | Licence models these reports do not cover, and capabilities granted outside this tenant's subscriptions |
subscribed_skus | How many SKUs the tenant holds |
skus | One row per SKU, as Microsoft returned it |
units_assigned | consumedUnits, summed |
report_identifiability | concealed or identifiable |
usage_window_days | The periods read, always an array, including when one period was asked for |
usage_report_refresh_date | The report's own statement of when its data was last rebuilt |
usage_rows / usage_rows_naming_a_principal | Rows returned, and rows that name a principal |
acquisition_attempts | Per surface: the operation, identity, result, reason, and whose limitation it is |
plans_with_one_source | Service plans delivered by exactly one of a person's assignments |
plans_with_more_than_one_source | Service plans another assignment also delivers |
dependency_evidence | partial. What one assignment uniquely delivers is calculable; what a capability is required for is not |
A SKU is a bundle, and a service plan is what somebody has
Counting SKUs does not tell you what anybody can do. A SKU carries service plans, an assignment can disable some of them, not every plan is a user capability, and two different SKUs can deliver the same plan. All four matter, and Microsoft documents each:
| Field | What it decides |
|---|---|
servicePlans[].appliesTo | User or Company. Only a plan that applies to a user is a capability a person holds |
servicePlans[].provisioningStatus | Success, Disabled, Error, PendingInput, PendingActivation, PendingProvisioning. A plan that is not provisioned is not a capability |
assignedLicenses[].disabledPlans | Plans switched off for this person, in this assignment |
subscribedSku.appliesTo | Only a SKU whose target class is User is assignable at all |
So what an assignment actually delivers is:
the SKU's service plans
minus that assignment's disabled plans
keeping only appliesTo = User and provisioningStatus = SuccessA plan delivered by exactly one of a person's assignments disappears if that assignment is removed. A plan delivered by more than one does not, because something else already carries it. Both are counted.
That is not a recommendation, and the distance between the two is the point. Two SKUs delivering one plan may differ in quota, in support, or in what a contract permits, and none of that is visible in this evidence. This plan also arrives elsewhere and this licence can be removed are different sentences, and only the first is established here.
What a capability is required for is not observable from an assignment or
from usage: a policy that depends on it, a role, a compliance obligation, a
workload that would stop. That is why dependency_evidence is partial rather
than complete, and why no optimisation conclusion is produced.
Every multi-valued field is an array at every cardinality. It is written
here because it was not true once: usage_window_days was built by a pipeline
whose input was wrapped and whose result was not, so it published a number for
one period and an array for two. A consumer parsing it as a number breaks on
the second period, and one parsing it as an array breaks on the first. The
engine now forces the shape after the whole pipeline and a gate checks zero,
one and many by type.
What has been read from a real tenant
Three of the four coverage areas have been acquired from a live directory, and the whole chain was crossed without manual repair between steps: collection, the evidence directory, the collection manifest, the independent reader, the canonical bundle, and a consumer opening it.
| Area | State |
|---|---|
assignment | acquired |
usage_identity | acquired, and the tenant read was concealed |
usage | acquired, with its window and refresh date |
dependency | collected by nothing |
The report returned rows and named nobody. That is recorded as usage
completed with usage_identity unavailable, never as usage unavailable: the
usage exists and cannot be attributed to a directory identity by this read.
No rule reads any of this, so evaluating it produces no finding. That is the expected outcome of an observation-only family and not a failed run.
There is no total number of licences
units_purchased existed and was removed. It summed prepaidUnits.enabled
across SKUs and returned a seven-figure total on a tenant holding a few dozen
assigned seats: arithmetically correct and meaningless, because the count means
something different on a paid seat SKU and on a free or effectively unlimited
one.
It was not replaced by a better total. An aggregate somebody hand-filtered is the same failure with more steps: it decides what a tenant holds by how the figures look.
consumedUnits is still summed, because an assignment means the same thing on
every SKU: somebody holds it.
What each subscription is, which is the question that comes first
The classification the paragraph above said this engine did not have now exists, and its answer is that nothing on this surface may be added to anything else on it. Every observed subscription carries two values and the documented rule that produced them.
capacity_kind, which is what the units are:
| Value | Established by |
|---|---|
user-assignable | appliesTo is User, the only target class Microsoft documents as assignable |
organisational | appliesTo is Company. These units were never seats anybody holds |
not-established | a target class outside the documented pair. Not an error, and not a licence to assume |
aggregation_eligibility, which is whether they may be counted with another
subscription's:
| Value | Meaning |
|---|---|
comparable | an established interpretation permits adding these units to another subscription's |
not-comparable | the number is valid and represents something else |
not-established | not enough authority to decide |
No subscription is comparable today, and that is the result rather than a
gap. Two things this surface does not carry decide it. Microsoft documents
self-service sign-up as provisioning services without asking you to take
action on their behalf, so a subscription being present in a tenant is not
evidence the organisation bought it. And prepaidUnits.enabled is documented
as the units enabled for the active subscription, which is a different
quantity from the seats a subscription carries.
A subscription is never excluded because its number looks wrong. A tenant holding a million units of one product and five hundred of another has them classified identically, because from this surface they are identical. A threshold would decide what a tenant holds by the size of its figures, which is the error the removed total made, with a filter in front of it.
The four prepaid counters
prepaidUnits is a licenseUnitsDetail, and Microsoft documents four counters
on it. This collector read the first and called it prepaid_units, which
flattens a subscription's lifecycle into one integer.
| Counter | What it counts |
|---|---|
enabled | units enabled for the active subscription |
warning | the grace period after the subscription expired, before cancellation |
suspended | after cancellation. Cannot be assigned, can still be reactivated |
lockedOut | after the customer cancelled |
A tenant whose subscription has expired has its units in warning, and a
single integer cannot say so: it reads zero, which looks like a subscription
holding nothing rather than one about to be lost.
Where the number everybody wants actually lives
Not here. companySubscription carries isTrial, documented as whether the
subscription is a free trial or purchased, and totalLicenses, documented as
the number of seats included in this subscription. It is at
/directory/subscriptions and subscribedSku.subscriptionIds is the join,
which this collector now captures.
That resource is in the beta endpoint only. The commercially authoritative
surface is not in v1.0, and that is a result about Microsoft Graph rather
than about this engine. Until an authorised read of it exists,
comparable_capacity is recorded as absent and names this measurement, never
estimated.
Whose limitation an absence is
Every acquisition surface records its attempt, and an absent one records who owns the limitation:
implementation: this version does not collect it yet. Our work.tenant-or-identity: the directory refused. A fact about the environment.microsoft: not exposed in this cloud. A fact about the platform.caller: not asked for, such as a usage read with no period.
Only the first is unfinished product. The others are evidence.
What this does not do
It produces no recommendation, no saving, no unused-licence count and no score. Concluding that a licence can be removed needs evidence of use and of dependency; this reads the first at best and the second not at all, and the absence is recorded as a fact so that a usage figure cannot be read as an answer.
What the Engine publishes
Generated from the Engine's capability manifest at1.0.0b8@2d4f5db76f21. No value on this panel is maintained by this site.
- Answers
- what is assigned in one tenant, and whether the usage reports are permitted to name the people who hold it
- Collector
- PnP PowerShell · mode
Licensing - Reads
Get-MgSubscribedSkuGet-MgUser -Property assignedLicensesGET /beta/admin/reportSettingsGET /v1.0/reports/getOffice365ActiveUserDetail
- Least privilege
Organization.Read.AllUser.Read.AllReports.Read.AllReportSettings.Read.All
- Produces
microsoft-365·tenant- Proven against a tenant
- partially live-validated, assignment, report identifiability and one usage report were observed against a real directory; the report returned rows and named nobody, and dependency evidence is collected by nothing
Rules that decide from it
No rule reads this evidence, and that is a recorded decision rather than a gap: the licensing evidence in a report, and any viewer is what consumes it. A threshold invented for a surface Microsoft publishes no position on would make a pass mean nothing.
Contracts it produces
Every collection writes evidence and an account of itself. The manifest publishes no narrower per-capability list, so this states what it does establish rather than guessing at more.