In this section

Entra ID: Portal, Graph PowerShell and KQL

Module 0

Introduction

Entra ID is worked through three surfaces and they answer different kinds of question. You will finish this section able to say which surface a question belongs to before starting, and why the most common mistake is reaching for a query language when the question is about configuration.

Scenario

An analyst is asked whether the tenant requires MFA for administrators. They query SigninLogs for administrative accounts over thirty days, find that every successful sign-in shows multi-factor authentication satisfied, and report that the requirement is enforced. It is not. There is no policy requiring it. Every administrator in the tenant happens to have registered a strong method and used it voluntarily, and the one who has not was on leave for the whole period.

01

Two questions that sound the same

Is it configured, and what happened

The analyst answered a real question accurately. The problem is that they answered a different question from the one asked.

What happened is a question about events, and events live in log tables. Thirty days of administrative sign-ins all satisfying MFA is a true statement about the past.

What is configured is a question about the current state of the tenant, and it lives in the directory. Whether a policy exists that would require MFA of an administrator who did not volunteer one is not derivable from any number of sign-in records, because a log records what people did rather than what they would have been made to do.

A useful test is to ask what the answer would be if nobody had signed in at all. A question about configuration has the same answer in an empty tenant as in a busy one, because the policy either exists or does not. A question about events has no answer at all, and if your method produces one anyway, the method is measuring the wrong thing.

The gap between those two is not academic. It is the difference between an estate that is protected and one that has been lucky, and the two produce identical telemetry right up until the moment somebody registers a weak method or a new administrator is created.

The scenario's specific failure has a name worth knowing, because it recurs whenever telemetry is used to assess a control. The analyst measured compliance and reported enforcement. Compliance is what people did; enforcement is what they would have been prevented from not doing. In a population that happens to behave well, the two are indistinguishable in every log.

Two tenants. Identical logs. One is protected. TENANT A, enforced Policy requires MFA of all directory roles 40 admins, 40 satisfied MFA in 30 days A new admin created tomorrow is covered A weak method registered is refused TENANT B, lucky No policy exists 40 admins, 40 satisfied MFA in 30 days A new admin created tomorrow is not The one on leave has never been tested The second line is the only one a query can see, and it is the same in both

Figure 0.12. Thirty days of SigninLogs cannot distinguish these two tenants, and everything that matters is in the lines it cannot see.

The tell is the account that was on leave. A single administrator who did not sign in during the window is enough to break the inference, and nothing in the query would have flagged that the population was incomplete.

Keep this

A log cannot tell you what a control would have done. It records outcomes under the conditions that actually occurred, so a control that was never tested by circumstance leaves exactly the same trace as one that does not exist.

Which means the first decision in any investigation is which surface the question belongs to. Getting that wrong does not usually produce an error; it produces a confident answer to a question nobody asked, which is the harder failure to notice and the one the rest of this section is about.

02

Three surfaces, three shapes of answer

And what each is bad at

Each surface is excellent at something and unusable for something else, and the boundaries are sharper than people expect.

Which surface answers which question Column width is how much of that question type the surface actually covers Portal one object, in depth, with its context no answer across 461 objects Graph PowerShell every object, current state, exportable and repeatable weak on history KQL state: no everything that happened, at any scale, within retention what is configured what happened

Figure 0.11. The portal and KQL barely overlap. Graph is the surface that spans, which is why this course reaches for it most.

The reason the boundaries surprise people is that all three can be made to produce output for almost any question. The portal has a search box, Graph has an endpoint for nearly everything, and KQL will run against any table you point it at. Getting output is not the constraint; getting output that answers the question you asked is.

The portal is unmatched for one object. It shows a Conditional Access policy with its assignments resolved, its exclusions listed and a What If tool that will evaluate it against a hypothetical sign-in. Nothing else gives you that. It is also incapable of answering anything across a population: there is no portal view that lists every policy with a particular exclusion.

It is also the surface where an answer cannot be kept. A question asked in the portal is answered on screen and gone, so establishing the same thing next quarter means repeating the clicks, and there is no way to diff this quarter's answer against last quarter's.

Graph PowerShell answers about the whole tenant, in current state, and produces output you can keep. That combination is what makes it the operational surface: a question asked once in the portal is an answer, and the same question asked in Graph is a script that can be rerun next quarter.

