Public/ConditionalAccess.ps1

<#
.SYNOPSIS
    Retrieves Conditional Access policies with optional filtering and summary metadata.
.DESCRIPTION
    This function queries Conditional Access policies via Microsoft Graph and allows filtering by state. It returns both the raw policies and a lightweight summary view so you can quickly review state, app/user targets, and grant/session controls before making changes.
.PARAMETER State
    Filter policies by state. Valid values: Enabled, Disabled, ReportOnly, All (default).
.PARAMETER IncludeSummary
    If set, returns an additional Summary property for each policy with common fields.
.EXAMPLE
    Get-O365ConditionalAccessPolicy -State ReportOnly -Verbose
.EXAMPLE
    Get-O365ConditionalAccessPolicy -State Enabled -IncludeSummary | Format-Table -AutoSize
.NOTES
    Requires Microsoft Graph permissions: Policy.Read.All at minimum.
    Source references: Office365itpros Conditional Access guidance; learn.microsoft.com/graph/api/conditionalaccessroot-list-policies.
#>

function Get-O365ConditionalAccessPolicy {
    [CmdletBinding()]
    param(
        [ValidateSet('Enabled','Disabled','ReportOnly','All')]
        [string]$State = 'All',
        [switch]$IncludeSummary
    )

    $policies = Get-MgIdentityConditionalAccessPolicy -All

    if ($State -ne 'All') {
        $stateMap = @{
            'Enabled'    = 'enabled'
            'Disabled'   = 'disabled'
            'ReportOnly' = 'enabledForReportingButNotEnforced'
        }
        $targetState = $stateMap[$State]
        $policies = $policies | Where-Object { $_.State -eq $targetState }
    }

    if (-not $IncludeSummary) {
        return $policies
    }

    $policies | ForEach-Object {
        $summary = [PSCustomObject]@{
            DisplayName     = $_.DisplayName
            State           = $_.State
            Id              = $_.Id
            Users           = $_.Conditions.Users | Select-Object -Property Users, IncludeUsers, ExcludeUsers, IncludeRoles, ExcludeRoles, IncludeGroups, ExcludeGroups, IncludeGuestsOrExternalUsers
            CloudApps       = $_.Conditions.Applications | Select-Object -Property IncludeApplications, ExcludeApplications, IncludeUserActions
            Platforms       = $_.Conditions.Platforms | Select-Object -Property IncludePlatforms, ExcludePlatforms
            GrantControls   = $_.GrantControls | Select-Object -Property BuiltInControls, Operator, CustomAuthenticationFactors, TermsOfUse
            SessionControls = $_.SessionControls | Select-Object -Property ApplicationEnforcedRestrictions, CloudAppSecurity, PersistentBrowser, SignInFrequency
        }
        $_ | Add-Member -MemberType NoteProperty -Name "Summary" -Value $summary -PassThru
    }
}

<#
.SYNOPSIS
    Changes the state of a Conditional Access policy (enable, disable, report-only).
.DESCRIPTION
    This function updates a Conditional Access policy's state using Microsoft Graph. It defaults to SupportsShouldProcess to encourage WhatIf/Confirm use before modifying policies in production tenants.
.PARAMETER PolicyId
    The Id of the Conditional Access policy to update.
.PARAMETER State
    Target state. Valid values: Enabled, Disabled, ReportOnly.
.EXAMPLE
    Set-O365ConditionalAccessPolicyState -PolicyId '1234-...' -State ReportOnly -WhatIf
.EXAMPLE
    Set-O365ConditionalAccessPolicyState -PolicyId '1234-...' -State Enabled -Confirm
.NOTES
    Requires Microsoft Graph permissions: Policy.ReadWrite.ConditionalAccess.
    Source references: Office365itpros Conditional Access change controls; learn.microsoft.com/graph/api/conditionalaccesspolicy-update.
#>

function Set-O365ConditionalAccessPolicyState {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory=$true)]
        [string]$PolicyId,
        [Parameter(Mandatory=$true)]
        [ValidateSet('Enabled','Disabled','ReportOnly')]
        [string]$State
    )

    $stateMap = @{
        'Enabled'    = 'enabled'
        'Disabled'   = 'disabled'
        'ReportOnly' = 'enabledForReportingButNotEnforced'
    }
    $targetState = $stateMap[$State]

    $policy = Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $PolicyId
    if (-not $policy) {
        Write-Warning "Conditional Access policy not found: $PolicyId"
        return
    }

    if ($PSCmdlet.ShouldProcess($policy.DisplayName, "Set Conditional Access policy state to $State")) {
        Update-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $PolicyId -BodyParameter @{state = $targetState} | Out-Null
        return Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $PolicyId
    }
}

<#
.SYNOPSIS
    Clones a Conditional Access policy into a new policy.
.DESCRIPTION
    This function copies a source Conditional Access policy (conditions, assignments, grant/session controls) into a new policy with a specified display name and desired state. It defaults the new policy to Disabled to avoid accidental impact.
.PARAMETER SourcePolicyId
    The Id of the existing Conditional Access policy to clone.
.PARAMETER TargetDisplayName
    The display name for the new policy.
.PARAMETER TargetState
    The desired state for the cloned policy. Defaults to Disabled.
.EXAMPLE
    Copy-O365ConditionalAccessPolicy -SourcePolicyId 'abcd-...' -TargetDisplayName 'CA - Test clone' -TargetState Disabled
.EXAMPLE
    Copy-O365ConditionalAccessPolicy -SourcePolicyId 'abcd-...' -TargetDisplayName 'CA - Report-only clone' -TargetState ReportOnly -WhatIf
