Providers/Okta/Public/New-OktaGroupRule.ps1

function New-OktaGroupRule {
    <#
    .EXTERNALHELP TestEnvironment-Help.xml
    .SYNOPSIS
        Creates the group rules that populate the automatic groups
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [string[]]$RuleName,

        [Parameter()]
        [switch]$SkipActivation,

        [Parameter()]
        [switch]$PassThru
    )

    $connection = Get-OktaConnection

    $dataPath = Get-OktaDataPath
    $rows = @(Import-Csv -Path (Join-Path -Path $dataPath -ChildPath 'OktaGroupRules.csv') -Encoding UTF8)
    $groupRows = @(Import-Csv -Path (Join-Path -Path $dataPath -ChildPath 'OktaGroups.csv') -Encoding UTF8)

    if ($RuleName) {
        $rows = @($rows | Where-Object { $RuleName -contains $_.Name })
        $unknown = @($RuleName | Where-Object { $rows.Name -notcontains $_ })
        if ($unknown) { throw "No rule definition for: $($unknown -join ', ')" }
    }

    $result = [PSCustomObject]@{
        TotalRules     = $rows.Count
        CreatedRules   = 0
        UpdatedRules   = 0
        ActivatedRules = 0
        Rules          = @()
        Errors         = @()
    }

    # The CSV names a target by its ASCII key; Okta knows it by its prefixed display name.
    $oktaNameByKey = @{}
    foreach ($groupRow in $groupRows) {
        $oktaNameByKey[$groupRow.Name] = '{0}-{1}' -f $connection.Prefix, $groupRow.DisplayName
    }

    $existingGroups = @(Get-OktaSeededGroup -Prefix $connection.Prefix -SeedMarker $connection.SeedMarker)
    $groupIdByName = @{}
    foreach ($group in $existingGroups) { $groupIdByName[$group.profile.name] = $group.id }

    $existingRules = @(Invoke-OktaRequest -Method GET -Path '/api/v1/groups/rules' `
        -Query @{ limit = 200 } -Paginate)

    $rules = [System.Collections.Generic.List[object]]::new()

    foreach ($row in $rows) {
        $ruleFullName = '{0}-Rule-{1}' -f $connection.Prefix, $row.Name
        $targetOktaName = $oktaNameByKey[$row.TargetGroup]

        if (-not $targetOktaName -or -not $groupIdByName.ContainsKey($targetOktaName)) {
            $message = ("Rule '$ruleFullName' targets group '$($row.TargetGroup)', which does not " +
                'exist. Run New-OktaGroup first. Skipped.')
            $result.Errors += $message
            Write-Warning $message
            continue
        }

        $activate = (-not $SkipActivation) -and ([bool]::Parse($row.Activate))

        if (-not $PSCmdlet.ShouldProcess($ruleFullName, 'Create Okta group rule')) { continue }

        $body = @{
            type       = 'group_rule'
            name       = $ruleFullName
            conditions = @{
                people     = @{
                    users  = @{ exclude = @() }
                    groups = @{ exclude = @() }
                }
                expression = @{
                    value = $row.Expression
                    type  = 'urn:okta:expression:1.0'
                }
            }
            actions    = @{
                assignUserToGroups = @{ groupIds = @($groupIdByName[$targetOktaName]) }
            }
        }

        try {
            $existing = @($existingRules | Where-Object { $_.name -eq $ruleFullName })

            if ($existing.Count -gt 0) {
                $ruleId = $existing[0].id

                # Okta refuses to modify an active rule. Deactivating first is not optional,
                # and doing it unconditionally is simpler than reading the current status and
                # costs one call that would usually be made anyway.
                try {
                    $null = Invoke-OktaRequest -Method POST `
                        -Path "/api/v1/groups/rules/$ruleId/lifecycle/deactivate"
                }
                catch {
                    Write-Verbose "Rule $ruleFullName was already inactive."
                }

                $rule = Invoke-OktaRequest -Method PUT -Path "/api/v1/groups/rules/$ruleId" -Body $body
                $result.UpdatedRules++
            }
            else {
                $rule = Invoke-OktaRequest -Method POST -Path '/api/v1/groups/rules' -Body $body
                $result.CreatedRules++
            }

            if ($activate) {
                $null = Invoke-OktaRequest -Method POST `
                    -Path "/api/v1/groups/rules/$($rule.id)/lifecycle/activate"
                $result.ActivatedRules++
            }

            $rules.Add([PSCustomObject]@{
                Id          = $rule.id
                Name        = $ruleFullName
                TargetGroup = $targetOktaName
                Expression  = $row.Expression
                Active      = $activate
            })
        }
        catch {
            $message = "Failed to create rule '$ruleFullName': $($_.Exception.Message)"
            $result.Errors += $message
            Write-Error $message
        }
    }

    $result.Rules = $rules.ToArray()

    if ($result.ActivatedRules -gt 0) {
        Write-Verbose ('Group rules evaluate asynchronously; membership will appear shortly, and ' +
            'only for active users.')
    }

    if ($PassThru) { return $result }
}