Private/Get-NSPConditionalAccessTravelDefinition.ps1

function Get-NSPConditionalAccessTravelDefinition {
    <#
    .SYNOPSIS
        Generates the TravelMFA policy definitions for a tenant profile's active travel entries.
    .DESCRIPTION
        Conditional Access cannot say "block unless in group X and in country Y" in one policy, so:
          - NSP-TRAVEL-<ISO>-01 / NSP-TRAVEL-TRIP-<NN>: the entry's travel group is blocked everywhere
            except the home country and the entry's countries (every stop, for a trip).
          - NSP-TRAVEL-SESSION-01: every active travel group must use MFA, with a sign-in frequency
            of Travel.SessionHours (default 72, sent as 3 days; whole days are sent as days).
        NSP-BASE-GEO-01 excludes a travel group only once its travel policy is On
        ({TravelGroups:Enforced}), so there is never a window without a country restriction.
        DefaultState is report-only, but the planner creates travel policies On while
        NSP-BASE-GEO-01 is On: for its members a travel policy blocks less than the baseline
        already does, so turning it on blocks no one new. The baseline's exclusion then runs
        after it in the same plan. NSP-TRAVEL-SESSION-01 is created On with them: it reaches only
        travel group members, and travelers abroad must have it.
        Definitions are generated in the file format and validated like shipped ones.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]$TenantProfile,
        [datetime]$Today = (Get-Date).Date
    )

    $active = @(Get-NSPConditionalAccessTravelEntry -TenantProfile $TenantProfile -Today $Today | Where-Object Status -eq 'Active')
    foreach ($entry in $active) {
        $names = @($entry.Countries | ForEach-Object { Get-NSPConditionalAccessCountryName $_ }) -join ', '
        $where = if ($entry.Kind -eq 'Trip') { "every stop of the trip ($names)" } else { $names }
        @{
            Id = $entry.PolicyId
            Name = if ($entry.Kind -eq 'Trip') { "Trip: $($entry.Name)" } else { "Travel: allow $names" }
            Version = 1
            Description = "Members of the travel group are blocked everywhere except the home country and $where."
            DefaultState = 'reportOnly'
            Users = @{
                IncludeGroups = @("{TravelGroup:$($entry.Key)}")
                ExcludeUsers = @('{BreakGlass}', '{ServiceAccounts}')
            }
            Applications = @{ Include = @('All') }
            Locations = @{
                Include = @('All')
                Exclude = @(@('{Country:Home}') + @($entry.Countries | ForEach-Object { "{Country:$_}" }))
            }
            Grant = @{ Operator = 'OR'; BuiltInControls = @('block') }
        }
    }
    if ($active.Count) {
        $hours = Get-NSPConditionalAccessMapValue (Get-NSPConditionalAccessMapValue $TenantProfile 'Travel') 'SessionHours'
        if (-not $hours) { $hours = 72 }
        # Whole days are sent as days (72 hours -> 3 days), as an operator would set it in the portal.
        $frequency = if ($hours % 24 -eq 0) { @{ Value = [int]($hours / 24); Type = 'days' } } else { @{ Value = [int]$hours; Type = 'hours' } }
        $length = "$($frequency.Value) $(if ($frequency.Value -eq 1) { $frequency.Type.TrimEnd('s') } else { $frequency.Type })"
        @{
            Id = 'NSP-TRAVEL-SESSION-01'
            Name = 'Travel: MFA and session length for travelers'
            Version = 1
            Description = "Every active travel group must use MFA and sign in again every $length."
            DefaultState = 'reportOnly'
            Users = @{
                IncludeGroups = @('{TravelGroups:Active}')
                ExcludeUsers = @('{BreakGlass}', '{ServiceAccounts}')
            }
            Applications = @{ Include = @('All') }
            Grant = @{ Operator = 'OR'; BuiltInControls = @('mfa') }
            Session = @{ SignInFrequency = $frequency }
        }
    }
}