Providers/Okta/Private/Get-OktaSeededUser.ps1

function Get-OktaSeededUser {
    <#
    .SYNOPSIS
        Finds the users this module created, and only those

    .DESCRIPTION
        Teardown deletes users irreversibly, so what counts as "ours" needs to be something
        stronger than a naming convention that a real employee could coincidentally match.

        Two independent markers are used, and either one is enough:

        - profile.labSeedTag equal to the prefix. This is the authoritative one, written by
          New-OktaUser and defined in the custom schema. It cannot be produced by
          accident.
        - The login sitting under the seed email domain. This is the fallback for the case
          where the schema attribute was removed before the users were, which is exactly what
          happens if a teardown is interrupted halfway.

        Filtering happens here rather than in the API query on purpose. The tenant holds at
        most ten users, so fetching all of them costs one call, whereas an Okta search
        expression over a custom attribute depends on that attribute still existing and on
        the search index having caught up. Neither is true immediately after a failed run.

    .PARAMETER Prefix
        The seed tag value to match

    .PARAMETER EmailDomain
        The email domain the seeded logins live under

    .OUTPUTS
        Array of Okta user objects

    .EXAMPLE
        Get-OktaSeededUser -Prefix 'OKTALAB' -EmailDomain 'oktalab.example.com'

    .NOTES
        Author: Jeffrey Stuhr
        Version: 1.0.0
        Last Updated: 2026-08-07
    #>


    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Prefix,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$EmailDomain
    )

    # Deactivated users still need deleting, and the default listing hides them, so ask for
    # them explicitly as a second pass rather than relying on the default population.
    $active = @(Invoke-OktaRequest -Method GET -Path '/api/v1/users' -Query @{ limit = 200 } -Paginate)
    $deprovisioned = @(Invoke-OktaRequest -Method GET -Path '/api/v1/users' `
        -Query @{ limit = 200; filter = 'status eq "DEPROVISIONED"' } -Paginate)

    $all = @($active) + @($deprovisioned)

    $suffix = '@' + $EmailDomain.TrimStart('@')

    $seeded = $all | Where-Object {
        $taggedProfile = $_.profile
        $byTag = $taggedProfile.PSObject.Properties['labSeedTag'] -and
            $taggedProfile.labSeedTag -eq (Get-OktaSeedTag -Prefix $Prefix)
        $byDomain = $taggedProfile.login -and
            $taggedProfile.login.EndsWith($suffix, [StringComparison]::OrdinalIgnoreCase)

        $byTag -or $byDomain
    }

    # The two listings can overlap if a user changes status between the calls.
    return @($seeded | Sort-Object -Property id -Unique)
}