IdentityCommand.SecureAI.psm1

function Get-SAIApiHeader {
    <#
    .SYNOPSIS
    Returns the Accept header value for a Secure AI resource.
 
    .DESCRIPTION
    Every Secure AI endpoint is a Beta API and requires an explicit Accept header naming the
    resource it belongs to; a request without it, or with the wrong one, is refused with 406 Not
    Acceptable. The three values are held here so the commands do not each carry a copy.
 
    Pass the returned value to Invoke-IDRestMethod's -Accept parameter.
 
    .PARAMETER Resource
    The resource the request addresses - agents, targets or policies.
 
    .EXAMPLE
    Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Agents)
 
    .OUTPUTS
    String
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [parameter(
            Mandatory = $true,
            Position = 0
        )]
        [ValidateSet('Agents', 'Targets', 'Policies')]
        [string]$Resource
    )

    switch ($Resource) {

        'Agents' { 'application/x.agents.beta+json' }
        'Targets' { 'application/x.targets.beta+json' }
        'Policies' { 'application/x.policies.beta+json' }

    }

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Connect-SAITenant {

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

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

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

        #Credential used to authenticate to CyberArk Identity. Authentication is performed even if an active IdentityCommand session is found, replacing it
        [parameter(Mandatory = $true, ParameterSetName = 'SubdomainCredential')]
        [parameter(Mandatory = $true, ParameterSetName = 'URLCredential')]
        [ValidateNotNullOrEmpty()]
        [PSCredential]$Credential,

        #Authenticate as a service user via New-IDPlatformToken (OAuth client_credentials) instead of the interactive New-IDSession
        [parameter(ParameterSetName = 'SubdomainCredential')]
        [parameter(ParameterSetName = 'URLCredential')]
        [Switch]$PlatformToken,

        #SAML assertion used to authenticate to CyberArk Identity. Authentication is performed even if an active IdentityCommand session is found, replacing it
        [parameter(Mandatory = $true, ParameterSetName = 'SubdomainSAML')]
        [parameter(Mandatory = $true, ParameterSetName = 'URLSAML')]
        [ValidateNotNullOrEmpty()]
        [String]$SAMLResponse

    )

    begin {

        $IDSession = Get-IDSession
        $HaveSession = $null -ne $IDSession.tenant_url
        $AuthRequested = $PSBoundParameters.ContainsKey('Credential') -or $PSBoundParameters.ContainsKey('SAMLResponse')

        if ($HaveSession -and $AuthRequested) {
            Write-Verbose 'Authentication parameters were supplied; authenticating and replacing the existing IdentityCommand session'
        }

    }#begin

    process {

        $UsingSubdomain = $PSCmdlet.ParameterSetName -like 'Subdomain*'

        #Resolve service URLs from platform discovery when a subdomain was supplied, or when
        #authentication is required and the CyberArk Identity URL must be discovered.
        $ServiceUrl = $null

        if ($UsingSubdomain) {

            $ServiceUrl = Resolve-ServiceUrl -Service aigw -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 '/$', ''

            if ($AuthRequested) {
                $ServiceUrl = Resolve-ServiceUrl -Service aigw -Url $tenant_url
            }

        }

        if ($AuthRequested -or (-not $HaveSession)) {

            if (-not $AuthRequested) {
                throw 'Authenticate with New-IDSession or New-IDPlatformToken, or supply -Credential, and try again'
            }

            $IdentityUrl = $ServiceUrl.IdentityUrl

            if ($PSCmdlet.ShouldProcess($IdentityUrl, 'Authenticate to CyberArk Identity')) {

                if ($PSCmdlet.ParameterSetName -like '*SAML') {
                    $null = New-IDSession -tenant_url $IdentityUrl -SAMLResponse $SAMLResponse
                } elseif ($PlatformToken) {
                    $null = New-IDPlatformToken -tenant_url $IdentityUrl -Credential $Credential
                } else {
                    $null = New-IDSession -tenant_url $IdentityUrl -Credential $Credential
                }

                $IDSession = Get-IDSession

            }

        }

        #Make the CyberArk Identity Session available in the IdentityCommand.SecureAI scope
        foreach ($key in $IDSession.keys) {
            if ($null -ne $IDSession[$key]) {
                $ISPSSSession[$key] = $IDSession[$key]
            }
        }

        #Set the Access Requests URL in the session data
        $ISPSSSession.tenant_url = $tenant_url

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Get-SAIAgent {
    [CmdletBinding(DefaultParameterSetName = 'All')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byId'
        )]
        [ValidateLength(1, 128)]
        [Alias('id')]
        [String]$agentId
    )

    begin { }#begin

    process {

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

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

            $URI = "$URI/$agentId"

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Agents)

        } else {

            #Send Request
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Agents)

            if ($null -ne $result) {

                $result.agents

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Get-SAIMcpServer {
    [CmdletBinding(DefaultParameterSetName = 'All')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byId'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$mcpServerId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'All'
        )]
        [ValidateRange(1, 2147483647)]
        [int]$pageSize
    )

    begin { }#begin

    process {

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

            $URI = "$($ISPSSSession.tenant_url)/api/targets/mcp-servers/$mcpServerId"

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Targets)

        } else {

            $URI = "$($ISPSSSession.tenant_url)/api/targets/mcp-servers"
            $URI = Add-QueryString -URI $URI -Parameter ($PSBoundParameters | Get-Parameter)

            #Send Request
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Targets)

            if ($null -ne $result) {

                Get-PagedResult -InitialResult $result -URI $URI -Style Cursor -ResultProperty 'mcpServers' -CursorRequestKey 'nextCursor' -CursorResponseKey 'nextCursor'

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Get-SAIModuleData {

    [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.SecureAI-help.xml
function Get-SAIPolicy {
    [CmdletBinding(DefaultParameterSetName = 'All')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byId'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$policyId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'All'
        )]
        [ValidateRange(1, 2147483647)]
        [int]$pageSize
    )

    begin { }#begin

    process {

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

            $URI = "$($ISPSSSession.tenant_url)/api/policies/$policyId"

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Policies)

        } else {

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

            #Send Request
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Policies)

            if ($null -ne $result) {

                #The continuation token is returned as nextCursor and resent as cursor
                Get-PagedResult -InitialResult $result -URI $URI -Style Cursor -ResultProperty 'policies' -CursorRequestKey 'cursor' -CursorResponseKey 'nextCursor'

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Get-SAIPredefinedMcpServer {
    [CmdletBinding(DefaultParameterSetName = 'All')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byId'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id', 'predefinedTargetId')]
        [String]$templateId
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/targets/predefined/mcp-servers"

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

            $URI = "$URI/$templateId"

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Targets)

        } else {

            #Send Request
            $result = Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SAIApiHeader -Resource Targets)

            if ($null -ne $result) {

                $result.targets

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function New-SAIAgent {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(2, 256)]
        [ValidatePattern('^[A-Za-z0-9 _-]+$')]
        [String]$name,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(2, 256)]
        [String]$type,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateCount(0, 10)]
        [String[]]$redirectCallbackUrls,

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$owners,

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

    begin { }#begin

    process {

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

        $body = $PSBoundParameters | Get-Parameter | ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($name, 'Register AI Agent')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body -Accept $(Get-SAIApiHeader -Resource Agents)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function New-SAIAgentConditionDefinition {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Any', Justification = 'Used via $PSCmdlet.ParameterSetName')]
    [CmdletBinding(DefaultParameterSetName = 'In')]
    [OutputType('IdCmd.SecureAI.Definition.AgentCondition')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'In'
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]$id,

        [parameter(
            Mandatory = $true,
            ParameterSetName = 'Any'
        )]
        [switch]$Any,

        [parameter(Mandatory = $false)]
        [psobject[]]$ConditionDefinition
    )

    begin { }#begin

    process {

        #Each group carries exactly one condition; groups are combined with or by the service
        $Condition = if ($PSCmdlet.ParameterSetName -eq 'Any') {
            [ordered]@{ op = 'any'; value = $null }
        } else {
            [ordered]@{ op = 'in'; key = 'id'; value = @($id) }
        }

        $Group = [pscustomobject]@{ conditions = @([pscustomobject]$Condition) }

        $Output = @($ConditionDefinition) + @($Group) | Where-Object { $null -ne $_ }

        $Output | Add-CustomType -Type 'IdCmd.SecureAI.Definition.AgentCondition'

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function New-SAIMcpServer {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Custom')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Custom'
        )]
        [ValidateLength(1, 256)]
        [String]$name,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Custom'
        )]
        [ValidateLength(1, 500)]
        [String]$description,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Custom'
        )]
        [ValidateSet('COMMUNICATION_AND_TEAM_CHAT', 'EMAIL_AND_SCHEDULING', 'KNOWLEDGE_BASES_AND_DOCS',
            'FILE_STORAGE_AND_CONTENT_REPOS', 'DEVELOPER_TOOLS_AND_SOURCE_CONTROL', 'DATABASES_AND_DATA_STORES',
            'OBSERVABILITY_MONITORING_AND_TELEMETRY', 'ITSM_AND_INCIDENT_RESPONSE', 'WEB_AND_BROWSER_AUTOMATION',
            'CLOUD_AND_INFRASTRUCTURE_OPERATIONS')]
        [String]$category,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Custom'
        )]
        [ValidateLength(1, 2048)]
        [String]$upstreamUrl,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Predefined'
        )]
        [ValidateNotNullOrEmpty()]
        [String]$predefinedTargetId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Predefined'
        )]
        [ValidateLength(1, 500)]
        [Alias('predefinedDescription')]
        [String]$templateDescription,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('OAUTH2.1', 'NONE')]
        [String]$authMethodType,

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

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 512)]
        [String]$clientId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [securestring]$clientSecret,

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$owners,

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

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/targets/mcp-servers"

        if ($PSBoundParameters.ContainsKey('clientSecret') -and (-not $PSBoundParameters.ContainsKey('clientId'))) {
            throw 'clientSecret cannot be provided without clientId'
        }

        if ($PSBoundParameters.ContainsKey('registrationEndpoint') -and
            ($PSBoundParameters.ContainsKey('clientId') -or $PSBoundParameters.ContainsKey('clientSecret'))) {
            throw 'registrationEndpoint cannot be combined with clientId or clientSecret - supply manual credentials or a registration endpoint for dynamic client registration, not both'
        }

        $Target = [ordered]@{ }

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

            $Target['source'] = [ordered]@{ type = 'PREDEFINED'; predefinedTargetId = $predefinedTargetId }

            if ($PSBoundParameters.ContainsKey('templateDescription')) {
                $Target['description'] = $templateDescription
            }

        } else {

            $Target['source'] = [ordered]@{ type = 'CUSTOM' }
            $Target['name'] = $name
            $Target['description'] = $description
            $Target['category'] = $category
            $Target['upstream'] = [ordered]@{ url = $upstreamUrl }

        }

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

            $AuthMethod = [ordered]@{ type = $authMethodType }

            foreach ($Field in 'authorizationServer', 'JWKS', 'clientId', 'registrationEndpoint') {
                if ($PSBoundParameters.ContainsKey($Field)) {
                    $AuthMethod[$Field] = $PSBoundParameters[$Field]
                }
            }

            if ($PSBoundParameters.ContainsKey('clientSecret')) {
                $AuthMethod['clientSecret'] = $(ConvertTo-InsecureString -SecureString $clientSecret)
            }

            $Target['authMethod'] = $AuthMethod

        }

        foreach ($Field in 'owners', 'tags') {
            if ($PSBoundParameters.ContainsKey($Field)) {
                $Target[$Field] = $PSBoundParameters[$Field]
            }
        }

        #The body can carry an OAuth client secret, so it travels as UTF8 bytes
        $body = $Target | ConvertTo-SecretBody -Depth 8

        if ($PSCmdlet.ShouldProcess($(if ($PSCmdlet.ParameterSetName -eq 'Predefined') { $predefinedTargetId } else { $name }), 'Register MCP Server')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body -Accept $(Get-SAIApiHeader -Resource Targets)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function New-SAIOwnerDefinition {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')]
    [CmdletBinding()]
    [OutputType('IdCmd.SecureAI.Definition.Owner')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [String]$id,

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

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('USER', 'ROLE')]
        [String]$type,

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

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

        [parameter(Mandatory = $false)]
        [psobject[]]$OwnerDefinition
    )

    begin { }#begin

    process {

        if (($type -eq 'USER') -and (-not ($PSBoundParameters.ContainsKey('sourceDirectoryName') -and $PSBoundParameters.ContainsKey('sourceDirectoryId')))) {
            throw 'sourceDirectoryName and sourceDirectoryId are required for a USER owner'
        }

        $Owner = $PSBoundParameters | Get-Parameter -ParametersToRemove OwnerDefinition

        $Output = @($OwnerDefinition) + @([pscustomobject]$Owner) | Where-Object { $null -ne $_ }

        $Output | Add-CustomType -Type 'IdCmd.SecureAI.Definition.Owner'

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function New-SAIPolicy {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 256)]
        [String]$name,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('ON_BEHALF_OF', 'AUTONOMOUS')]
        [String]$accessType,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('ENABLED', 'DISABLED')]
        [String]$state,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$agent,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$resources,

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

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$owners,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$user
    )

    begin { }#begin

    process {

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

        #A user grant is required for an on-behalf-of policy and rejected for an autonomous one
        if (($accessType -eq 'ON_BEHALF_OF') -and (-not $PSBoundParameters.ContainsKey('user'))) {
            throw 'A user grant is required when accessType is ON_BEHALF_OF'
        }

        if (($accessType -eq 'AUTONOMOUS') -and $PSBoundParameters.ContainsKey('user')) {
            throw 'A user grant cannot be supplied when accessType is AUTONOMOUS'
        }

        $body = $PSBoundParameters | Get-Parameter | ConvertTo-Json -Depth 10

        if ($PSCmdlet.ShouldProcess($name, 'Create Secure AI Access Policy')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body -Accept $(Get-SAIApiHeader -Resource Policies)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function New-SAIResourceConditionDefinition {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')]
    [CmdletBinding(DefaultParameterSetName = 'MatchId')]
    [OutputType('IdCmd.SecureAI.Definition.ResourceCondition')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'MatchId'
        )]
        [ValidateNotNullOrEmpty()]
        [String]$matchId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'MatchTag'
        )]
        [ValidateNotNullOrEmpty()]
        [String]$matchTag,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'InId'
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]$id,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'InTag'
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]$tag,

        [parameter(Mandatory = $false)]
        [psobject[]]$ConditionDefinition
    )

    begin { }#begin

    process {

        $Condition = switch ($PSCmdlet.ParameterSetName) {

            'MatchTag' { [ordered]@{ op = 'match'; key = 'tag'; value = $matchTag } }
            'InId' { [ordered]@{ op = 'in'; key = 'id'; value = @($id) } }
            'InTag' { [ordered]@{ op = 'in'; key = 'tag'; value = @($tag) } }
            default { [ordered]@{ op = 'match'; key = 'id'; value = $matchId } }

        }

        $Group = [pscustomobject]@{ conditions = @([pscustomobject]$Condition) }

        $Output = @($ConditionDefinition) + @($Group) | Where-Object { $null -ne $_ }

        $Output | Add-CustomType -Type 'IdCmd.SecureAI.Definition.ResourceCondition'

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function New-SAIUserConditionDefinition {
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Function builds a definition object and does not change state')]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Any', Justification = 'Used via $PSCmdlet.ParameterSetName')]
    [CmdletBinding(DefaultParameterSetName = 'InId')]
    [OutputType('IdCmd.SecureAI.Definition.UserCondition')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'InId'
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]$id,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'InRole'
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]$roleId,

        [parameter(
            Mandatory = $true,
            ParameterSetName = 'Any'
        )]
        [switch]$Any,

        [parameter(Mandatory = $false)]
        [psobject[]]$ConditionDefinition
    )

    begin { }#begin

    process {

        $Condition = switch ($PSCmdlet.ParameterSetName) {

            'Any' { [ordered]@{ op = 'any'; value = $null } }
            'InRole' { [ordered]@{ op = 'in'; key = 'roleId'; value = @($roleId) } }
            default { [ordered]@{ op = 'in'; key = 'id'; value = @($id) } }

        }

        $Group = [pscustomobject]@{ conditions = @([pscustomobject]$Condition) }

        $Output = @($ConditionDefinition) + @($Group) | Where-Object { $null -ne $_ }

        $Output | Add-CustomType -Type 'IdCmd.SecureAI.Definition.UserCondition'

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Remove-SAIAgent {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 128)]
        [Alias('id')]
        [String]$agentId
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/agents/$agentId"

        if ($PSCmdlet.ShouldProcess($agentId, 'Delete AI Agent')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SAIApiHeader -Resource Agents)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Remove-SAIMcpServer {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$mcpServerId
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/targets/mcp-servers/$mcpServerId"

        if ($PSCmdlet.ShouldProcess($mcpServerId, 'Delete MCP Server')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SAIApiHeader -Resource Targets)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Remove-SAIPolicy {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$policyId
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/policies/$policyId"

        if ($PSCmdlet.ShouldProcess($policyId, 'Delete Secure AI Access Policy')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE -Accept $(Get-SAIApiHeader -Resource Policies)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Set-SAIAgent {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 128)]
        [Alias('id')]
        [String]$agentId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(2, 256)]
        [ValidatePattern('^[A-Za-z0-9 _-]+$')]
        [String]$name,

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$owners,

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

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/agents/$agentId"

        $body = $PSBoundParameters | Get-Parameter -ParametersToRemove agentId | ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($agentId, 'Update AI Agent')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method PUT -Body $body -Accept $(Get-SAIApiHeader -Resource Agents)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Set-SAIAgentState {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 128)]
        [Alias('id')]
        [String]$agentId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('ACTIVE', 'SUSPENDED')]
        [String]$state
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/agents/$agentId/state"

        $body = [ordered]@{ state = $state } | ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($agentId, "Set AI Agent State: $state")) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body -Accept $(Get-SAIApiHeader -Resource Agents)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Set-SAIMcpServer {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$mcpServerId,

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('COMMUNICATION_AND_TEAM_CHAT', 'EMAIL_AND_SCHEDULING', 'KNOWLEDGE_BASES_AND_DOCS',
            'FILE_STORAGE_AND_CONTENT_REPOS', 'DEVELOPER_TOOLS_AND_SOURCE_CONTROL', 'DATABASES_AND_DATA_STORES',
            'OBSERVABILITY_MONITORING_AND_TELEMETRY', 'ITSM_AND_INCIDENT_RESPONSE', 'WEB_AND_BROWSER_AUTOMATION',
            'CLOUD_AND_INFRASTRUCTURE_OPERATIONS')]
        [String]$category,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$owners,

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

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/targets/mcp-servers/$mcpServerId"

        $body = $PSBoundParameters | Get-Parameter -ParametersToRemove mcpServerId | ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($mcpServerId, 'Update MCP Server')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method PUT -Body $body -Accept $(Get-SAIApiHeader -Resource Targets)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Set-SAIMcpServerState {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$mcpServerId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('ENABLED', 'DISABLED')]
        [String]$state
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/targets/mcp-servers/$mcpServerId/state"

        $body = [ordered]@{ state = $state } | ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($mcpServerId, "Set MCP Server State: $state")) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body -Accept $(Get-SAIApiHeader -Resource Targets)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Set-SAIPolicy {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$policyId,

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('ENABLED', 'DISABLED')]
        [String]$state,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$agent,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$resources,

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

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$owners,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [psobject[]]$user
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/policies/$policyId"

        #The service leaves omitted fields unchanged, so only what was supplied is sent
        $body = $PSBoundParameters | Get-Parameter -ParametersToRemove policyId | ConvertTo-Json -Depth 10

        if ($PSCmdlet.ShouldProcess($policyId, 'Update Secure AI Access Policy')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method PUT -Body $body -Accept $(Get-SAIApiHeader -Resource Policies)

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecureAI-help.xml
function Set-SAIPolicyState {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$policyId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('ENABLED', 'DISABLED')]
        [String]$state
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/policies/$policyId/state"

        $body = [ordered]@{ state = $state } | ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($policyId, "Set Secure AI Access Policy State: $state")) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body -Accept $(Get-SAIApiHeader -Resource Policies)

        }

    }#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 'agentId' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SAIAgent' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName @(
    'Get-SAIAgent'
    'Remove-SAIAgent'
    'Set-SAIAgent'
    'Set-SAIAgentState'
)

Register-ArgumentCompleter -ParameterName 'mcpServerId' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SAIMcpServer' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName @(
    'Get-SAIMcpServer'
    'Remove-SAIMcpServer'
    'Set-SAIMcpServer'
    'Set-SAIMcpServerState'
)

#The predefined catalog is the source of the template id a registration is created from
Register-ArgumentCompleter -ParameterName 'predefinedTargetId' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SAIPredefinedMcpServer' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName 'New-SAIMcpServer'

Register-ArgumentCompleter -ParameterName 'templateId' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SAIPredefinedMcpServer' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName 'Get-SAIPredefinedMcpServer'

Register-ArgumentCompleter -ParameterName 'policyId' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SAIPolicy' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName @(
    'Get-SAIPolicy'
    'Remove-SAIPolicy'
    'Set-SAIPolicy'
    'Set-SAIPolicyState'
)

#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