Deploying Conditional Access Policies via PowerShell

Back to Blog

Deploying Conditional Access Policies via PowerShell

There is a new GitHub repository available from Microsoft: Manage Conditional Access policies like code. Similar to the infamous Intune samples repo from which I and many others have built their automated Intune setup scripts for new tenants, this repo is replete with the resources that you need for accomplishing Conditional Access deployments via PowerShell script or application (Graph).

The Graph option has previously been available to us (in beta), but now it is generally available, and the PowerShell option is brand new. I love having both.

Recently, I blogged about a simpler Conditional Access baseline, designed with the SMB in mind. I have updated this baseline slightly, and made it available via my own GitHub repo. Here is a summary of the policy set:

  1. BLOCK – Legacy authentication: This policy blocks legacy clients such as IMAP, POP, and EAS.
  2. GRANT – Require MFA for admins: This policy will require Global admins to perform MFA. I decided to add this to the simple baseline, because very often it takes some extra time/effort to get all of the end-users prepared for MFA, whereas the admin policy can be deployed more quickly since there tend to be fewer. As well, in the SMB we rarely see folks delegating roles outside of Global admin, since there is usually only one or two individuals with the admin job function (and they tend to wear all the hats, so to speak).
  3. GRANT – Require MFA for all users: This policy does what it says; every user (admin or not) will be required to register for multi-factor and start using it.
  4. GRANT – Require MAM or MDM for mobile device access: This policy gives the customer a choice. They can either use the approved Microsoft apps such as Outlook and Edge (MAM), or they can enroll using the Company Portal app (for MDM), which would allow them to use the native or “built-in” email and web browser clients for iOS and Android. (UPDATE: This can also be broken into two policies, and you can choose to enforce MAM, MDM, or both!)
  5. GRANT – Require compliant device for Windows or Mac access: Desktop computers should be fully managed in order to gain access to corporate data. This policy will prevent access from unmanaged devices using client apps such as Outlook and OneDrive, which cache local data on the device. NOTE: You can also modify the policy to add the ‘Browser’ condition if you wanted to block web access from unmanaged computers.
  6. BLOCK – Unsupported device platforms: Platforms such as ChromeOS, Linux, etc. will be prevented from accessing corporate apps and data. You can also modify this policy to block any other platforms that you do not intend to support (e.g. if you do not want to support MacOS).

To run the script:

  1. Download it from GitHub
  2. For a newer tenant, you may have to disable ‘Security defaults‘ first
  3. Be sure that you have the AzureAD PowerShell module installed (Install-Module AzureAD)
  4. Connect to Azure AD (Connect-AzureAD); you must authenticate with Global admin, or Conditional Access admin, or Security admin roles
  5. Run the script!

When the policies show up, they will be disabled by default. This gives you a chance to plan the implementation and notify end users about the expected impacts. For example, legacy authentication will no longer work and they will be required to use MFA. Devices must be enrolled, etc. As well, it will create a security group called “Exclude from CA” and this group will be excluded from every Conditional Access policy. You should populate this security group with at least one emergency access account, and any other accounts which must be excluded from Conditional Access.

And that’s it!

