Microsoft Entra Best Practice Toolkit
Available at the approved release scope. The maintainer confirmed that public-release approvals were finalized on September 29, 2026. Availability covers the documented Conditional Access baseline; report-only defaults, emergency-access verification, tenant-specific pilot requirements, and existing feature restrictions remain unchanged.
⚠️ Write-capable. Pilot first, and keep a break-glass account. This product creates real Conditional Access policies in your tenant. Every new policy is report-only by default and the default configuration requires an emergency-access exclusion. Configuration and existing enforcing policies still matter; this is not a lockout guarantee. Security Defaults must be verified disabled before any tenant write. The toolkit never disables it or performs the transition automatically.
Every actual Conditional Access create or adoption write also revalidates the
current Graph account/tenant and the supplied emergency users/groups against
live directory and permanent Global Administrator role state. Standalone
writes require the intended Context.TenantId GUID as well as
Context.TenantAdminUpn; caller verification flags are not proof.
Assessment/WhatIf remain available without these additional write-only reads.
See standalone prerequisites.
PowerShell automation that deploys the Conditional Access baseline from the
Microsoft Identity Protection Best Practice Deployment guide for a Microsoft
365 Business Premium tenant (Microsoft Entra ID P1). Built to the same
operator contract as the Purview and Intune toolkits: configuration-driven,
-WhatIf/ShouldProcess on every write, a shared transient-retry boundary,
read-back verification, and structured JSON/HTML evidence.
First time here? Start with a preview
- Download or clone the approved repository version containing
Products\Entra. Keep the entire product folder, includingModulesandConfig, and use the documentation from that same version. - Open PowerShell 7 in an interactive desktop terminal and change to the repository root. Review the operator prerequisites before sign-in.
- Replace the sample administrator UPN and run:
cd .\Products\Entra
.\Deploy-EntraBestPractice.ps1 `
-TenantAdminUpn admin@contoso.onmicrosoft.com `
-WhatIf
This first preview needs no pilot group or high-risk approval switches.
It reads available tenant state and writes local evidence without changing
tenant configuration. It can still require sign-in and approved Graph consent.
If a required module is missing, approve its installation only if permitted,
or add -AutoInstallModules when CurrentUser installation is already approved.
Use the printed command to open the saved HTML report. The defaults are
Reports\entra-run-report.html and Reports\entra-run-log.json. Missing
emergency-access configuration can block deployment and make health
Indeterminate; that is not proof that the tenant has no emergency accounts.
No tenant action should be Created, Updated, or Adopted in a preview.
Do not disable Security Defaults or remove -WhatIf just to clear a warning.
Follow the pilot preparation and apply sequence
and use evidence and troubleshooting to
interpret blocked work or a failed health check.
Choose your starting point
| Need | Guide |
|---|---|
| Run the first preview, prepare a pilot, or apply an approved change | Operator guide |
| Understand the policies and their user impact | Scenarios |
| Interpret a report or recover from a failure | Evidence and troubleshooting |
| Configure and test emergency access | Break-glass guide |
| Plan Security Defaults transition or app-protection migration | Transition guide |
| Check licensing, API boundaries, and remaining gaps | Coverage |
What it deploys
Twelve P1 Conditional Access policies: the ten guide templates plus device-code
blocking and security-information registration protection. Each is created in the state configured by
ConditionalAccess.DefaultState (report-only by default):
| Policy | Recommended target |
|---|---|
| Require MFA for admins | On |
| Block legacy authentication | On |
| Require MFA for all users | On |
| Require MFA for guest access | On |
| Require MFA for Azure management | On |
| Require MFA for Microsoft admin portals | On |
| Block access for unknown/unsupported device platform | On |
| No persistent browser session | Report-only |
| Require app protection policy | Report-only |
| Require compliant/hybrid device or MFA for all users | Report-only |
| Block device code flow | Report-only, review device-code dependencies before enforcement |
| Protect security information registration | Report-only, verify combined registration and MFA bootstrap |
The optional phishing-resistant MFA for privileged/admin roles policy is
also P1, but is not selected by default. Set its policy reference’s Enabled
to $true in your private config after reviewing method readiness. It targets
the configured directory roles, not the pilot group, and defaults to
report-only. SMS, voice and ordinary MFA do not satisfy this strength.
P2 / Identity Protection risk policies are not deployed. Console output
distinguishes P1Baseline, P1Hardened and the P2 guidance boundary. See
coverage, prerequisites and current Microsoft references.
Plus emergency access (break-glass) handling (guide Priority 1): the toolkit
verifies a configured break-glass account/group before using it as an exclusion.
Opt-in creation of a cloud-only account stops the run before Conditional Access
writes. Complete credential setup, permanent Global Administrator assignment
and recovery testing, configure its object ID, then rerun verification.
Verified exclusions flow through a validated in-memory module result, never
through the editable entra-breakglass.json diagnostic file.
The “Require MFA for Intune enrollment” Conditional Access policy is intentionally not part of this product — it belongs to the Intune toolkit, which owns device-enrollment Conditional Access.
Safety model
- Security Defaults gate. Enabled or unknown means assessment-only across
every tenant writer, including emergency-account creation and opted-in
tenant settings.
Policy.Read.Allreads the state before setup and again at selected write boundaries. See the coverage and transition guide. - Report-only by default.
ConditionalAccess.DefaultStateisenabledForReportingButNotEnforced. Nothing is enforced until you promote it. - Break-glass is mandatory.
RequireBreakGlassExclusionistrue; the baseline refuses to create any policy until an emergency-access exclusion exists, and it injects that exclusion into every policy. See the break-glass guide. - High-risk gates. The Conditional Access write requires
-IncludeHighRisk(with-CustomerApprovalId),-BreakGlassExclusionsConfirmed, and-RollbackAcknowledged. Without them the module reports what it would create and changes nothing. - Pilot scope by default. All-users-style policies are scoped to a
-PilotGroupId; tenant-wide is an explicit-AssignTenantWideopt-in. The admin MFA and admin-portal policies target their configured directory roles across the tenant. Azure-management MFA targets all users for that application. Guest MFA targets all guests and external users acting in this tenant, not workforce members of the pilot group. These policies are not capped by the pilot group; review their scope and emergency exclusions before creating or enforcing them. - Existing policies. Current and configured legacy names are matched
across all result pages; ambiguous matches block creation. The app-protection
policy is review-only when present, including with
-AdoptExisting. It is not silently repaired, renamed or duplicated.
The corrected guest MFA, admin MFA, admin-portal, Azure-management, device-or-MFA and
browser-session policies also require ReviewExistingOnly = $true in custom
configuration. Existing matches remain unchanged and require manual review,
including with -AdoptExisting. This avoids silently changing the coverage or
grant requirements of an enforcing policy. The device-or-MFA template uses
an OR between compliant device, hybrid-joined device and MFA, not a requirement
to satisfy all three.
Creation readback checks the compared grants, targeting and emergency exclusions as well as state. For browser-session policies it also checks the configured session controls and device filter. Missing or changed fields are reported as a mismatch, not successful verification. Existing authentication strength policies remain manual-review only; ordinary MFA is not substituted for phishing-resistant authentication.
Guest MFA uses the documented includeUsers = ["GuestsOrExternalUsers"]
selector without group or role includes. It remains report-only by default.
The toolkit does not combine a guest selector with the pilot group, because
Conditional Access includes are a union, not a guest/group intersection.
Existing group-scoped guest policies are left unchanged for manual migration
review, including when they are enforcing or -AdoptExisting is supplied.
These templates are not a proven one-for-one replacement for Security Defaults. Protecting registration does not enroll everyone, and report-only device-code blocking does not block sign-ins. Both need explicit coverage review. Do not disable active protection and leave only report-only policies in its place. Use an approved transition with verified active replacement controls, not the toolkit’s first preview, for cutover.
New app-protection policies use compliantApplication and include Android/iOS
without excluding those same platforms. They require compatible apps and
assigned Intune app-protection policies. The legacy key/file
require-approved-client-apps is retained for configuration compatibility.
Use the migration procedure
for existing policies and update custom policy references to the current
LegacyDisplayNames / ReviewExistingOnly fields.
Authentication
Entra uses the same delegated Microsoft Graph sign-in behavior as Purview.
Supply the administrator UPN for a direct tenant run, or add the customer
tenant’s verified domain through -DelegatedOrganization for GDAP:
.\Deploy-EntraBestPractice.ps1 `
-TenantAdminUpn delegatedadmin@partner.onmicrosoft.com `
-DelegatedOrganization customer.onmicrosoft.com `
-WhatIf
The orchestrator connects once before any setup module runs. It requests the
delegated scopes in Config\EntraConfig.psd1, reuses a cached Graph context
only when the account and every required scope match, and verifies the live
tenant through /organization. For GDAP, it authenticates directly to the
customer domain and rejects a cached context for another customer.
After a fresh sign-in, the actual Graph account must also match
-TenantAdminUpn (case-insensitively) before successful connection evidence or
setup. A missing/wrong account or failed tenant verification stops the run and
attempts to disconnect the invalid context; consent scopes are unchanged.
Current Microsoft Graph Authentication modules use Windows Authentication
Manager (WAM) on a supported interactive Windows desktop. A first consent,
missing scope, account change, or customer-tenant switch can still require a
browser prompt. Use -AutoInstallModules to install a missing Graph
authentication module to CurrentUser without a toolkit prompt.
-NonInteractive suppresses toolkit confirmation and module-install prompts.
It does not select application authentication and cannot guarantee a
prompt-free first sign-in. Pre-authenticate in an approved interactive session
before using it. Client IDs, certificates, and client secrets are not accepted.
Setup modules use the orchestrator’s verified context and do not reconnect. To run a setup module directly, establish the Graph connection first in the same PowerShell session.
Usage
Start in Products\Entra in an interactive PowerShell 7 terminal:
# Preview: no tenant configuration writes; sign-in and local reports still run.
.\Deploy-EntraBestPractice.ps1 -TenantAdminUpn admin@contoso.onmicrosoft.com -WhatIf
The first preview prints all missing Conditional Access apply parameters before
sign-in, even when no break-glass IDs are configured. Removing -WhatIf alone
does not authorize Conditional Access deployment.
| Apply prerequisite | What to supply or verify |
|---|---|
| Private configuration | Copy the complete Config\EntraConfig.psd1 outside the repository, then supply -ConfigPath. Keep DefaultState report-only and tenant-setting Apply flags false unless separately approved. |
| Emergency access | Configure verified ConditionalAccess.BreakGlass.ExcludeUserIds or ExcludeGroupIds. Independently test recovery and keep the emergency-access verification module selected. |
| Change approval | -IncludeHighRisk -CustomerApprovalId '<approved-change-reference>'. Only a hash of the reference enters evidence. |
| Safety confirmations | -BreakGlassExclusionsConfirmed -RollbackAcknowledged. These record operator decisions; they do not configure recovery for you. |
| Pilot scope | -PilotGroupId '<entra-group-object-id>'. Role-, guest- and app-scoped templates retain their configured targeting; the pilot is not an intersection with privileged roles or guest users. |
| Live readiness | Verified tenant identity, effective Graph scopes/roles, P1 entitlement and disabled Security Defaults. Disabled is not proof of active replacement protection. |
After an approved Security Defaults transition, or independent confirmation of an already-disabled tenant’s protection, preview the exact intended write:
# Later pilot preview: only after an approved Security Defaults transition
# or independent confirmation of an already-disabled tenant's protection.
# Use a complete private config with verified existing break-glass IDs,
# report-only DefaultState, and tenant-setting Apply flags still false.
.\Deploy-EntraBestPractice.ps1 -TenantAdminUpn admin@contoso.onmicrosoft.com `
-ConfigPath '<private-config-path>' `
-IncludeHighRisk -CustomerApprovalId 'CHG0001234' `
-BreakGlassExclusionsConfirmed -RollbackAcknowledged `
-PilotGroupId '<entra-group-object-id>' -WhatIf
Read the reports before a separately approved apply. Disabled Security Defaults passes one gate, not all safety or authorization checks. The state is never automatically changed, and there is no bypass switch.
The final output prints the full paths of successfully saved HTML and JSON
reports and a copyable Invoke-Item -LiteralPath command to open the HTML.
This also runs for -WhatIf, blocked work, and failures after logging has
initialized. The browser never opens automatically. A report-write warning
means that file was not confirmed saved; an older file at the same path is
not evidence of the current run.
The default files are Reports\entra-run-report.html and
Reports\entra-run-log.json, relative to Deploy-EntraBestPractice.ps1.
Override them in the private configuration’s Report section if needed.
Reruns overwrite these filenames, so preserve each run’s evidence in an
approved private location before rerunning. Do not commit tenant reports.
For that approved apply, run the same command with the same private config,
parameters and tenant, omitting only -WhatIf. Creating report-only policies
and promoting them to enforcement are separate changes. -NonInteractive
does not waive any approval or readiness requirement.
Tenant-wide assignment is not the default: it requires -AssignTenantWide,
-RollbackAcknowledged and
Assignment.AllowTenantWideAssignmentForHighRisk = $true in configuration.
Do not use it merely to avoid preparing a pilot group.
Reading the console
Numbered stages show prerequisites, tenant discovery/Security Defaults, emergency access, Conditional Access assessment/deployment, optional tenant settings, read-only validation and the final summary. The CA stage separates existing-policy discovery, P1 baseline and optional P1 hardening.
Information is cyan, successful operations and verified reads are green,
warnings/blocked/guided-only/retry outcomes are yellow, failures are red and
skips are gray. Text status/disposition labels remain visible without color.
VALIDATED means the recorded readback check passed, not full tenant security
validation. Stages and module results also appear in the HTML/JSON evidence.
The summary uses OK, FAILED, SKIPPED and BLOCKED; a completed process
does not mean every requested operation was applied.
Policy naming and selection
Existing names remain the default. For new policies, set
ConditionalAccess.DisplayNamePrefix = 'SMB-CA-' in your complete private
configuration. Each reference supplies a stable tier, sequence and scope
NameCode, producing names such as
SMB-CA-P1-11-Users-Block device code flow.
Naming is descriptive, not an assignment or licensing gate. The purpose
comes from the template; actual scope is determined by its conditions and the
approved run parameters. The toolkit recognizes both prefixed and original
names and does not rename existing policies. Multiple matches block the run.
If you later change or remove a prefix, retain the old value in
PreviousDisplayNamePrefixes; for a changed NameCode or manual rename, add
the exact previous full name to that reference’s LegacyDisplayNames.
Policy references can set Boolean Enabled = $false to exclude an item from
deployment and the selected-policy health inventory. This does not disable or
delete a tenant policy. Existing enforcing policies are still checked for
emergency-access risk. The new device-code, registration and hardened-admin
references require ReviewExistingOnly = $true: existing matches are assessed
without automatic migration, including with -AdoptExisting.
Even when compared fields match, these policies report GuidedOnly manual
review rather than verified compliance. Matching fields do not establish
application, registration or authentication-method readiness.
Deployment health check
After earlier tasks complete, the run finishes with a read-only health
check for Security Defaults readiness and emergency-access drift. It issues GET requests
only, changes nothing, and is safe to run on a schedule. Skip it with
-SkipDeploymentHealth.
The check exists mainly to catch one thing: break-glass exclusion drift. Every policy the toolkit creates excludes your emergency-access principals. If someone later edits a policy and removes that exclusion, nothing appears broken while the policy is report-only, and then the tenant locks every administrator out the moment that policy is promoted to enforcing.
It reports a verdict rather than stopping the run:
| Verdict | Meaning |
|---|---|
Healthy |
Security Defaults is verified disabled, no legacy migration remains, and the implemented policy-presence, state and emergency-access checks passed. This is not a full coverage or sign-in guarantee. |
DriftDetected |
A readiness or drift finding needs review, including enabled Security Defaults, a legacy name, missing/duplicate policy, unknown policy state or an exclusion gap. Enabled Security Defaults alone is not a security defect; toolkit writes remain blocked. |
LockoutRisk |
Emergency access is compromised right now. An enforcing policy (managed or customer-authored) does not exclude a break-glass principal; a break-glass account is disabled, has lost Global Administrator, or cannot be read; or a break-glass group has no enabled member or no longer provides Global Administrator. Act on this first. |
Indeterminate |
Security Defaults or an emergency-access control could not be fully evaluated. Never accept an unknown state as Healthy. More severe lockout findings retain priority. |
The summary line carries the counts behind the verdict, for example:
Verdict=Healthy; ExclusionsEvaluated=True; UnmanagedPoliciesEvaluated=True;
ManagedPoliciesConfigured=12; Present=12; Missing=0; Unevaluable=0;
DuplicateNames=0; Enforcing=0; Disabled=0; UnknownState=0;
EnforcingPoliciesInTenant=0;
BreakGlassExclusionGaps=0; LockoutRisks=0; UnmanagedEnforcingRisks=0;
BreakGlassAccountsDisabled=0; BreakGlassAccountsWithoutRole=0;
BreakGlassGroupsUnusable=0; BreakGlassGroupsWithoutRole=0;
BreakGlassPrincipalsUnreadable=0; Assessment=ReadOnly.
SecurityDefaultsState=Disabled; TenantWritesAllowed=True; LegacyPolicies=0.
TenantWritesAllowed covers the Security Defaults gate only. Enabled or
unknown state adds a blocked disposition; every selected writer independently
checks the live state. A completed run can still contain blocked findings.
Read ExclusionsEvaluated first. When it is False no break-glass principal
was available, so every exclusion-related counter is 0 because the check did
not run, not because nothing was found. EnforcingPoliciesInTenant is still
counted in that case, and a tenant that is enforcing access with no configured
break-glass principal is reported as a finding in its own right.
UnmanagedPoliciesEvaluated=False means the unmanaged-policy check could not
run. This happens when no break-glass principal is available, or when the
toolkit could not build the complete managed-policy name set and therefore
could not safely distinguish toolkit policies from customer-authored ones. The
overall verdict is Indeterminate, never Healthy.
The check fails closed everywhere it can:
- If the policy list cannot be read, or any page of it fails, no verdict is produced at all.
- A policy state outside the three documented Conditional Access states is treated as potentially enforcing rather than assumed report-only.
- A break-glass principal that cannot be read, or whose role cannot be confirmed, is treated as unavailable rather than assumed intact.
- Enforcing policies the toolkit does not manage are checked too, because a customer-authored policy can lock the tenant out just as easily. Their display names are customer data and are not written to evidence, so the finding points at the portal instead.
What it does not cover
The check now includes Security Defaults state, but it does not prove replacement coverage, perform a transition, or verify complete per-policy body or assignment drift beyond the emergency-access checks. Legacy policy presence is recorded for migration review, not proof of effective protection. Treat the verdict as readiness and emergency-access health, not a full tenant security assessment.
Break-glass role verification (permanently assigned Global Administrator)
runs twice by design: Setup-EmergencyAccess verifies it before policy
creation, and the health check verifies it again to catch later drift. The two
modules use the same shared direct-or-group role check. The check reads active
role assignment schedule instances and accepts only tenant-wide assignments
with no expiry. Temporary PIM activations and expiring assignments do not
qualify. Pagination covers role assignments and group memberships.
Known gaps (Product Group asks)
See docs/Coverage.md for the full mapping and the Product-Group ask list, including:
- Registration readiness: the new policy protects registration with MFA, but does not enable combined registration, issue Temporary Access Passes, configure authentication methods or force every user to register.
- Guest migration readiness: the guest MFA policy targets guests and external users across the tenant. An existing group-scoped policy needs manual migration review; the toolkit does not prove guest MFA enrollment, cross-tenant trust or sign-in compatibility.