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. -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, the 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' #> [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, [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 } } } 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) } |