Comments (26)

  • Tracy Ratz Reply

    Alex:

    Very cool indeed. The script can be modified to download the policies and then reimported into another tenant, no? This way after the initial consult, the tenant can take additional policies you want and applied to the tenant

    September 23, 2020 at 2:45 pm
    • Alex Reply

      When you use the Get cmdlet it will show all the policies but to see/store the details behind conditions, controls, etc. it would be a bit more complicated. To export/import stuff that is already present in the tenant, the graph option is probably best, where the policy contents are represented as JSON for example. From PowerShell, you have to use $policy = Get-AzureADMSConditionalAccessPolicy -PolicyID , and then you would use $policy.Condtions.ClientAppTypes, etc. to display items within the policy. So this method is good when you already know how to represent all of the policy attributes, but if you would like export/import I would suggest their Graph samples.

      September 23, 2020 at 3:26 pm
  • John Reply

    Alex – thanks for this. Very helpful for managing many tenants.

    Have you come across issues with this config and letting Power Automate flows run? The requirement for the device being managed is causing us issues, as flows are linked to individual user accounts. I don’t fancy setting up a service account that’s excluded from conditional access policies but also has been given access to all the myriad of resources all our different flows need access to…

    September 24, 2020 at 10:34 am
    • Alex Reply

      Yes this can mess with PowerAutomate attaching to stuff. You can also narrow down the scope to only Office 365, or Exchange Online for example, to put a wall up in front of the data that “matters most” (advise not to use ‘all cloud apps’). And as well, the most important thing to protect would be “Client apps” as those actually store copies of the data on the local device. Leaving web access open but still protected by MFA is sometimes preferable for certain environments, and may make it easier to execute certain flows that make HTTP call for example.

      September 24, 2020 at 11:03 am
  • Olin Reply

    Thanks, really helpful. Is it possible to Powershell the session access controls for sign in frequency? Have not been able to find that.

    September 25, 2020 at 1:16 am
    • Alex Reply

      Yes, it is possible. For example, take a policy that is already existing and use $policy = Get-AzureADMSConditionalAccessPolicy -PolicyID . With the entire policy stored in a variable, you can explore how the sub-settings are configured. For instance, you can type $policy.SessionControls.SignInFrequency and get the output:
      Type Value IsEnabled
      —- —– ———
      Days 30 True

      And that tells you how you would need to construct it if you were going to put the policy together in a script. I will do another post on this topic with additional security policies that go beyond the baseline.

      September 28, 2020 at 11:12 am
      • janne Reply

        Can u add exact script to configure this sign-in freq.?

        November 8, 2021 at 3:12 am
  • Henrik Damslund Reply

    Hi Alex, excellent scripts and I appreciate all the work you are putting into this site.
    I have been trying to create an CA policy for disabling OWA for Externals, and have used this script:

    ## This policy blocks access to OWA if not on Trusted Network
    $conditions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet
    $conditions.Applications = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessApplicationCondition
    $conditions.Applications.IncludeApplications = “00000002-0000-0ff1-ce00-000000000000”
    $conditions.Users = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessUserCondition
    $conditions.Users.IncludeUsers = “All”
    $conditions.Platforms = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessPlatformCondition
    $conditions.Platforms.IncludePlatforms = “All”
    $conditions.Locations = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessLocations
    $conditions.Locations.IncludeLocations = “All”
    #$conditions.Locations.ExcludeLocations = “00000000-0000-0000-0000-000000000000”
    $conditions.ClientAppTypes = @(‘Browser’)
    $controls = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls
    $controls._Operator = “OR”
    $controls.BuiltInControls = “Block”

    New-AzureADMSConditionalAccessPolicy -DisplayName “TEST – BLOCK – OWA for External Access” -State “Disabled” -Conditions $conditions -GrantControls $controls

    But when i executes it errors out with:
    New-Object : Cannot find type [Microsoft.Open.MSGraph.Model.ConditionalAccessLocations]: verify that the assembly containing this type is loaded.
    As far as I can see, the Type should be supported (https://docs.microsoft.com/en-us/graph/api/resources/conditionalaccessconditionset?view=graph-rest-1.0)

    Do you have any ideas, and could you verify if the script works for you?

    October 15, 2020 at 1:08 pm
    • Alex Reply

      Before you can use the Location condition you have to create the object for it. See in my script how I create the object for controls, etc. You would need a line that does similar for Location, then you are placing the location conditions within there.

      October 15, 2020 at 1:50 pm
      • Henrik Damslund Reply

        Thanks, I updated the script with the following:
        $conditions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet
        $conditions.Applications = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessApplicationCondition
        $conditions.Applications.IncludeApplications = “00000002-0000-0ff1-ce00-000000000000”
        $conditions.Users = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessUserCondition
        $conditions.Users.IncludeUsers = “All”
        $conditions.ClientAppTypes = “Browser”
        $conditions.Platforms = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessPlatformCondition
        $conditions.Platforms.IncludePlatforms = “All”
        $conditions.Locations = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessLocationCondition
        $conditions.Locations.IncludeLocations = “All”
        $conditions.Locations.ExcludeLocations = “00000000-0000-0000-0000-000000000000”
        $controls = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls
        $controls._Operator = “OR”
        $controls.BuiltInControls = “Block”

        New-AzureADMSConditionalAccessPolicy -DisplayName “TEST – BLOCK – OWA for External Access” -State “Disabled” -Conditions $conditions -GrantControls $controls

        But it not fails with:
        New-AzureADMSConditionalAccessPolicy : Error occurred while executing NewAzureADMSConditionalAccessPolicy
        Code: Internal Server Error
        Message: There was an internal server error while processing the request. Error ID: 5666fe48-9c4f-499f-b5f9-23afb2a54091
        InnerError:
        RequestId: f4b1b742-dc04-4d9d-9a96-abc4088c7234
        DateTimeStamp: Thu, 15 Oct 2020 20:52:16 GMT
        HttpStatusCode: InternalServerError
        HttpStatusDescription: Internal Server Error
        HttpResponseStatus: Completed
        At line:16 char:1
        + New-AzureADMSConditionalAccessPolicy -DisplayName “TEST – BLOCK – OWA …
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : NotSpecified: (:) [New-AzureADMSConditionalAccessPolicy], ApiException
        + FullyQualifiedErrorId : Microsoft.Open.MSGraphV10.Client.ApiException,Microsoft.Open.MSGraphV10.PowerShell.NewAzureADMSConditionalAccessPolicy

        Any suggestions?
        I even tried the Microsoft example with same error:

        $conditions = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet
        $conditions.Applications = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessApplicationCondition
        $conditions.Applications.IncludeApplications = “00000002-0000-0ff1-ce00-000000000000”
        $conditions.Users = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessUserCondition
        $conditions.Users.IncludeUsers = “all”
        $conditions.Locations = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessLocationCondition
        $conditions.Locations.IncludeLocations = “198ad66e-87b3-4157-85a3-8a7b51794ee9”
        $controls = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls
        $controls._Operator = “OR”
        $controls.BuiltInControls = “block”
        New-AzureADMSConditionalAccessPolicy -DisplayName “MFA policy” -State “Enabled” -Conditions $conditions -GrantControls $controls

        October 15, 2020 at 3:55 pm
        • Alex Reply

          For an “internal server error” I would probably contact MSFT. I copied/pasted the OWA policy you created above, and did not receive any error–it worked on first try.

          October 17, 2020 at 4:55 am
  • Patrick Reply

    Hi Alex,

    would you know the command for including/excluding device states per script? Let’s say I want a Conditional Access Policy which requires Browser MFA for all users and devices, but excludes the MFA requirement for devices which are compliant.

    I was expecting the Device State part of the script to look something like this but it does not work and I can’t find anywhere what I should use instead.

    $conditions.Devices = New-Object -TypeName Microsoft.Open.MSGraph.Model.ConditionalAccessDeviceStateCondition
    $conditions.Devices.IncludeDeviceStates = @(‘All’)
    $conditions.Devices.ExcludeDeviceStates = @(‘Compliant’)

    Thank you for your time and keep up your great work.

    January 16, 2021 at 4:30 pm
    • Alex Reply

      I could probably figure it out, but in the meantime I will offer an alternative that should be logically equivalent. Use the access controls to enforce managed devices and MFA, but select only require one of the above rather than all of the above controls. That means as long as one access control is met (e.g. compliant/hybrid device), then the other requirement is moot and prompt should not occur.

      January 21, 2021 at 2:43 pm
      • Patrick Reply

        That is a good tip, I will try that out for sure!

        Still curious though how DeviceStates can be managed through Powershell and I will keep on trying to get it to work. If you ever figure it out, please post your findings :)

        Thank you very much.

        January 22, 2021 at 8:21 am
  • Jocsan Villalobos Reply

    Hi Alex, How can you do to apply conditional access policy on azure SQL database specific? To block any actions from user can not have access to download massive data.

    Thanks

    February 4, 2021 at 4:21 pm
    • Alex Reply

      If you are building an application that leverages Azure SQL then the sign-in to that app would be what you apply the policies against. If you are just talking about logging into the Azure management portal, then there is a built-in app called Azure Management that you can select. That app is included of course in “All cloud apps” as well.

      February 5, 2021 at 11:25 am
      • Jocsan Villalobos Reply

        Hi, we need to create control hours logging into azure SQL database but this option isn’t available on azure but in active directory exist. We intention are make this control hours loggin (example: all the users can access at Azure SQL database at 7:00 a.m. to 5:00 p.m.), so obviously I see this process with office 365 administration but in azure SQL database not yet.

        February 5, 2021 at 11:52 am
        • Alex Reply

          Time bound controls are not a function of Conditional Access policies at this time.

          February 5, 2021 at 12:27 pm
      • Jocsan Villalobos Reply

        Hi, our intention is to create an access schedule to the azure sql database but do not exist this option in azure sql. So, we think to create script for this option, but not working yet.

        February 5, 2021 at 11:57 am
  • Stephen Reply

    Hello,
    I am trying to create a conditional access policy to Block High Risk Users and Sign-ins. Do you happen to know if the $condition.UserRiskLevels is available, when I try to set it to high im getting an error: The property ‘userRiskLevels’ cannot be found on this object. Verify that the property exists and can be set.
    Although I am able to set $conditions.SignInRiskLevels = “High” without issue?

    When I export the json relating to the conditional access policy that I manually created I can see that it does fall under Conditions:

    “conditions”: {
    “userRiskLevels”: [
    “high”
    ],
    “signInRiskLevels”: [
    “high”
    ],

    Thanks

    April 27, 2021 at 6:13 pm
    • Alex Reply

      Is that feature still in preview? If so you may need to check the AzureADPreview PowerShell module to see if it is available there.

      April 28, 2021 at 4:50 pm
  • Kimon Reply

    Hi Alex,

    I really appreciate the scripts that you have made. I have one question about running the Baseline-ConditionalAccessPolicies.ps1 script. When running the script within Azure AD, I receive the following error: “Cannot find type [Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet]: verify that the assembly containing this type is loaded.” Do you know the cause of this error and a potential fix? It does say load the assembly but how would one do this?

    July 15, 2021 at 9:51 am
    • Alex Reply

      Make sure you have the latest powershell module installed, or even use the AzureADPreview module, which will have the preview features for CA included.

      July 16, 2021 at 12:20 pm
  • Hank Reply

    Little late to the game here but Uncle Google found this article. First, thank you for putting this together. My questions revolves around how to enumerate the User ObjectID’s. When I run the command ((Get-AzureADMSConditionalAccessPolicy).Conditions).Users, it provides only 4 ObjectID’s in the IncludeUsers section. How do I get a complete list of all of them?

    August 5, 2022 at 11:12 am
    • Alex Fields Reply

      When working in the aad powershell module (support for which drops end of this year, btw–we are supposed to move to msgraph instead), I normally get a CA policy stored as a variable ($policy = Get-AzureADMSConditionalAccessPolicy…) and after a specific policy is stored as such, you can call $policy.Condtions.Etc.

      August 5, 2022 at 12:27 pm
      • Hank Reply

        Thank you Alex for the quick reply. I did it actually like this:

        ((Get-AzureADMSConditionalAccessPolicy -PolicyId “actual-policy-id-goes-here”).Conditions).Users

        Among the output is this:

        IncludeUsers : {“real objectid-1”, “real objectid-2”,
        “real objectid-3”, “real objectid-4″…}

        Notice how it only gave me 4 ObjectID’s and then the …} at the end? I would like it return all 407 UserID’s.

        August 5, 2022 at 12:34 pm

Leave a Reply

Back to Blog

Helping IT Consultants Succeed in the Microsoft Cloud

Have a Question? Contact me today.