30-second answer

Because an empty list and an unreadable one look identical. On the identity tested here, Get-PnPSiteCollectionAdmin returned no entries and no error for a group-connected site whose administrator a delegated session saw in the same minute. Nothing distinguishes that from a site that genuinely has none, so a script cannot treat zero as a count.

What this proves

One site, read twice on 18 August 2026, minutes apart, with PnP.PowerShell 3.3.0.

IdentityAdministrators returnedError
Delegated, interactive, tenant administrator1 (a Microsoft 365 group)none
Application-only, certificate, Sites.Read.All0none

The site is group-connected. Its administrator is the group, and the delegated read returned it as a principal of type group. The application-only read returned an empty collection and completed normally.

That is the whole finding: the empty result was silent. There was no Access denied, no exception to catch, and nothing in the return value that a caller could inspect to tell the two situations apart.

What it does not prove

It does not establish that this is how every application-only identity behaves. One identity was tested, holding Sites.Read.All and nothing else, against one tenant, on one date, with one module version. Whether a wider application role returns the group, and whether the behaviour is the same for a site whose administrator is an individual rather than a group, was not established here.

It does not establish why. A plausible reading is that resolving a group-backed administrator needs a directory read the application was not granted, but nothing observed here settles that, and the error that would have said so did not occur.

And it does not say the portal is wrong. The portal and the delegated session agreed.

PowerShell

PowerShell
# As a person
Connect-PnPOnline -Url https://<tenant>.sharepoint.com/sites/<site> `
  -Interactive -ClientId $appId
Get-PnPSiteCollectionAdmin | Select-Object LoginName, PrincipalType

# As the application, same site
Connect-PnPOnline -Url https://<tenant>.sharepoint.com/sites/<site> `
  -ClientId $appId -Tenant $tenantId -CertificatePath ./app.pfx `
  -CertificatePassword $password
Get-PnPSiteCollectionAdmin | Select-Object LoginName, PrincipalType

Example output

Text
LoginName                                             PrincipalType
---------                                             -------------
c:0o.c|federateddirectoryclaimprovider|<group id>_o   SecurityGroup

LoginName                                             PrincipalType
---------                                             -------------

The second block is not truncated. It is the whole result.

Explanation

The failure mode is that nothing fails.

A script that reads administrators almost always looks like this: call the cmdlet, catch what it throws, and treat what comes back as the answer. Here nothing throws, so the catch never runs, and an empty collection flows into whatever counts it. $admins.Count is 0, and zero is a number that looks exactly like a measurement.

The consequence is a confident wrong answer rather than a missing one. A report built this way says this site has no administrators, which is a finding somebody will act on: they will go and add one to a site that already has an owner, and the real state of the tenant will be less clear afterwards than it was before. That is worse than saying nothing, because silence invites a second look and a wrong number does not.

Count what you could establish, and say when you could not:

PowerShell
$admins = @(Get-PnPSiteCollectionAdmin)
if ($admins.Count -eq 0) {
    'not established: no entries returned. An identity that cannot resolve ' +
    'them returns the same empty list as a site that has none.'
}
else {
    "$($admins.Count) administrators"
}

The same care applies one level down. Where an administrator is a group, the number of people who can act is the group's membership, which this cmdlet does not expand. A count of administrators is not a count of people.

Production considerations

  • Verify an application-only script against a delegated read of the same resource before trusting its counts. The two identities are permitted to resolve different things, and the difference is silent.
  • Prefer evidence that records which identity produced it. A number without the identity beside it cannot be re-examined later, because the question "could this identity have seen that?" is no longer answerable.
  • An empty result deserves the same scrutiny as an error. Errors are loud and get handled; empties are quiet and get counted.

None directly. What this establishes is a limit on what an empty result means, which is a precondition for counting anything, not a claim about a tenant.

References

Found something wrong? Suggest a correction. The article source is not public; the engine it cites is.

What this answer underwrites

The engine reads these Microsoft operations to collect evidence, so what is established here is what those collectors rest on.

  • ownerswho administers one site