Public/Add-NSPConditionalAccessPolicyTarget.ps1

function Add-NSPConditionalAccessPolicyTarget {
    <#
    .SYNOPSIS
        Adds a user or group to one policy's includes or excludes, or a service account to every policy's exclusions, in the tenant profile.
    .DESCRIPTION
        Offline; changes only the tenant profile. The next deployment plan updates the policy, and
        the update is reviewed, approved, and undoable like any other.
 
        -Field says what the addition does:
          - ExcludeUsers / ExcludeGroups: the policy leaves them out. From a block policy, that
            lets them through; from an MFA policy, they no longer need MFA from it.
          - IncludeUsers / IncludeGroups: the policy also applies to them, which restricts them.
            Refused for a policy that already includes every user.
        Additions sit on top of the definition's own targets, which are not changed here
        (breakglass, the group slots). They stay through a profile rebuild.
 
        -ServiceAccount adds an account to the profile's service accounts instead: every policy
        that excludes {ServiceAccounts} (most of them) leaves it out. A breakglass account is
        refused; it is already excluded from every policy.
    .EXAMPLE
        Add-NSPConditionalAccessPolicyTarget -TenantProfile $profilePath -PolicyId 'NSP-BASE-GEO-01' -Field ExcludeUsers -Id $user.id -Label $user.userPrincipalName
    .EXAMPLE
        Add-NSPConditionalAccessPolicyTarget -TenantProfile $profilePath -ServiceAccount -Id $user.id -Label 'scanner@contoso.example'
    #>

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Policy')]
    param(
        [Parameter(Mandatory)][string]$TenantProfile,
        [Parameter(Mandatory, ParameterSetName = 'Policy')][string]$PolicyId,
        [Parameter(Mandatory, ParameterSetName = 'Policy')][ValidateSet('IncludeUsers', 'IncludeGroups', 'ExcludeUsers', 'ExcludeGroups')][string]$Field,
        [Parameter(Mandatory, ParameterSetName = 'ServiceAccount')][switch]$ServiceAccount,
        [Parameter(Mandatory)][ValidatePattern('^[0-9a-fA-F-]{36}$')][string]$Id,
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Label
    )

    $content = Get-Content -LiteralPath $TenantProfile -Raw | ConvertFrom-Json
    $Id = $Id.ToLowerInvariant()
    $breakGlass = @(Get-NSPConditionalAccessMapValue $content.BreakGlass 'Ids' | ForEach-Object { ([string]$_).ToLowerInvariant() })
    if ($Id -in $breakGlass) { throw "$Label is a breakglass account; every policy already excludes it." }

    if ($ServiceAccount) {
        if (-not $content.ServiceAccounts) { $content | Add-Member -NotePropertyName ServiceAccounts -NotePropertyValue ([pscustomobject]@{ Ids = @(); Labels = @() }) -Force }
        $ids = @($content.ServiceAccounts.Ids | Where-Object { $_ })
        if ($Id -in @($ids | ForEach-Object { ([string]$_).ToLowerInvariant() })) { throw "$Label is already a service account." }
        $content.ServiceAccounts.Ids = @($ids) + $Id
        $content.ServiceAccounts.Labels = @(@($content.ServiceAccounts.Labels | Where-Object { $_ }) + $Label)
        $what = "Add service account $Label"
    } else {
        $definitions = @(Get-NSPConditionalAccessDefinition) + @(Get-NSPConditionalAccessTravelDefinition -TenantProfile $content)
        $definition = @($definitions | Where-Object { $_['Id'] -eq $PolicyId })[0]
        if (-not $definition) { throw "No policy definition $PolicyId. Known: $(@($definitions | ForEach-Object { $_['Id'] }) -join ', ')." }
        if ($Field -like 'Include*' -and @(Get-NSPConditionalAccessMapValue $definition['Users'] 'IncludeUsers') -contains 'All') {
            throw "$PolicyId already applies to every user, so no one can be added to it. Leave people out with ExcludeUsers or ExcludeGroups instead."
        }
        if (-not $content.PolicyTargets) { $content | Add-Member -NotePropertyName PolicyTargets -NotePropertyValue ([pscustomobject]@{}) -Force }
        if (-not $content.PolicyTargets.PSObject.Properties[$PolicyId]) { $content.PolicyTargets | Add-Member -NotePropertyName $PolicyId -NotePropertyValue ([pscustomobject]@{}) }
        $targets = $content.PolicyTargets.$PolicyId
        foreach ($other in 'IncludeUsers', 'IncludeGroups', 'ExcludeUsers', 'ExcludeGroups') {
            if (@($targets.$other | Where-Object { $_ -and ([string]$_.Id).ToLowerInvariant() -eq $Id }).Count) {
                throw "$Label is already added to $PolicyId as $other. Remove that first."
            }
        }
        $entry = [pscustomobject][ordered]@{ Id = $Id; Label = $Label; AddedAtUtc = (Get-Date).ToUniversalTime().ToString('o') }
        $targets | Add-Member -NotePropertyName $Field -NotePropertyValue @(@($targets.$Field | Where-Object { $_ }) + $entry) -Force
        $what = "Add $Label to $PolicyId $Field"
    }

    if ($PSCmdlet.ShouldProcess($TenantProfile, $what)) {
        Save-NSPConditionalAccessJson -InputObject $content -Path $TenantProfile
    }
    Get-NSPConditionalAccessPolicyTarget -TenantProfile $content | Where-Object { $_.Id -eq $Id -and ($ServiceAccount -or $_.PolicyId -eq $PolicyId) }
}