IdentityCommand.SecretsHub.psm1

function ConvertTo-SHFilterString {
    <#
    .SYNOPSIS
    Assembles a Secrets Hub filter expression.
 
    .DESCRIPTION
    Formats filter clauses into the query language accepted by the Secrets Hub service. The dialect
    is deliberately simple: clauses are juxtaposed and joined with AND, there is no OR, and
    parentheses are not supported.
 
        name CONTAINS my AND storeName CONTAINS our
 
    Comparison is by EQ, CONTAINS, NOTCONTAINS, NEQ or GE, and values are case insensitive. A value
    containing whitespace is quoted, and a quote character within a quoted value is escaped with a
    backslash - both handled by ConvertTo-FilterClause. Callers pass raw values and never pre-quote.
 
    The expression is not url encoded - Add-QueryString encodes the query string as a whole.
 
    .PARAMETER Filter
    The clauses to assemble, each a hashtable with Field, Operator and Value keys.
 
    .EXAMPLE
    ConvertTo-SHFilterString -Filter @{ Field = 'name'; Operator = 'CONTAINS'; Value = 'my' }
 
    Outputs: name CONTAINS my
 
    .EXAMPLE
    ConvertTo-SHFilterString -Filter @(
        @{ Field = 'name'; Operator = 'CONTAINS'; Value = 'my' }
        @{ Field = 'storeName'; Operator = 'CONTAINS'; Value = 'our' }
    )
 
    Outputs: name CONTAINS my AND storeName CONTAINS our
 
    .EXAMPLE
    ConvertTo-SHFilterString -Filter @{ Field = 'name'; Operator = 'EQ'; Value = 'my value' }
 
    Outputs: name EQ "my value"
 
    .OUTPUTS
    String
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true
        )]
        [hashtable[]]$Filter
    )

    begin {

        #The documented query language lists EQ, CONTAINS and NOTCONTAINS; NEQ and GE are confirmed
        #additionally, both observed live against /api/secrets (vendorSubType NEQ, GE on a numeric
        #field) and matching that endpoint's own filter table. HAS (secret stores' behaviors field)
        #and LE (paired with GE on several /api/secrets fields) remain unconfirmed and excluded.
        $ValidOperator = @('EQ', 'CONTAINS', 'NOTCONTAINS', 'NEQ', 'GE')

        $Clauses = [System.Collections.Generic.List[string]]::new()

    }

    process {

        foreach ($Clause in $Filter) {

            if (-not $Clause.ContainsKey('Field')) {
                throw 'Each filter clause requires a Field'
            }

            if (-not $Clause.ContainsKey('Operator')) {
                throw "Filter clause for field '$($Clause['Field'])' requires an Operator"
            }

            if (-not $Clause.ContainsKey('Value')) {
                throw "Filter clause for field '$($Clause['Field'])' requires a Value"
            }

            $Operator = $Clause['Operator']

            if ($Operator -notin $ValidOperator) {
                throw "'$Operator' is not a filter operator supported by the Secrets Hub service. Valid operators are: $($ValidOperator -join ', ')"
            }

            $Clauses.Add($(ConvertTo-FilterClause -Field $Clause['Field'] -Operator $Operator -Value $Clause['Value']))

        }

    }

    end {

        if ($Clauses.Count -eq 0) {

            return

        }

        $Clauses -join ' AND '

    }

}

