Public/Add-NSPConditionalAccessTravel.ps1
|
function Add-NSPConditionalAccessTravel { <# .SYNOPSIS Adds a travel entry to a tenant profile: a standing country allowance, or a dated trip. .DESCRIPTION Offline; changes only the tenant profile. The next deployment plan then proposes the travel group (on-premises in a hybrid tenant, with a cloud-only _azure companion), any missing country named locations, the travel policy in report-only, and NSP-TRAVEL-SESSION-01. Country (-Country FR): group Group-Travel-France, policy NSP-TRAVEL-FR-01 'Travel: allow France'. Group and policy names use the country's name; the code stays the entry's key. One entry per country. Trip (-TripName, -Country, -EndDate): group Group-Travel-Trip-NN, policy NSP-TRAVEL-TRIP-NN, allowing every listed country (layovers included). Trip numbers are never reused. After the end date, the plan retires the trip. -AllowanceName with -Country creates one standing multi-country allowance, with a single group and NSP-TRAVEL-SET-NN policy. Include every needed home and destination country; separate travel groups intersect. A trip already supports several countries with an end date. With permanent home groups configured, new travel policies start report-only even when the default baseline is On. Promote them, then update the home and baseline exclusions. -SessionHours sets how often travelers sign in again (NSP-TRAVEL-SESSION-01). Left unset, it follows the module's default: 72 hours, sent to Graph as 3 days. A travel group is excluded from NSP-BASE-GEO-01 only while its travel policy is On. When NSP-BASE-GEO-01 is On and no permanent home groups are configured, travel policies are created On and the exclusion follows in the same run, so the traveler can sign in abroad as soon as it finishes. Keep each traveler in one travel group at a time: travel policies are separate blocks and stack. .EXAMPLE Add-NSPConditionalAccessTravel -TenantProfile $profilePath -Country 'CA' .EXAMPLE Add-NSPConditionalAccessTravel -TenantProfile $profilePath -TripName 'Sales Tokyo' -Country 'FR', 'JP' -EndDate '2026-10-30' .EXAMPLE Add-NSPConditionalAccessTravel -TenantProfile $profilePath -AllowanceName 'Pacific travel' -Country US,JP,CA #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Country')] param( [Parameter(Mandatory)][string]$TenantProfile, [Parameter(Mandatory)][ValidatePattern('^[A-Za-z]{2}$')][string[]]$Country, [Parameter(Mandatory, ParameterSetName = 'Trip')][ValidateNotNullOrEmpty()][string]$TripName, [Parameter(Mandatory, ParameterSetName = 'Trip')][datetime]$EndDate, [Parameter(Mandatory, ParameterSetName = 'Allowance')][ValidateNotNullOrEmpty()][string]$AllowanceName, [ValidateRange(1, 720)][int]$SessionHours ) $content = Get-Content -LiteralPath $TenantProfile -Raw | ConvertFrom-Json $homeCountry = [string]$content.HomeCountry $countries = @($Country | ForEach-Object { $_.ToUpperInvariant() } | Select-Object -Unique) if (-not $content.Travel) { $content | Add-Member -NotePropertyName Travel -NotePropertyValue ([pscustomobject]@{ NextTripNumber = 1; Entries = [pscustomobject]@{} }) } if (-not $content.Travel.Entries) { $content.Travel | Add-Member -NotePropertyName Entries -NotePropertyValue ([pscustomobject]@{}) -Force } if ($PSBoundParameters.ContainsKey('SessionHours')) { $content.Travel | Add-Member -NotePropertyName SessionHours -NotePropertyValue $SessionHours -Force } $now = (Get-Date).ToUniversalTime().ToString('o') $added = @(if ($PSCmdlet.ParameterSetName -eq 'Trip') { $stops = @($countries | Where-Object { $_ -ne $homeCountry }) if (-not $stops.Count) { throw 'A trip needs at least one country other than the home country.' } if ($EndDate.Date -lt (Get-Date).Date) { throw "The end date $($EndDate.ToString('yyyy-MM-dd')) has already passed." } $number = [int]$content.Travel.NextTripNumber if ($number -lt 1) { $number = 1 } if ($number -gt 99) { throw 'Trip numbers are exhausted (NSP-TRAVEL-TRIP-99). Renumbering needs a design decision.' } $key = 'TRIP-{0:D2}' -f $number $content.Travel | Add-Member -NotePropertyName NextTripNumber -NotePropertyValue ($number + 1) -Force [pscustomobject]@{ Key = $key; Entry = [pscustomobject][ordered]@{ Kind = 'Trip'; Name = $TripName; Countries = $stops; EndDate = $EndDate.ToString('yyyy-MM-dd') Ids = @(); Labels = @(); Missing = $true; DefaultName = 'Group-Travel-Trip-{0:D2}' -f $number; CreatedAtUtc = $now } } } elseif ($PSCmdlet.ParameterSetName -eq 'Allowance') { $stops = @($countries | Where-Object { $_ -ne $homeCountry }) if (-not $stops.Count) { throw 'An allowance needs at least one country other than the default home country.' } $number = [int]$content.Travel.NextAllowanceNumber if ($number -lt 1) { $number = 1 } if ($number -gt 99) { throw 'Standing allowance numbers are exhausted.' } $key = 'SET-{0:D2}' -f $number $content.Travel | Add-Member -NotePropertyName NextAllowanceNumber -NotePropertyValue ($number + 1) -Force [pscustomobject]@{ Key = $key; Entry = [pscustomobject][ordered]@{ Kind = 'Allowance'; Name = $AllowanceName; Countries = $stops Ids = @(); Labels = @(); Missing = $true; DefaultName = "Group-Travel-$key"; CreatedAtUtc = $now } } } else { foreach ($iso in $countries) { if ($iso -eq $homeCountry) { throw "$iso is the home country; it needs no travel entry." } if ($content.Travel.Entries.PSObject.Properties[$iso]) { throw "A travel entry for $iso already exists." } [pscustomobject]@{ Key = $iso; Entry = [pscustomobject][ordered]@{ Kind = 'Country'; Name = (Get-NSPConditionalAccessCountryName $iso); Countries = @($iso) Ids = @(); Labels = @(); Missing = $true; DefaultName = "Group-Travel-$(Get-NSPConditionalAccessCountryName $iso -ForObjectName)"; CreatedAtUtc = $now } } } }) foreach ($new in $added) { $content.Travel.Entries | Add-Member -NotePropertyName $new.Key -NotePropertyValue $new.Entry } if ($PSCmdlet.ShouldProcess($TenantProfile, "Add travel entr$(if ($added.Count -eq 1) { 'y' } else { 'ies' }) $(($added | ForEach-Object Key) -join ', ')")) { Save-NSPConditionalAccessJson -InputObject $content -Path $TenantProfile } Get-NSPConditionalAccessTravelEntry -TenantProfile $content | Where-Object Key -in @($added | ForEach-Object Key) } |