It is also the surface most people underuse for the thing it is best at, which is establishing a baseline across a population. Counting how many service principals hold a credential older than a year is one command and it is the entire finding from section 0.6.

KQL answers what happened, at any volume, within retention. It cannot tell you what exists now, because the tables record events rather than objects, and an object nobody has touched inside the retention window appears in no table at all.

03

Answering the scenario properly

The question was about policy

The right answer to "does the tenant require MFA for administrators" is a policy question, and there are two ways to reach it.

Entra Admin Center

ProtectionConditional AccessPolicies, then What If
What If evaluates a hypothetical sign-in against every policy and tells you which apply and what they would require. It is the only place in the product that answers the counterfactual the scenario needed.

What If is worth understanding properly because it is the only counterfactual tool in the product. You describe a sign-in that has not happened, choosing the user, the application, the location, the client and the device state, and it reports which policies apply, which do not, and why each was excluded. That last part is the valuable half: knowing a policy did not apply is more useful than knowing which ones did, and it is where an exclusion nobody remembered surfaces.

That is the portal at its best: one question, one object, evaluated with its full context. It does not scale to asking the same thing of forty administrators, which is where the second route comes in.

Connect-MgGraph -Scopes "Policy.Read.All"

Get-MgIdentityConditionalAccessPolicy -All |
  Where-Object { $_.State -eq "enabled" } |
  Where-Object { $_.GrantControls.BuiltInControls -contains "mfa" -or
                 $_.GrantControls.AuthenticationStrength } |
  Select-Object DisplayName,
    @{n="Roles";e={ $_.Conditions.Users.IncludeRoles.Count }},
    @{n="Excluded";e={ $_.Conditions.Users.ExcludeUsers.Count }}

# (no output)
#
# No enabled policy requires MFA of any directory role in this tenant.

Two details in that command are worth noting. It checks for either a built-in MFA control or an authentication strength, because the newer authentication strength model is where phishing-resistant requirements live and a query looking only for the legacy control misses those tenants entirely. And it counts exclusions rather than listing them, which is deliberate: a policy requiring MFA of every administrator with eleven exclusions is a different control from the same policy with none, and the count is the first thing to look at.

No output, again, is the finding, and it is the same shape as section 0.4's workload policy check. Per section 0.2 an empty result deserves suspicion rather than relief, and the way to tell the two apart is to run the query without its filters and confirm it returns something.

04

Where KQL is the only answer

Volume, sequence and absence over time

None of that argues against KQL. It argues for using it on the questions it owns, and there are three shapes it owns completely.

Volume across a population. How many distinct accounts failed from one address in an hour is not a question the directory can answer, because it is about a pattern across thousands of events.

Sequence. A credential added, then a service principal authenticating from a new address forty minutes later, is a relationship between two events, and per section 0.5 that pairing is the detection where neither half works alone.

Absence over time. An account that used to sign in daily and stopped three weeks ago is a finding no state query can produce, because the directory holds the account as it is now and knows nothing about its rhythm. That shape matters more than it sounds: a workload identity going quiet is frequently the first sign that something was replaced, and a person going quiet is a leaver nobody offboarded.

Those three have a common property: they are about relationships between records rather than about any single record. Counting across a population, ordering two events in time, or noticing that something stopped are all comparisons, and comparisons are what a query language is for. No amount of Graph scripting substitutes, because Graph returns objects one at a time and the relationship is the thing you are looking for.

let creds = AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName == "Add service principal credentials"
| extend spn = tostring(TargetResources[0].displayName)
| project credAdded = TimeGenerated, spn, actor = tostring(InitiatedBy.user.userPrincipalName);
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(30d)
| where ResultType == 0
| join kind=inner creds on $left.ServicePrincipalName == $right.spn
| where TimeGenerated between (credAdded .. (credAdded + 4h))
| summarize firstUse = min(TimeGenerated), addresses = dcount(IPAddress) by spn, actor, credAdded
| where addresses > 0

The join is the part worth reading closely. It takes credential additions from the audit log, keeps the application name and who did it, then looks for that same application authenticating within four hours, and reports how many distinct addresses it used. Per section 0.5 neither half is a detection alone: credentials are added legitimately every week, and service principals authenticate constantly.