function Get-SHApiHeader {
    <#
    .SYNOPSIS
    Returns the Accept header value for the Secrets Hub beta API surface.
 
    .DESCRIPTION
    Several Secrets Hub endpoints are documented as Beta and require an explicit Accept header on
    every call; a request without it is refused with 406 Not Acceptable. The value is held here so
    the commands which need it do not each carry a copy.
 
    Pass the returned value to Invoke-IDRestMethod's -Accept parameter.
 
    .EXAMPLE
    Invoke-IDRestMethod -Uri $URI -Method GET -Accept $(Get-SHApiHeader)
 
    .OUTPUTS
    String
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param()

    'application/x.secretshub.beta+json'

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Connect-SHTenant {

    [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('secretshub_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 secrets_hub -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 secrets_hub -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.SecretsHub 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.SecretsHub-help.xml
function Get-SHConfiguration {
    [CmdletBinding()]
    param()

    begin { }#begin

    process {

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

        #Send Request
        Invoke-IDRestMethod -Uri $URI -Method GET

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Get-SHModuleData {

    [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.SecretsHub-help.xml
function Get-SHSecret {
    [CmdletBinding(DefaultParameterSetName = 'byQuery')]
    param(
        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byQuery'
        )]
        [ValidateNotNullOrEmpty()]
        [String]$filter,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byFilterCriteria'
        )]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]$FilterCriteria,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('EXTEND', 'REGULAR')]
        [String]$projection,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateRange(0, 150000)]
        [int]$offset,

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 100)]
        [String]$sort,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 200)]
        [String]$search
    )

    begin { }#begin

    process {

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

        $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove FilterCriteria

        if ($PSBoundParameters.ContainsKey('FilterCriteria')) {
            $boundParameters['filter'] = ConvertTo-SHFilterString -Filter $FilterCriteria
        }

        $URI = Add-QueryString -URI $URI -Parameter $boundParameters

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

        if ($null -ne $result) {

            Get-PagedResult -InitialResult $result -URI $URI -Style Offset -ResultProperty 'secrets' -TotalResponseKey 'totalCount'

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Get-SHSecretsFilter {
    [CmdletBinding(DefaultParameterSetName = 'byStore')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [String]$storeId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byId'
        )]
        [ValidateNotNullOrEmpty()]
        [String]$filterId
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/$storeId/filters"

        if ($PSCmdlet.ParameterSetName -eq 'byId') {
            $URI = "$URI/$filterId"
        }

        #Send Request
        Invoke-IDRestMethod -Uri $URI -Method GET

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Get-SHSecretStore {
    [CmdletBinding(DefaultParameterSetName = 'byQuery')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byId'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$storeId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byQuery'
        )]
        [ValidateLength(1, 2000)]
        [String]$filter,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byFilterCriteria'
        )]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]$FilterCriteria,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byQuery'
        )]
        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byFilterCriteria'
        )]
        [ValidateRange(0, 2147483647)]
        [int]$offset,

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

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byQuery'
        )]
        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byFilterCriteria'
        )]
        [ValidateLength(1, 100)]
        [String]$sort,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byQuery'
        )]
        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byFilterCriteria'
        )]
        [ValidateLength(1, 100)]
        [String]$search
    )

    begin { }#begin

    process {

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

            $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/$storeId"

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method GET

        } else {

            $URI = "$($ISPSSSession.tenant_url)/api/secret-stores"

            $boundParameters = $PSBoundParameters | Get-Parameter -ParametersToRemove FilterCriteria

            if ($PSBoundParameters.ContainsKey('FilterCriteria')) {
                $boundParameters['filter'] = ConvertTo-SHFilterString -Filter $FilterCriteria
            }

            $URI = Add-QueryString -URI $URI -Parameter $boundParameters

            #Send Request
            $result = Invoke-IDRestMethod -Uri $URI -Method GET

            if ($null -ne $result) {

                Get-PagedResult -InitialResult $result -URI $URI -Style Offset -ResultProperty 'secretStores' -TotalResponseKey 'totalCount'

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Get-SHSyncPolicy {
    [CmdletBinding(DefaultParameterSetName = 'byQuery')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byId'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$policyId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byQuery'
        )]
        [ValidateNotNullOrEmpty()]
        [String]$filter,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('EXTEND', 'REGULAR', 'METADATA')]
        [String]$projection,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'byQuery'
        )]
        [ValidateRange(0, 150000)]
        [int]$offset,

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

    begin { }#begin

    process {

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

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

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method GET

        } 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

            if ($null -ne $result) {

                #The policies response reports the tenant total as count, with no separate totalCount
                Get-PagedResult -InitialResult $result -URI $URI -Style Offset -ResultProperty 'policies' -TotalResponseKey 'count'

            }

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Get-SHTransformation {
    [CmdletBinding()]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$transformationId
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/transformations/$transformationId"

        #Send Request
        Invoke-IDRestMethod -Uri $URI -Method GET

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function New-SHSecretsFilter {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [String]$storeId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 28)]
        [String]$safeName
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/$storeId/filters"

        #PAM_SAFE is the only filter type the service supports
        $body = [ordered]@{ type = 'PAM_SAFE'; data = @{ safeName = $safeName } } | ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($safeName, 'Create Secrets Filter')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function New-SHSecretStore {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('PAM_PCLOUD', 'PAM_SELF_HOSTED', 'AWS_ASM', 'AZURE_AKV', 'GCP_GSM', 'HASHICORP_VAULT', 'HASHICORP_VAULT_ENT')]
        [String]$type,

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

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

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

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

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secret-stores"

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

        if ($PSCmdlet.ShouldProcess($name, 'Create Secret Store')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function New-SHSyncPolicy {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'BySafeName')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 100)]
        [String]$name,

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

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

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

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'BySafeName'
        )]
        [ValidateLength(1, 28)]
        [String]$safeName,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'ByFilterId'
        )]
        [ValidateNotNullOrEmpty()]
        [String]$filterId,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('password_only_plain_text')]
        [String]$transformation
    )

    begin { }#begin

    process {

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

        $Policy = [ordered]@{
            name   = $name
            source = @{ id = $sourceId }
            target = @{ id = $targetId }
        }

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

        $Policy['filter'] = if ($PSCmdlet.ParameterSetName -eq 'ByFilterId') {
            @{ id = $filterId }
        } else {
            [ordered]@{ type = 'PAM_SAFE'; data = @{ safeName = $safeName } }
        }

        if ($PSBoundParameters.ContainsKey('transformation')) {
            $Policy['transformation'] = @{ predefined = $transformation }
        }

        $body = $Policy | ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($name, 'Create Sync Policy')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method POST -Body $body

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Publish-SHSecret {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('AWS_ASM', 'GCP_GSM', 'AZURE_AKV', 'HASHICORP_VAULT', 'HASHICORP_VAULT_ENT')]
        [String]$sourceSecretStoreType,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('PAM_PCLOUD', 'PAM_SELF_HOSTED')]
        [String]$targetSecretStoreType,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$secretId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('PLAINTEXT', 'JSON', 'TEMPLATE')]
        [String]$secretValueType,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateLength(1, 28)]
        [String]$safeName,

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

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secrets-onboarding/$sourceSecretStoreType/$targetSecretStoreType"

        $body = $PSBoundParameters | Get-Parameter -ParametersToRemove sourceSecretStoreType, targetSecretStoreType |
            ConvertTo-Json -Depth 8

        if ($PSCmdlet.ShouldProcess($secretId, 'Onboard Secret')) {

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

        }

    }#process

    end { }#end

}

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

    begin { }#begin

    process {

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

        #The service requires fromTarget, and documents true as its only accepted value
        $URI = Add-QueryString -URI $URI -Parameter @{ fromTarget = 'true' }

        if ($PSCmdlet.ShouldProcess($secretId, 'Delete Secret From Target Secret Store')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Remove-SHSecretsFilter {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [String]$storeId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$filterId
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/$storeId/filters/$filterId"

        if ($PSCmdlet.ShouldProcess($filterId, 'Delete Secrets Filter')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE

        }

    }#process

    end { }#end

}

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

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/$storeId"

        if ($PSCmdlet.ShouldProcess($storeId, 'Delete Secret Store')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Remove-SHSyncPolicy {
    [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 Sync Policy')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method DELETE

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Set-SHConfiguration {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateRange(1, 730)]
        [int]$secretValidity,

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

    begin { }#begin

    process {

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

        $SyncSettings = [ordered]@{ }

        if ($PSBoundParameters.ContainsKey('secretValidity')) {
            $SyncSettings['secretValidity'] = $secretValidity
        }

        if ($PSBoundParameters.ContainsKey('gcpReplicationRegion')) {
            #An empty list reverts regional replication to GCP's default global replication
            $SyncSettings['gcp'] = @{ secretReplication = @{ regions = @($gcpReplicationRegion) } }
        }

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

        if ($PSCmdlet.ShouldProcess('Secrets Hub Configuration', 'Update Configuration')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method PATCH -Body $body

        }

    }#process

    end { }#end

}

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

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

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

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

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/$storeId"

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

        if ($PSCmdlet.ShouldProcess($storeId, 'Update Secret Store')) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method PATCH -Body $body

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Set-SHSecretStoreState {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Single')]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Single'
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$storeId,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'Bulk'
        )]
        [ValidateCount(1, 500)]
        [String[]]$secretStoreIds,

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('enable', 'disable')]
        [String]$action
    )

    begin { }#begin

    process {

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

            $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/states"
            $Target = "$($secretStoreIds.Count) secret stores"
            $body = [ordered]@{ action = $action; secretStoreIds = $secretStoreIds } | ConvertTo-Json -Depth 8

        } else {

            $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/$storeId/state"
            $Target = $storeId
            $body = [ordered]@{ action = $action } | ConvertTo-Json -Depth 8

        }

        if ($PSCmdlet.ShouldProcess($Target, "Set Secret Store State: $action")) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method PUT -Body $body

        }

    }#process

    end { }#end

}

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

        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateSet('enable', 'disable')]
        [String]$action
    )

    begin { }#begin

    process {

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

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

        if ($PSCmdlet.ShouldProcess($policyId, "Set Sync Policy State: $action")) {

            #Send Request
            Invoke-IDRestMethod -Uri $URI -Method PUT -Body $body

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Start-SHScan {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateCount(1, 1)]
        [Alias('storeId')]
        [String[]]$secretStoresIds,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [String]$type = 'secret-store',

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [String]$id = 'default'
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/scan-definitions/$type/$id/scan"

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

        if ($PSCmdlet.ShouldProcess($secretStoresIds -join ', ', 'Trigger Scan')) {

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

        }

    }#process

    end { }#end

}

