IdentityCommand.SecretsManager.psm1

function Get-SMApiHeader {
    <#
    .SYNOPSIS
    Returns the Accept header value for a Secrets Manager / SWA API request.
 
    .DESCRIPTION
    Every request in both specs carries an Accept header naming its API version - the stable
    `application/x.secretsmgr.v2+json`, or the beta `application/x.secretsmgr.v2beta+json` for
    endpoints the spec marks APIv2 beta. This holds both values so a command names which one it
    needs rather than hardcoding the media type string.
 
    .PARAMETER Version
    'V2' for the stable API, 'Beta' for APIv2 beta endpoints.
 
    .EXAMPLE
    Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version Beta)
    #>

    [OutputType([string])]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, Position = 0)]
        [ValidateSet('V2', 'Beta')]
        [string]$Version
    )

    switch ($Version) {
        'V2' { 'application/x.secretsmgr.v2+json' }
        'Beta' { 'application/x.secretsmgr.v2beta+json' }
    }

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Add-SMGroupMember {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(3, 500)]
        [String]$identifier,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('roleId')]
        [ValidateLength(1, 500)]
        [String]$id,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('roleKind')]
        [ValidateSet('user', 'workload', 'group')]
        [String]$kind
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/groups/$([uri]::EscapeDataString($identifier))/members"

        $body = $PSBoundParameters | Get-Parameter -ParametersToRemove identifier

        if ($PSCmdlet.ShouldProcess($identifier, "Add $kind '$id' as a group member")) {

            #Send Request
            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-Json -Depth 4) -Accept $(Get-SMApiHeader -Version Beta)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Connect-SMTenant {

    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Subdomain')]
    param(

        #subdomain
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Subdomain')]
        [ValidateNotNullOrEmpty()]
        [Alias('subdomain')]
        [String]$tenant_subdomain,

        #tenant_url
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'URL')]
        [ValidateNotNullOrEmpty()]
        [Alias('secretsmgr_url')]
        [String]$tenant_url,

        #A Conjur access token obtained some other way, as the exact value the API expects inside
        #Authorization: Token token="...". Supplying this skips the CyberArk Identity -> Conjur
        #exchange this command otherwise performs automatically.
        [parameter(Mandatory = $false)]
        [SecureString]$ConjurAccessToken

    )

    begin { }#begin

    process {

        $UsingSubdomain = $PSCmdlet.ParameterSetName -eq 'Subdomain'

        if ($UsingSubdomain) {

            $ServiceUrl = Resolve-ServiceUrl -Service secrets_manager -Subdomain $tenant_subdomain
            $tenant_url = $ServiceUrl.ServiceUrl

        } else {

            #Ensure URL is in expected format - remove trailing slash if provided in Url
            $tenant_url = $tenant_url -replace '/$', ''

        }

        $ISPSSSession.tenant_url = $tenant_url

        if ($PSBoundParameters.ContainsKey('ConjurAccessToken')) {

            if ($PSCmdlet.ShouldProcess($tenant_url, 'Set Conjur access token')) {

                $Token = ConvertTo-InsecureString -SecureString $ConjurAccessToken

            }

        } elseif ($PSCmdlet.ShouldProcess($tenant_url, 'Exchange CyberArk Identity session for a Conjur access token')) {

            #Conjur accepts the same bearer-authenticated WebSession every other companion module
            #copies from Get-IDSession - no id_token, cookie or CSRF header is required.
            $ExchangeUri = "$tenant_url/api/authn-oidc/cyberark/conjur/authenticate?set_conjur_cookie=true"
            $ConjurResponse = Invoke-IDRestMethod -Method POST -URI $ExchangeUri -WebSession (Get-IDSession).WebSession

            $Token = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($ConjurResponse | ConvertTo-Json -Compress -Depth 3)))

        }

        if ($Token) {

            #Secrets Manager/SWA authenticate with a Conjur access token, not the CyberArk Identity
            #bearer session every other companion module shares - building an independent
            #WebRequestSession here, rather than copying $IDSession.WebSession, keeps this
            #module's calls from mutating (or being mutated by) that shared session object.
            $WebSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
            $WebSession.Headers['Authorization'] = "Token token=`"$Token`""

            $ISPSSSession.WebSession = $WebSession

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMAuthenticator {
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ById')]
        [ValidateSet('jwt', 'gcp', 'azure', 'aws_iam')]
        [String]$type,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ById')]
        [ValidateLength(1, 60)]
        [String]$name
    )

    begin { }#begin

    process {

        if ($PSCmdlet.ParameterSetName -eq 'ById') {

            $URI = "$($ISPSSSession.tenant_url)/api/authenticators/$type/$([uri]::EscapeDataString($name))"

            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version Beta)

            if ($null -ne $result) {

                $result

            }

        } else {

            $URI = "$($ISPSSSession.tenant_url)/api/authenticators"

            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version Beta)

            if ($null -ne $result) {

                $result.authenticators

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMCABundle {
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('der', 'pem')]
        [String]$format
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/.well-known/ca-bundles"

        if ($PSBoundParameters.ContainsKey('format')) {
            $URI = Add-QueryString -URI $URI -Parameter @{ format = $format }
        }

        #This endpoint is public - no authentication or Accept header is required.
        $result = Invoke-IDRestMethod -Uri $URI -Method GET

        if ($null -ne $result) {

            $result

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMIssuer {
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ById')]
        [Alias('id')]
        [String]$issuerName
    )

    begin { }#begin

    process {

        if ($PSCmdlet.ParameterSetName -eq 'ById') {

            $URI = "$($ISPSSSession.tenant_url)/api/issuers/$([uri]::EscapeDataString($issuerName))"

            #Send Request
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) {

                $result

            }

        } else {

            $URI = "$($ISPSSSession.tenant_url)/api/issuers/conjur"

            #Send Request
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) {

                $result.issuers

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMJwks {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'JWKS is an acronym (JSON Web Key Set), not a plural noun')]
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/.well-known/jwks"

        #This endpoint is public - no authentication is required. Returned whole (not unwrapped to
        #just .keys) since it is a discovery document, not a list resource.
        $result = Invoke-IDRestMethod -Uri $URI -Method GET

        if ($null -ne $result) { $result }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMModuleData {

    [CmdletBinding()]
    param()

    begin { }#begin

    process {

        #Calculate the time elapsed since the start of the session and include in return data
        if ($null -ne $ISPSSSession.StartTime) {
            $ISPSSSession.ElapsedTime = '{0:HH:mm:ss}' -f ([datetime]$($(Get-Date) - $($ISPSSSession.StartTime)).Ticks)
        } else { $ISPSSSession.ElapsedTime = $null }

        #Deep Copy the $ISPSSSession session object and return as IdCmd Session type.
        Get-SessionClone -InputObject $ISPSSSession | Add-CustomType -Type IdCmd.Session

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMNodeGroup {
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByName')]
        [String]$nodeGroupName,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List')]
        [ValidateRange(1, 1000)]
        [int]$limit,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List')]
        [ValidateRange(0, [int]::MaxValue)]
        [int]$offset
    )

    begin { }#begin

    process {

        $BaseURI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))/node-groups"

        if ($PSCmdlet.ParameterSetName -eq 'ByName') {

            $URI = "$BaseURI/$([uri]::EscapeDataString($nodeGroupName))"
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        } else {

            $URI = Add-QueryString -URI $BaseURI -Parameter ($PSBoundParameters | Get-Parameter -ParametersToRemove trustDomainName, serverGroupName)
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result.node_groups }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMOpenIDConfiguration {
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/.well-known/openid-configuration"

        #This endpoint is public - no authentication is required.
        $result = Invoke-IDRestMethod -Uri $URI -Method GET

        if ($null -ne $result) { $result }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMSecretValue {
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('ids')]
        [ValidateCount(1, 250)]
        [String[]]$id,

        #Base64-encode every returned secret value.
        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [switch]$encode_values
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secrets/values"

        if ($encode_values.IsPresent) {
            $URI = Add-QueryString -URI $URI -Parameter @{ encode_values = 'base64' }
        }

        $body = @{ ids = @($id) }

        #Send Request
        $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-Json -Depth 3) -Accept $(Get-SMApiHeader -Version Beta)

        if ($null -ne $result) {

            $result.secrets

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMServer {
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByName')]
        [String]$serverName,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List')]
        [ValidateRange(1, 1000)]
        [int]$limit,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List')]
        [ValidateRange(0, [int]::MaxValue)]
        [int]$offset
    )

    begin { }#begin

    process {

        $BaseURI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))/components"

        if ($PSCmdlet.ParameterSetName -eq 'ByName') {

            $URI = "$BaseURI/$([uri]::EscapeDataString($serverName))"
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        } else {

            $URI = Add-QueryString -URI $BaseURI -Parameter ($PSBoundParameters | Get-Parameter -ParametersToRemove trustDomainName, serverGroupName)
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result.components }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMServerGroup {
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByName')]
        [String]$serverGroupName,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List')]
        [ValidateRange(1, 1000)]
        [int]$limit,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List')]
        [ValidateRange(0, [int]::MaxValue)]
        [int]$offset
    )

    begin { }#begin

    process {

        $BaseURI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups"

        if ($PSCmdlet.ParameterSetName -eq 'ByName') {

            $URI = "$BaseURI/$([uri]::EscapeDataString($serverGroupName))"
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        } else {

            $URI = Add-QueryString -URI $BaseURI -Parameter ($PSBoundParameters | Get-Parameter -ParametersToRemove trustDomainName)
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result.server_groups }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Get-SMTrustDomain {
    [CmdletBinding(DefaultParameterSetName = 'List')]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'ByName')]
        [Alias('name')]
        [String]$trustDomainName,

        #The response reports only the count of items in this page, not an overall total or a next
        #offset - so this command cannot page automatically. Increase -offset yourself until a page
        #comes back with fewer than -limit trust domains.
        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List')]
        [ValidateRange(1, 1000)]
        [int]$limit,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true, ParameterSetName = 'List')]
        [ValidateRange(0, [int]::MaxValue)]
        [int]$offset
    )

    begin { }#begin

    process {

        if ($PSCmdlet.ParameterSetName -eq 'ByName') {

            $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))"

            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) {

                $result

            }

        } else {

            $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains"
            $URI = Add-QueryString -URI $URI -Parameter ($PSBoundParameters | Get-Parameter)

            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) {

                $result.trust_domains

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function New-SMAuthenticator {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('jwt', 'aws_iam')]
        [String]$type,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('gitlab', 'github_actions', 'kubernetes', 'jenkins')]
        [String]$subtype,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(1, 60)]
        [String]$name,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [bool]$enabled,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$ownerId,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('user', 'workload', 'group')]
        [String]$ownerKind,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$ca_cert,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$audience,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$jwks_uri,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [hashtable]$public_keys,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$issuer,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [hashtable]$claim_aliases,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$enforced_claims,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$identity_path,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$token_app_property,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [hashtable]$annotations
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/authenticators"

        $body = [ordered]@{ type = $type; name = $name }

        foreach ($p in 'subtype', 'enabled', 'annotations') {
            if ($PSBoundParameters.ContainsKey($p)) { $body[$p] = $PSBoundParameters[$p] }
        }

        if ($PSBoundParameters.ContainsKey('ownerId')) {
            $body['owner'] = [ordered]@{ id = $ownerId; kind = $ownerKind }
        }

        $Data = [ordered]@{}
        foreach ($p in 'ca_cert', 'audience', 'jwks_uri', 'public_keys', 'issuer') {
            if ($PSBoundParameters.ContainsKey($p)) { $Data[$p] = $PSBoundParameters[$p] }
        }

        $Identity = [ordered]@{}
        foreach ($p in 'claim_aliases', 'enforced_claims', 'identity_path', 'token_app_property') {
            if ($PSBoundParameters.ContainsKey($p)) { $Identity[$p] = $PSBoundParameters[$p] }
        }
        if ($Identity.Keys.Count -gt 0) { $Data['identity'] = $Identity }
        if ($Data.Keys.Count -gt 0) { $body['data'] = $Data }

        if ($PSCmdlet.ShouldProcess($name, "Create $type authenticator")) {

            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-Json -Depth 6) -Accept $(Get-SMApiHeader -Version Beta)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function New-SMIssuedCertificate {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$issuerName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(1, 64)]
        [String]$common_name,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$organization,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$org_units,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$locality,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$state,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(2, 2)]
        [String]$country,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('RSA_2048', 'RSA_3072', 'RSA_4096', 'EC_P256', 'EC_P384', 'EC_P521', 'EC_ED25519')]
        [String]$key_type,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$dns_names,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$ip_addresses,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$email_addresses,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$uris,

        #ISO 8601 duration, e.g. P30D
        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$ttl,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$zone,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [bool]$ignore_storage
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/issuers/$([uri]::EscapeDataString($issuerName))/issue"

        $Subject = [ordered]@{ common_name = $common_name }
        foreach ($p in 'organization', 'org_units', 'locality', 'state', 'country') {
            if ($PSBoundParameters.ContainsKey($p)) { $Subject[$p] = $PSBoundParameters[$p] }
        }

        $body = [ordered]@{ subject = $Subject }

        $AltNames = [ordered]@{}
        foreach ($p in 'dns_names', 'ip_addresses', 'email_addresses', 'uris') {
            if ($PSBoundParameters.ContainsKey($p)) { $AltNames[$p] = @($PSBoundParameters[$p]) }
        }
        if ($AltNames.Keys.Count -gt 0) { $body['alt_names'] = $AltNames }

        foreach ($p in 'key_type', 'ttl', 'zone', 'ignore_storage') {
            if ($PSBoundParameters.ContainsKey($p)) { $body[$p] = $PSBoundParameters[$p] }
        }

        if ($PSCmdlet.ShouldProcess($issuerName, "Issue certificate for '$common_name'")) {

            #The response can carry a private_key - convert to UTF8 bytes rather than a String
            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-SecretBody -Depth 6) -Accept $(Get-SMApiHeader -Version Beta)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function New-SMIssuer {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'password_secret_ref', Justification = 'Reference to an existing stored secret, not the secret itself')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '', Justification = 'user_id_secret_ref/password_secret_ref are references to existing stored secrets, not credentials')]
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'AWS')]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('issuerName')]
        [ValidateLength(1, 60)]
        [String]$id,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateRange(900, 43200)]
        [int]$max_ttl,

        #--- AWS ---
        [parameter(Mandatory = $true, ParameterSetName = 'AWS')]
        [ValidateLength(16, 16)]
        [String]$access_key_id,

        [parameter(Mandatory = $true, ParameterSetName = 'AWS')]
        [SecureString]$secret_access_key,

        #--- GCP ---
        [parameter(Mandatory = $true, ParameterSetName = 'GCP')]
        [ValidateLength(6, 500)]
        [String]$service_account_key_secret_ref,

        [parameter(Mandatory = $false, ParameterSetName = 'GCP')]
        [String[]]$access_token_permitted_scope,

        #--- PKI_VENAFI_SAAS ---
        [parameter(Mandatory = $true, ParameterSetName = 'VenafiSaaS')]
        [String]$service_account_token_url,

        [parameter(Mandatory = $true, ParameterSetName = 'VenafiSaaS')]
        [String]$user_id_secret_ref,

        [parameter(Mandatory = $true, ParameterSetName = 'VenafiSaaS')]
        [String]$password_secret_ref,

        [parameter(Mandatory = $true, ParameterSetName = 'VenafiSaaS')]
        [String]$default_zone,

        [parameter(Mandatory = $true, ParameterSetName = 'VenafiSaaS')]
        [String[]]$allowed_zones
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/issuers/conjur"

        $data = switch ($PSCmdlet.ParameterSetName) {

            'AWS' {
                [ordered]@{
                    access_key_id     = $access_key_id
                    secret_access_key = $(ConvertTo-InsecureString -SecureString $secret_access_key)
                }
            }

            'GCP' {
                $GcpData = [ordered]@{ service_account_key_secret_ref = [ordered]@{ id = $service_account_key_secret_ref } }
                if ($PSBoundParameters.ContainsKey('access_token_permitted_scope')) {
                    $GcpData['access_token_permitted_scope'] = @($access_token_permitted_scope)
                }
                $GcpData
            }

            'VenafiSaaS' {
                [ordered]@{
                    service_account_token_url = $service_account_token_url
                    identity_user_details     = [ordered]@{
                        user_id_secret_ref   = [ordered]@{ id = $user_id_secret_ref }
                        password_secret_ref  = [ordered]@{ id = $password_secret_ref }
                    }
                    default_zone              = $default_zone
                    allowed_zones             = @($allowed_zones)
                }
            }

        }

        $Type = switch ($PSCmdlet.ParameterSetName) {
            'AWS' { 'AWS' }
            'GCP' { 'GCP' }
            'VenafiSaaS' { 'PKI_VENAFI_SAAS' }
        }

        $body = [ordered]@{ id = $id; type = $Type; data = $data }

        if ($PSBoundParameters.ContainsKey('max_ttl')) { $body['max_ttl'] = $max_ttl }

        if ($PSCmdlet.ShouldProcess($id, 'Create Secrets Manager issuer')) {

            #Secrets carried in -data (AWS secret_access_key) are decoded above, so the body travels
            #as UTF8 bytes rather than a String.
            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-SecretBody -Depth 6) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function New-SMNodeGroup {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('nodeGroupName')]
        [ValidateLength(1, 60)]
        [String]$Name,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('kubernetes', 'unix')]
        [String]$workload_type,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$description,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$spiffe_id_template,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$workload_registration_policies
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))/node-groups"

        $body = [ordered]@{ name = $Name; workload_type = $workload_type }
        if ($PSBoundParameters.ContainsKey('description')) { $body['description'] = $description }

        $WorkloadConfig = [ordered]@{}
        if ($PSBoundParameters.ContainsKey('spiffe_id_template')) { $WorkloadConfig['spiffe_id_template'] = $spiffe_id_template }
        if ($PSBoundParameters.ContainsKey('workload_registration_policies')) { $WorkloadConfig['workload_registration_policies'] = @($workload_registration_policies) }
        if ($WorkloadConfig.Keys.Count -gt 0) { $body['workload_configuration'] = $WorkloadConfig }

        if ($PSCmdlet.ShouldProcess($Name, 'Create SWA node group')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-Json -Depth 5) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function New-SMServer {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('serverName')]
        [ValidateLength(1, 51)]
        [String]$Name,

        #Subject claim value from the workload JWT identifying this server.
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$sub,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$ca_cert,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$audience,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$jwks_uri,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [hashtable]$public_keys,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$issuer,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [hashtable]$claim_aliases,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$enforced_claims,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$identity_path,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$token_app_property
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))/components"

        $Data = [ordered]@{ sub = $sub }
        foreach ($p in 'ca_cert', 'audience', 'jwks_uri', 'public_keys', 'issuer') {
            if ($PSBoundParameters.ContainsKey($p)) { $Data[$p] = $PSBoundParameters[$p] }
        }

        $Identity = [ordered]@{}
        foreach ($p in 'claim_aliases', 'enforced_claims', 'identity_path', 'token_app_property') {
            if ($PSBoundParameters.ContainsKey($p)) { $Identity[$p] = $PSBoundParameters[$p] }
        }
        if ($Identity.Keys.Count -gt 0) { $Data['identity'] = $Identity }
        $body = [ordered]@{
            name           = $Name
            authentication = [ordered]@{
                type = 'JWT'
                data = $Data
            }
        }

        if ($PSCmdlet.ShouldProcess($Name, 'Register SWA server')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-Json -Depth 6) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function New-SMServerGroup {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('serverGroupName')]
        [ValidateLength(1, 60)]
        [String]$Name,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(0, 1024)]
        [String]$description,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$gcp_service_account_allowed_project_ids,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$gcp_service_account_audiences,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_assume_role,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('aws', 'aws-cn', 'aws-us-gov')]
        [String]$aws_iid_partition,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_management_account_id,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_management_account_region,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_assume_org_role,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_org_account_map_ttl,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_account_list_file
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups"

        $body = [ordered]@{ name = $Name }
        if ($PSBoundParameters.ContainsKey('description')) { $body['description'] = $description }

        $Attestation = [ordered]@{}

        if ($PSBoundParameters.ContainsKey('gcp_service_account_allowed_project_ids')) {
            $Gcp = [ordered]@{ allowed_project_ids = @($gcp_service_account_allowed_project_ids) }
            if ($PSBoundParameters.ContainsKey('gcp_service_account_audiences')) {
                $Gcp['audiences'] = @($gcp_service_account_audiences)
            }
            $Attestation['gcp_service_account'] = $Gcp
        }

        $AwsIidParams = 'aws_iid_assume_role', 'aws_iid_partition'
        if (($AwsIidParams | Where-Object { $PSBoundParameters.ContainsKey($_) }) -or
            ($PSBoundParameters.ContainsKey('aws_iid_management_account_id'))) {

            $AwsIid = [ordered]@{}
            if ($PSBoundParameters.ContainsKey('aws_iid_assume_role')) { $AwsIid['assume_role'] = $aws_iid_assume_role }
            if ($PSBoundParameters.ContainsKey('aws_iid_partition')) { $AwsIid['partition'] = $aws_iid_partition }

            $VerifyOrg = [ordered]@{}
            foreach ($p in 'management_account_id', 'management_account_region', 'assume_org_role', 'org_account_map_ttl', 'account_list_file') {
                $ParamName = "aws_iid_$p"
                if ($PSBoundParameters.ContainsKey($ParamName)) { $VerifyOrg[$p] = $PSBoundParameters[$ParamName] }
            }
            if ($VerifyOrg.Keys.Count -gt 0) { $AwsIid['verify_organization'] = $VerifyOrg }

            $Attestation['aws_iid'] = $AwsIid

        }
        if ($Attestation.Keys.Count -gt 0) { $body['attestation'] = $Attestation }

        if ($PSCmdlet.ShouldProcess($Name, 'Create SWA server group')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-Json -Depth 6) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function New-SMSignedCertificate {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$issuerName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [String]$csr,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$zone,

        #ISO 8601 duration, e.g. P30D
        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$ttl
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/issuers/$([uri]::EscapeDataString($issuerName))/sign"

        $body = $PSBoundParameters | Get-Parameter -ParametersToRemove issuerName

        if ($PSCmdlet.ShouldProcess($issuerName, 'Sign certificate from CSR')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-Json -Depth 4) -Accept $(Get-SMApiHeader -Version Beta)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function New-SMTrustDomain {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(1, 60)]
        [String]$name,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('EC_P256', 'EC_P384', 'EC_P521', 'RSA_2048', 'RSA_4096')]
        [String]$signing_key_type,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('ES256', 'ES384', 'ES512', 'RS256', 'RS384', 'RS512')]
        [String]$signature_algorithm,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateRange(3600, 2592000)]
        [int]$signing_key_ttl,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateRange(60, 86400)]
        [int]$token_ttl,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateRange(600, 86400)]
        [int]$workload_ttl
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains"

        $body = [ordered]@{ name = $name }

        $Jwt = [ordered]@{}
        foreach ($p in 'signing_key_type', 'signature_algorithm', 'signing_key_ttl', 'token_ttl') {
            if ($PSBoundParameters.ContainsKey($p)) { $Jwt[$p] = $PSBoundParameters[$p] }
        }
        if ($Jwt.Keys.Count -gt 0) { $body['jwt'] = $Jwt }

        if ($PSBoundParameters.ContainsKey('workload_ttl')) {
            $body['x509'] = [ordered]@{ workload_ttl = $workload_ttl }
        }

        if ($PSCmdlet.ShouldProcess($name, 'Register SWA trust domain')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method POST -Body ($body | ConvertTo-Json -Depth 4) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMAuthenticator {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        #Not applicable to gcp authenticators, per the service.
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('jwt', 'azure', 'aws_iam')]
        [String]$type,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(1, 60)]
        [String]$name
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/authenticators/$type/$([uri]::EscapeDataString($name))"

        if ($PSCmdlet.ShouldProcess($name, "Delete $type authenticator")) {

            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version Beta)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMGroupMember {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(3, 500)]
        [String]$identifier,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('roleId')]
        [ValidateLength(1, 1000)]
        [String]$id,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('roleKind')]
        [ValidateSet('workload', 'user', 'group')]
        [String]$kind
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/groups/$([uri]::EscapeDataString($identifier))/members/$kind/$([uri]::EscapeDataString($id))"

        if ($PSCmdlet.ShouldProcess($identifier, "Remove $kind '$id' as a group member")) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version Beta)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMIssuer {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('id')]
        [String]$issuerName,

        #For AWS and GCP issuers only: keep the secrets (variables) associated with the issuer rather
        #than deleting them along with it.
        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [switch]$keep_secrets
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/issuers/$([uri]::EscapeDataString($issuerName))"

        if ($keep_secrets.IsPresent) {
            $URI = Add-QueryString -URI $URI -Parameter @{ keep_secrets = $true }
        }

        if ($PSCmdlet.ShouldProcess($issuerName, 'Delete Secrets Manager issuer')) {

            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version V2)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMNodeGroup {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$nodeGroupName
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))/node-groups/$([uri]::EscapeDataString($nodeGroupName))"

        if ($PSCmdlet.ShouldProcess($nodeGroupName, 'Delete SWA node group')) {

            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version V2)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMServer {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverName
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))/components/$([uri]::EscapeDataString($serverName))"

        if ($PSCmdlet.ShouldProcess($serverName, 'Delete SWA server')) {

            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version V2)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMServerGroup {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))"

        #Deletes every server in the group and their related Secrets Manager resources along with it.
        if ($PSCmdlet.ShouldProcess($serverGroupName, 'Delete SWA server group')) {

            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version V2)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMTrustDomain {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))"

        #Deletes every resource inside the trust domain along with it.
        if ($PSCmdlet.ShouldProcess($trustDomainName, 'Delete SWA trust domain')) {

            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version V2)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMWorkload {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(3, 500)]
        [String]$identifier
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/workloads/$([uri]::EscapeDataString($identifier))"

        if ($PSCmdlet.ShouldProcess($identifier, 'Delete workload')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version Beta)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Remove-SMWorkloadAnnotation {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(3, 500)]
        [String]$identifier,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(1, 1000)]
        [String]$annotationName
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/workloads/$([uri]::EscapeDataString($identifier))/annotations/$([uri]::EscapeDataString($annotationName))"

        if ($PSCmdlet.ShouldProcess($identifier, "Remove annotation '$annotationName'")) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SMApiHeader -Version Beta)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Set-SMAuthenticatorState {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('jwt', 'gcp', 'azure', 'aws_iam')]
        [String]$type,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(1, 60)]
        [String]$name,

        #Only the enabled field is currently updatable on an authenticator.
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [bool]$enabled
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/authenticators/$type/$([uri]::EscapeDataString($name))"

        $body = @{ enabled = $enabled }

        if ($PSCmdlet.ShouldProcess($name, "Set authenticator state: $(if ($enabled) { 'enable' } else { 'disable' })")) {

            $result = Invoke-IDRestMethod -Uri $URI -Method PATCH -Body ($body | ConvertTo-Json -Depth 2) -Accept $(Get-SMApiHeader -Version Beta)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Set-SMIssuer {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'AWS')]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('id')]
        [String]$issuerName,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateRange(900, 43200)]
        [int]$max_ttl,

        #--- AWS ---
        [parameter(Mandatory = $true, ParameterSetName = 'AWS')]
        [ValidateLength(16, 16)]
        [String]$access_key_id,

        [parameter(Mandatory = $true, ParameterSetName = 'AWS')]
        [SecureString]$secret_access_key,

        #--- GCP ---
        [parameter(Mandatory = $true, ParameterSetName = 'GCP')]
        [ValidateLength(6, 500)]
        [String]$service_account_key_secret_ref,

        [parameter(Mandatory = $false, ParameterSetName = 'GCP')]
        [String[]]$access_token_permitted_scope
    )

    begin { }#begin

    process {

        #Only AWS and GCP issuers can be updated - Certificate Manager (PKI_VENAFI_SAAS) issuers are
        #not, matching the spec's own note on this endpoint.
        $URI = "$($ISPSSSession.tenant_url)/api/issuers/$([uri]::EscapeDataString($issuerName))"

        $body = [ordered]@{}

        if ($PSBoundParameters.ContainsKey('max_ttl')) { $body['max_ttl'] = $max_ttl }

        if ($PSCmdlet.ParameterSetName -eq 'AWS') {

            $body['data'] = [ordered]@{
                access_key_id     = $access_key_id
                secret_access_key = $(ConvertTo-InsecureString -SecureString $secret_access_key)
            }

        } elseif ($PSCmdlet.ParameterSetName -eq 'GCP') {

            $GcpData = [ordered]@{ service_account_key_secret_ref = [ordered]@{ id = $service_account_key_secret_ref } }
            if ($PSBoundParameters.ContainsKey('access_token_permitted_scope')) {
                $GcpData['access_token_permitted_scope'] = @($access_token_permitted_scope)
            }
            $body['data'] = $GcpData

        }

        if ($PSCmdlet.ShouldProcess($issuerName, 'Update Secrets Manager issuer')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method PATCH -Body ($body | ConvertTo-SecretBody -Depth 6) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Set-SMNodeGroup {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$nodeGroupName,

        #The name, workload_type and parent server group cannot be changed after creation.
        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$description,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$spiffe_id_template,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$workload_registration_policies
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))/node-groups/$([uri]::EscapeDataString($nodeGroupName))"

        $body = [ordered]@{}
        if ($PSBoundParameters.ContainsKey('description')) { $body['description'] = $description }

        $WorkloadConfig = [ordered]@{}
        if ($PSBoundParameters.ContainsKey('spiffe_id_template')) { $WorkloadConfig['spiffe_id_template'] = $spiffe_id_template }
        if ($PSBoundParameters.ContainsKey('workload_registration_policies')) { $WorkloadConfig['workload_registration_policies'] = @($workload_registration_policies) }
        if ($WorkloadConfig.Keys.Count -gt 0) { $body['workload_configuration'] = $WorkloadConfig }

        if ($PSCmdlet.ShouldProcess($nodeGroupName, 'Update SWA node group')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method PATCH -Body ($body | ConvertTo-Json -Depth 5) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Set-SMServer {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverName,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$ca_cert,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$audience,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$jwks_uri,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [hashtable]$public_keys,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$issuer,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [hashtable]$claim_aliases,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$enforced_claims,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$identity_path,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$token_app_property,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [hashtable]$annotations
    )

    begin { }#begin

    process {

        #The sub claim and authentication type are not updatable, matching the spec's own note.
        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))/components/$([uri]::EscapeDataString($serverName))"

        $Data = [ordered]@{}
        foreach ($p in 'ca_cert', 'audience', 'jwks_uri', 'public_keys', 'issuer') {
            if ($PSBoundParameters.ContainsKey($p)) { $Data[$p] = $PSBoundParameters[$p] }
        }

        $Identity = [ordered]@{}
        foreach ($p in 'claim_aliases', 'enforced_claims', 'identity_path', 'token_app_property') {
            if ($PSBoundParameters.ContainsKey($p)) { $Identity[$p] = $PSBoundParameters[$p] }
        }
        if ($Identity.Keys.Count -gt 0) { $Data['identity'] = $Identity }
        $Authentication = [ordered]@{}
        if ($Data.Keys.Count -gt 0) { $Authentication['data'] = $Data }
        if ($PSBoundParameters.ContainsKey('annotations')) { $Authentication['annotations'] = $annotations }

        if ($Authentication.Keys.Count -eq 0) {
            throw 'Supply at least one setting to update'
        }

        $body = [ordered]@{ authentication = $Authentication }

        if ($PSCmdlet.ShouldProcess($serverName, 'Update SWA server')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method PATCH -Body ($body | ConvertTo-Json -Depth 6) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Set-SMServerGroup {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [String]$serverGroupName,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateLength(0, 1024)]
        [String]$description,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$gcp_service_account_allowed_project_ids,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String[]]$gcp_service_account_audiences,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_assume_role,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('aws', 'aws-cn', 'aws-us-gov')]
        [String]$aws_iid_partition,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_management_account_id,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_management_account_region,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_assume_org_role,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_org_account_map_ttl,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [String]$aws_iid_account_list_file
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))/server-groups/$([uri]::EscapeDataString($serverGroupName))"

        $body = [ordered]@{}
        if ($PSBoundParameters.ContainsKey('description')) { $body['description'] = $description }

        $Attestation = [ordered]@{}

        if ($PSBoundParameters.ContainsKey('gcp_service_account_allowed_project_ids')) {
            $Gcp = [ordered]@{ allowed_project_ids = @($gcp_service_account_allowed_project_ids) }
            if ($PSBoundParameters.ContainsKey('gcp_service_account_audiences')) {
                $Gcp['audiences'] = @($gcp_service_account_audiences)
            }
            $Attestation['gcp_service_account'] = $Gcp
        }

        $AwsIidParams = 'aws_iid_assume_role', 'aws_iid_partition'
        if (($AwsIidParams | Where-Object { $PSBoundParameters.ContainsKey($_) }) -or
            ($PSBoundParameters.ContainsKey('aws_iid_management_account_id'))) {

            $AwsIid = [ordered]@{}
            if ($PSBoundParameters.ContainsKey('aws_iid_assume_role')) { $AwsIid['assume_role'] = $aws_iid_assume_role }
            if ($PSBoundParameters.ContainsKey('aws_iid_partition')) { $AwsIid['partition'] = $aws_iid_partition }

            $VerifyOrg = [ordered]@{}
            foreach ($p in 'management_account_id', 'management_account_region', 'assume_org_role', 'org_account_map_ttl', 'account_list_file') {
                $ParamName = "aws_iid_$p"
                if ($PSBoundParameters.ContainsKey($ParamName)) { $VerifyOrg[$p] = $PSBoundParameters[$ParamName] }
            }
            if ($VerifyOrg.Keys.Count -gt 0) { $AwsIid['verify_organization'] = $VerifyOrg }

            $Attestation['aws_iid'] = $AwsIid

        }
        if ($Attestation.Keys.Count -gt 0) { $body['attestation'] = $Attestation }

        if ($PSCmdlet.ShouldProcess($serverGroupName, 'Update SWA server group')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method PATCH -Body ($body | ConvertTo-Json -Depth 6) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) { $result }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsManager-help.xml

function Set-SMTrustDomain {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory = $true, ValueFromPipelinebyPropertyName = $true)]
        [Alias('name')]
        [String]$trustDomainName,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('EC_P256', 'EC_P384', 'EC_P521', 'RSA_2048', 'RSA_4096')]
        [String]$signing_key_type,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateSet('ES256', 'ES384', 'ES512', 'RS256', 'RS384', 'RS512')]
        [String]$signature_algorithm,

        #NOTE: the update range (3600-86400) is narrower than create's (3600-2592000) - a signing key
        #can only be extended up to 24 hours through this call.
        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateRange(3600, 86400)]
        [int]$signing_key_ttl,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateRange(60, 86400)]
        [int]$token_ttl,

        [parameter(Mandatory = $false, ValueFromPipelinebyPropertyName = $true)]
        [ValidateRange(600, 86400)]
        [int]$workload_ttl
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/swa/trust-domains/$([uri]::EscapeDataString($trustDomainName))"

        $body = [ordered]@{}

        $Jwt = [ordered]@{}
        foreach ($p in 'signing_key_type', 'signature_algorithm', 'signing_key_ttl', 'token_ttl') {
            if ($PSBoundParameters.ContainsKey($p)) { $Jwt[$p] = $PSBoundParameters[$p] }
        }
        if ($Jwt.Keys.Count -gt 0) { $body['jwt'] = $Jwt }

        #x509's workload_ttl is required if x509 is sent at all - update requests it directly.
        if ($PSBoundParameters.ContainsKey('workload_ttl')) {
            $body['x509'] = [ordered]@{ workload_ttl = $workload_ttl }
        }

        if ($body.Keys.Count -eq 0) {
            throw 'Supply at least one setting to update'
        }

        if ($PSCmdlet.ShouldProcess($trustDomainName, 'Update SWA trust domain')) {

            $result = Invoke-IDRestMethod -Uri $URI -Method PATCH -Body ($body | ConvertTo-Json -Depth 4) -Accept $(Get-SMApiHeader -Version V2)

            if ($null -ne $result) {

                $result

            }

        }

    }#process

    end { }#end

}

#Copy IdentityCommand's private helpers into this module: this module's functions call them, and
#the argument completer registrations below do so at import time.
#Each copy is created from the function definition, so it runs in this module's scope and uses this
#module's $ISPSSSession, whether IdentityCommand loaded from source or from its combined psm1.
#Resolve a single IdentityCommand module: with more than one version loaded, Get-Module returns
#an array.
$Module = Get-Module -Name IdentityCommand | Sort-Object Version -Descending | Select-Object -First 1

if ($null -eq $Module) {
    throw 'The IdentityCommand module is not loaded. Import IdentityCommand and try again.'
}

& $Module { Get-ChildItem -Path Function: } |

    Where-Object { $_.ModuleName -eq $Module.Name -and -not $Module.ExportedFunctions.ContainsKey($_.Name) } |

    ForEach-Object {

        . ([scriptblock]::Create("function $($_.Name) {$($_.Definition)}"))

    }

#region Registration

Register-ArgumentCompleter -ParameterName 'trustDomainName' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SMTrustDomain' -ValueProperty 'name' -LabelProperty 'name'
) -CommandName 'Get-SMTrustDomain', 'Set-SMTrustDomain', 'Remove-SMTrustDomain', 'Get-SMCABundle',
'Get-SMServerGroup', 'New-SMServerGroup', 'Set-SMServerGroup', 'Remove-SMServerGroup',
'Get-SMNodeGroup', 'New-SMNodeGroup', 'Set-SMNodeGroup', 'Remove-SMNodeGroup',
'Get-SMServer', 'New-SMServer', 'Set-SMServer', 'Remove-SMServer',
'Get-SMOpenIDConfiguration', 'Get-SMJwks'

Register-ArgumentCompleter -ParameterName 'issuerName' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SMIssuer' -ValueProperty 'id' -LabelProperty 'id'
) -CommandName 'Set-SMIssuer', 'Remove-SMIssuer', 'New-SMIssuedCertificate', 'New-SMSignedCertificate'

#endregion Registration

# Script scope session object for session data
$ISPSSSession = [ordered]@{
    tenant_url         = $null
    User               = $null
    TenantId           = $null
    SessionId          = $null
    WebSession         = $null
    StartTime          = $null
    ElapsedTime        = $null
    LastCommand        = $null
    LastCommandTime    = $null
    LastCommandResults = $null
    LastError          = $null
    LastErrorTime      = $null
} | Add-CustomType -Type IdCmd.Session

New-Variable -Name ISPSSSession -Value $ISPSSSession -Scope Script -Force