.NOTES
    Requires Microsoft Graph permissions: Policy.Read.All and Policy.ReadWrite.ConditionalAccess.
    Source references: Office365itpros policy hygiene guidance; learn.microsoft.com/graph/api/conditionalaccessroot-post-policies.
#>

function Copy-O365ConditionalAccessPolicy {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory=$true)]
        [string]$SourcePolicyId,
        [Parameter(Mandatory=$true)]
        [string]$TargetDisplayName,
        [ValidateSet('Enabled','Disabled','ReportOnly')]
        [string]$TargetState = 'Disabled'
    )

    $stateMap = @{
        'Enabled'    = 'enabled'
        'Disabled'   = 'disabled'
        'ReportOnly' = 'enabledForReportingButNotEnforced'
    }
    $policy = Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $SourcePolicyId
    if (-not $policy) {
        Write-Warning "Conditional Access policy not found: $SourcePolicyId"
        return
    }

    $body = @{
        displayName     = $TargetDisplayName
        state           = $stateMap[$TargetState]
        conditions      = $policy.Conditions
        grantControls   = $policy.GrantControls
        sessionControls = $policy.SessionControls
        description     = $policy.Description
    }

    if ($PSCmdlet.ShouldProcess($TargetDisplayName, "Create clone of Conditional Access policy '$($policy.DisplayName)'")) {
        $newPolicy = New-MgIdentityConditionalAccessPolicy -BodyParameter $body
        return $newPolicy
    }
}

<#
.SYNOPSIS
    Exports Conditional Access policies to JSON for backup or templating.
.DESCRIPTION
    This function exports one or more Conditional Access policies to a JSON file. It can export all policies or a specific policy by Id.
.PARAMETER OutputPath
    Destination file path for the JSON export.
.PARAMETER PolicyId
    Optional Id of a single policy to export. If omitted, all policies are exported.
.EXAMPLE
    Export-O365ConditionalAccessPolicy -OutputPath '.\\ca-policies-backup.json'
.EXAMPLE
    Export-O365ConditionalAccessPolicy -PolicyId 'abcd-...' -OutputPath '.\\ca-policy.json'
.NOTES
    Requires Microsoft Graph permissions: Policy.Read.All.
    Source references: Office365itpros backup guidance; learn.microsoft.com/graph/api/conditionalaccesspolicy-get.
#>

function Export-O365ConditionalAccessPolicy {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]$OutputPath,
        [string]$PolicyId
    )

    $policies = if ($PolicyId) {
        @(Get-MgIdentityConditionalAccessPolicy -ConditionalAccessPolicyId $PolicyId)
    }
    else {
        Get-MgIdentityConditionalAccessPolicy -All
    }

    if (-not $policies) {
        Write-Warning "No Conditional Access policies found to export."
        return
    }

    $json = $policies | ConvertTo-Json -Depth 15
    $OutputDirectory = Split-Path -Path $OutputPath -Parent
    if ($OutputDirectory -and -not (Test-Path -Path $OutputDirectory)) {
        New-Item -ItemType Directory -Path $OutputDirectory | Out-Null
    }
    $json | Out-File -FilePath $OutputPath -Encoding utf8
    Write-Verbose "Exported $($policies.Count) Conditional Access policy(ies) to $OutputPath"
}

<#
.SYNOPSIS
    Imports Conditional Access policies from JSON.
.DESCRIPTION
    This function reads a JSON file containing one or more Conditional Access policy definitions (such as those produced by Export-O365ConditionalAccessPolicy) and creates new policies. By default, imported policies are created Disabled to reduce risk; you can override with TargetState.
.PARAMETER Path
    Path to the JSON file containing policy definitions.
.PARAMETER TargetState
    State to apply to imported policies. Defaults to Disabled.
.EXAMPLE
    Import-O365ConditionalAccessPolicy -Path '.\\ca-policies-backup.json' -TargetState Disabled -WhatIf
.EXAMPLE
    Import-O365ConditionalAccessPolicy -Path '.\\ca-policy.json' -TargetState ReportOnly
.NOTES
    Requires Microsoft Graph permissions: Policy.ReadWrite.ConditionalAccess.
    Source references: Office365itpros deployment checklists; learn.microsoft.com/graph/api/conditionalaccessroot-post-policies.
#>

function Import-O365ConditionalAccessPolicy {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory=$true)]
        [string]$Path,
        [ValidateSet('Enabled','Disabled','ReportOnly')]
        [string]$TargetState = 'Disabled'
    )

    if (-not (Test-Path -Path $Path)) {
        Write-Warning "File not found: $Path"
        return
    }

    $stateMap = @{
        'Enabled'    = 'enabled'
        'Disabled'   = 'disabled'
        'ReportOnly' = 'enabledForReportingButNotEnforced'
    }

    $content = Get-Content -Path $Path -Raw | ConvertFrom-Json
    $policies = if ($content -is [System.Collections.IEnumerable]) { $content } else { @($content) }

    $created = @()
    foreach ($policy in $policies) {
        $body = @{
            displayName     = $policy.displayName
            description     = $policy.description
            state           = $stateMap[$TargetState]
            conditions      = $policy.conditions
            grantControls   = $policy.grantControls
            sessionControls = $policy.sessionControls
        }

        if ($PSCmdlet.ShouldProcess($policy.displayName, "Import Conditional Access policy as $TargetState")) {
            $newPolicy = New-MgIdentityConditionalAccessPolicy -BodyParameter $body
            $created += $newPolicy
        }
    }

    return $created
}