azureblog.co.uk
← cd ~/learn
// hands-on lab · intermediate

Build a Conditional Access baseline in report-only

Create three baseline policies in report-only mode with Graph PowerShell, measure what they would have done for two weeks, then switch them on with no surprises.

time
1 hour, then 1 to 2 weeks of watching
level
Intermediate
cost
Entra ID P1. Log Analytics ingestion if you use the workbook.
You'll need
  • Conditional Access Administrator or Security Administrator
  • Break-glass accounts to exclude (see the first lab)
  • Sign-in logs sent to Log Analytics, for the workbook and the query in step 5
  • Microsoft Graph PowerShell installed
0 of 7 steps done
Ticks are saved in this browser only. Checked 9 Oct 2026: confirm details in Microsoft's documentation before using in production.
  1. 01

    Connect and collect the exclusions

    Every policy in this lab excludes your . Put them in a group if you haven't already, so new policies only need one exclusion.

    powershell
    Connect-MgGraph -Scopes "Policy.ReadWrite.ConditionalAccess","Policy.Read.All","Group.Read.All"
    $bg = (Get-MgGroup -Filter "displayName eq 'CA-Exclude-BreakGlass'").Id
  2. 02

    Block legacy authentication

    Legacy protocols such as basic-auth IMAP and POP can't do , so they're a common way round it. The client app types exchangeActiveSync and other cover them. The state enabledForReportingButNotEnforced is .

    powershell
    $p = @{
      displayName = "CA001 Block legacy authentication"
      state = "enabledForReportingButNotEnforced"
      conditions = @{
        users = @{ includeUsers = @("All"); excludeGroups = @($bg) }
        applications = @{ includeApplications = @("All") }
        clientAppTypes = @("exchangeActiveSync", "other")
      }
      grantControls = @{ operator = "OR"; builtInControls = @("block") }
    }
    New-MgIdentityConditionalAccessPolicy -BodyParameter $p
  3. 03

    Require MFA for all users

    powershell
    $p = @{
      displayName = "CA002 Require MFA for all users"
      state = "enabledForReportingButNotEnforced"
      conditions = @{
        users = @{ includeUsers = @("All"); excludeGroups = @($bg) }
        applications = @{ includeApplications = @("All") }
        clientAppTypes = @("all")
      }
      grantControls = @{ operator = "OR"; builtInControls = @("mfa") }
    }
    New-MgIdentityConditionalAccessPolicy -BodyParameter $p

    If you use or service accounts that sign in as users, list them now. They'll show up in the report-only results in step 5, and you can decide whether to exclude them or move them to .

  4. 04

    Require phishing-resistant MFA for admins

    This policy targets directory roles and uses the built-in . Its ID, 00000000-0000-0000-0000-000000000004, is the same in every tenant. The role IDs below are , Privileged Role Administrator, Security Administrator and Administrator; add any others you use.

    powershell
    $roles = @("62e90394-69f5-4237-9190-012177145e10", "e8611ab8-c189-46e8-94e1-60213ab1f814",
               "194ae4cb-b126-40b2-bd5b-6091b380977d", "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9")
    $p = @{
      displayName = "CA003 Phishing-resistant MFA for admins"
      state = "enabledForReportingButNotEnforced"
      conditions = @{
        users = @{ includeRoles = $roles; excludeGroups = @($bg) }
        applications = @{ includeApplications = @("All") }
        clientAppTypes = @("all")
      }
      grantControls = @{ operator = "OR"; authenticationStrength = @{ id = "00000000-0000-0000-0000-000000000004" } }
    }
    New-MgIdentityConditionalAccessPolicy -BodyParameter $p
  5. 05

    Measure for one to two weeks

    Report-only policies are evaluated at every sign-in and the result is logged, but nothing is enforced. Open any sign-in in the Entra admin center and look at the Report-only tab, or use the Conditional Access insights and reporting workbook. To see everything at once, run this in :

    kql
    SigninLogs
    | where TimeGenerated > ago(14d)
    | mv-expand ca = ConditionalAccessPolicies
    | where tostring(ca.displayName) startswith "CA00"
    | summarize Signins = count(), Users = dcount(UserPrincipalName) by Policy = tostring(ca.displayName), Result = tostring(ca.result)
    | order by Policy asc, Signins desc

    reportOnlyFailure means the policy would have blocked that sign-in. reportOnlyInterrupted means the user would have been asked for MFA they didn't do. Those two are what to investigate.

  6. 06

    Fix what would break

    For each user or app in the failure results, decide: register MFA (send them a or the registration link), move an app off legacy authentication, or add a documented, time-limited exclusion. Use the What If tool to check a specific user before switching on.

  7. 07

    Switch on, one policy at a time

    Start with the legacy authentication block, which usually has the smallest impact, and leave a few days between each policy. Tell the service desk first.

    powershell
    $id = (Get-MgIdentityConditionalAccessPolicy -Filter "displayName eq 'CA001 Block legacy authentication'").Id
    Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $id -State "enabled"

    Keep the report-only query saved. It's also the quickest way to test any policy you add later.