That is a question with no answer anywhere else. The directory knows the credential exists and knows nothing about when it was first used or from where, and the portal shows one application at a time with no join to sign-in activity at all.

05

Six questions, three surfaces

Assign each before reading on

Below are six questions of the kind this course keeps asking, and the exercise is to assign each to a surface before seeing which is right.

Work them in order and notice how the wording of each question points at a surface, once you know to look for it. Anything phrased as a count of things that exist is a state question. Anything with a time expression in it, since, over the last, still, is an event question. Anything asking what would happen is the portal, because it is the only surface that evaluates a hypothetical.

The two that catch people are the ones where the obvious surface produces an answer that is wrong rather than absent. A question that returns plausible output from the wrong surface is worse than one that returns nothing, because nothing prompts a second attempt and a plausible number gets reported. An empty result is at least an invitation to check the method.

The second pattern worth recognizing is a question that seems to be about events and is really about state. How many guests have not signed in for ninety days sounds like a log question and is answered badly by one, because a guest who has never signed in at all appears in no table and is therefore invisible to a query that starts from sign-in records. The correct route is to enumerate the guests from the directory and then check each against the log, which is Graph first and KQL second.

That is exactly the scenario at the top of this section. SigninLogs returned real data, correctly, and the conclusion drawn from it was false. Nothing about the query was wrong, nothing about the data was wrong, and the report was wrong, which is the combination that makes this failure hard to catch in review.

06

The habit, and why it saves time

One question before the tool

The habit is a single question asked before opening anything: am I asking what exists or what happened. It takes a second and it routes correctly almost every time.

If the answer is what exists, the surface is the portal for one object and Graph for a population, and no amount of log data will substitute. If it is what happened, the surface is KQL, and the directory will not help because it does not remember.

That question also protects against a subtler error, which is asking a state question of a log and getting a partial answer. Sign-in records will tell you about every application that authenticated in the window and nothing about the ones that did not, so an inventory built from logs silently excludes everything dormant, and dormant is where section 0.6's findings live.

Where a question genuinely needs both, and many of the interesting ones do, the order matters. Establish the state first, because it is cheap and it bounds the problem: knowing that four applications hold tenant-wide mail permissions turns an open-ended log question into a specific one about four names.

Keep this

State first, then events. A state query is fast, exact and needs no retention. It narrows what you then have to search for, and it frequently answers the question outright, which is the cheapest possible outcome.

The cost of getting this wrong goes past wasted effort. A question answered from the wrong surface produces a number that goes into a report, and the report is used to make a decision. The scenario's analyst did not waste an afternoon; they told the organization it had a control it did not have, and somebody planned around that.

Every module after this one uses all three, and the balance shifts by subject: id01 and id03 are mostly Graph because governance is a state problem, id04 is mostly KQL because detection is an event problem, and the portal appears throughout because it is where a control is read and understood before it is queried at scale.

07

Practice

Answer the scenario's question about your own tenant

Fifteen minutes, and it is worth doing before id04 because it establishes which surface your instincts reach for.

Do this Ask the same thing three ways
  1. Run the Graph command above against your tenant. If it returns nothing, run it again without the state filter to prove the query works before concluding anything.
  2. Use What If in the portal for one administrator and confirm the two agree. Where they do not, the portal is right and your filter is wrong.
  3. Query thirty days of administrative sign-ins in KQL and notice that the answer looks reassuring regardless of what the first two found.
  4. Write down which of the three you would have reached for first if nobody had asked the question this way. That instinct is the thing this section is trying to move.

What you should end up with: the actual policy position for administrators in your tenant, and a demonstration that the log-based answer would have agreed with either outcome.

The next section takes the measurement problem this module keeps circling, and asks what an honest coverage figure would look like.

// reasoning-review IS NAMED HERE BECAUSE IT EMITS ITS rc-code AT RUNTIME. // This condition scans the SERVER-RENDERED content, and reasoning-review.js builds its // artifact block after fetch, so the page contains no rc-code at the moment this runs and // code-chrome never loaded. Deployed 2026-08-26 with correct markup, transparent background // and no chrome, because the class the loader looks for did not exist yet. Any future // component that writes rc-code from script has to be named here too.