# .ExternalHelp IdentityCommand.SecretsHub-help.xml
function Test-SHSecretStoreConnection {
    [CmdletBinding()]
    param(
        [parameter(
            Mandatory = $true,
            ValueFromPipelinebyPropertyName = $true
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('id')]
        [String]$storeId
    )

    begin { }#begin

    process {

        $URI = "$($ISPSSSession.tenant_url)/api/secret-stores/$storeId/status/connection"

        #Send Request
        Invoke-IDRestMethod -Uri $URI -Method GET

    }#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 'storeId' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SHSecretStore' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName @(
    'Get-SHSecretStore'
    'Get-SHSecretsFilter'
    'New-SHSecretsFilter'
    'Remove-SHSecretStore'
    'Remove-SHSecretsFilter'
    'Set-SHSecretStore'
    'Set-SHSecretStoreState'
    'Test-SHSecretStoreConnection'
)

Register-ArgumentCompleter -ParameterName 'secretStoresIds' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SHSecretStore' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName 'Start-SHScan'

Register-ArgumentCompleter -ParameterName 'secretStoreIds' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SHSecretStore' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName 'Set-SHSecretStoreState'

Register-ArgumentCompleter -ParameterName 'sourceId' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SHSecretStore' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName 'New-SHSyncPolicy'

Register-ArgumentCompleter -ParameterName 'targetId' -ScriptBlock (
    Get-ArgumentCompleter -RetrievalCommand 'Get-SHSecretStore' -ValueProperty 'id' -LabelProperty 'name'
) -CommandName 'New-SHSyncPolicy'

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

#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