Public/iso.ps1

function Get-CSISO {
    <#
    .SYNOPSIS
        Lists ISOs in CloudStack.

    .DESCRIPTION
        Retrieves ISOs with optional filtering by ID, name, zone, or hypervisor.
        Wraps the listIsos API call.

    .PARAMETER Id
        Filter by ISO ID

    .PARAMETER Name
        Filter by ISO name

    .PARAMETER Keyword
        Filter by keyword (partial match on name)

    .PARAMETER IsoFilter
        Which ISOs to list. Defaults to 'executable', i.e. the ISOs the caller
        is allowed to deploy from.

    .PARAMETER ZoneId
        Filter by zone ID

    .PARAMETER Hypervisor
        Filter by hypervisor type

    .PARAMETER Bootable
        Only return bootable ISOs

    .PARAMETER IsReady
        Only return ISOs that have finished downloading and are ready to use

    .PARAMETER Account
        Filter by account name (must be used with DomainId)

    .PARAMETER DomainId
        Filter by domain ID

    .PARAMETER ProjectId
        Filter by project ID

    .PARAMETER ListAll
        List all ISOs the caller has access to (requires appropriate permissions)

    .EXAMPLE
        Get-CSISO
        Lists every ISO available to deploy from

    .EXAMPLE
        Get-CSISO -Name "rocky-9.4-minimal"
        Gets a specific ISO by name

    .EXAMPLE
        Get-CSISO -ZoneId $zoneId -Bootable -IsReady
        Lists bootable, ready ISOs in a zone
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(ParameterSetName = 'ById')]
        [string]$Id,

        [Parameter(ParameterSetName = 'ByName')]
        [string]$Name,

        [Parameter(ParameterSetName = 'Default')]
        [string]$Keyword,

        [Parameter()]
        [ValidateSet('featured', 'self', 'selfexecutable', 'sharedexecutable', 'executable', 'community', 'all')]
        [string]$IsoFilter = 'executable',

        [Parameter()]
        [string]$ZoneId,

        [Parameter()]
        [string]$Hypervisor,

        [Parameter()]
        [switch]$Bootable,

        [Parameter()]
        [switch]$IsReady,

        [Parameter()]
        [string]$Account,

        [Parameter()]
        [string]$DomainId,

        [Parameter()]
        [string]$ProjectId,

        [Parameter()]
        [switch]$ListAll
    )

    # listIsos requires isofilter, so it is always sent
    $apiParams = @{
        'isofilter' = $IsoFilter
    }

    if ($PSBoundParameters.ContainsKey('Id')) {
        $apiParams['id'] = $Id
    }

    if ($PSBoundParameters.ContainsKey('Name')) {
        $apiParams['name'] = $Name
    }

    if ($PSBoundParameters.ContainsKey('Keyword')) {
        $apiParams['keyword'] = $Keyword
    }

    if ($PSBoundParameters.ContainsKey('ZoneId')) {
        $apiParams['zoneid'] = $ZoneId
    }

    if ($PSBoundParameters.ContainsKey('Hypervisor')) {
        $apiParams['hypervisor'] = $Hypervisor
    }

    if ($Bootable) {
        $apiParams['bootable'] = 'true'
    }

    if ($IsReady) {
        $apiParams['isready'] = 'true'
    }

    if ($PSBoundParameters.ContainsKey('Account')) {
        $apiParams['account'] = $Account
    }

    if ($PSBoundParameters.ContainsKey('DomainId')) {
        $apiParams['domainid'] = $DomainId
    }

    if ($PSBoundParameters.ContainsKey('ProjectId')) {
        $apiParams['projectid'] = $ProjectId
    }

    if ($ListAll) {
        $apiParams['listall'] = 'true'
    }

    $response = Invoke-CSApiRequest -Command 'listIsos' -Parameters $apiParams
    Write-Verbose "API Parameters: $($apiParams | Out-String)"

    if ($response.listisosresponse.iso) {
        return $response.listisosresponse.iso
    }
    else {
        Write-Verbose "No ISOs found matching the criteria."
        return $null
    }
}

function Mount-CSISO {
    <#
    .SYNOPSIS
        Attaches an ISO to a virtual machine.

    .DESCRIPTION
        Wraps the attachIso API. Optionally use -Forced to eject the ISO already
        attached to a VMware virtual machine before attaching this ISO.

    .PARAMETER IsoId
        The ISO to attach

    .PARAMETER VirtualMachineId
        The virtual machine to attach the ISO to

    .PARAMETER Forced
        Eject an ISO already attached to a VMware VM before attaching this one

    .EXAMPLE
        Mount-CSISO -IsoId 'iso-uuid' -VirtualMachineId 'vm-uuid'

        Attaches the selected ISO to the VM.

    .EXAMPLE
        Mount-CSISO -IsoId 'new-iso-uuid' -VirtualMachineId 'vm-uuid' -Forced

        Replaces the ISO currently attached to a VMware VM.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$IsoId,
        [Parameter(Mandatory=$true)][string]$VirtualMachineId,
        [switch]$Forced
    )
    $apiParams = @{ id = $IsoId; virtualmachineid = $VirtualMachineId }
    if ($Forced) { $apiParams['forced'] = 'true' }
    Invoke-CSApiRequest -Command 'attachIso' -Parameters $apiParams
}

function Copy-CSISO {
    <#
    .SYNOPSIS
        Copies an ISO to one or more zones.

    .DESCRIPTION
        Wraps the copyIso API. Use either -DestinationZoneId or -DestinationZoneIds,
        but not both. If neither is supplied, CloudStack can synchronize a cross-zone
        ISO to the region-wide image store.

    .PARAMETER IsoId
        The ISO to copy

    .PARAMETER DestinationZoneId
        Copy to this single zone. Mutually exclusive with -DestinationZoneIds.

    .PARAMETER DestinationZoneIds
        Copy to these zones. Mutually exclusive with -DestinationZoneId.

    .PARAMETER SourceZoneId
        The zone to copy from, when the ISO exists in more than one

    .EXAMPLE
        Copy-CSISO -IsoId 'iso-uuid' -DestinationZoneId 'zone-uuid'

        Copies an ISO to one destination zone.

    .EXAMPLE
        Copy-CSISO -IsoId 'iso-uuid' -DestinationZoneIds @('zone-a','zone-b')

        Copies an ISO to multiple zones in one request.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$IsoId,
        [string]$DestinationZoneId,
        [string[]]$DestinationZoneIds,
        [string]$SourceZoneId
    )
    if ($PSBoundParameters.ContainsKey('DestinationZoneId') -and $PSBoundParameters.ContainsKey('DestinationZoneIds')) {
        throw 'Specify DestinationZoneId or DestinationZoneIds, not both.'
    }
    if (-not $PSBoundParameters.ContainsKey('DestinationZoneId') -and -not $PSBoundParameters.ContainsKey('DestinationZoneIds')) {
        throw 'Specify a destination zone with DestinationZoneId or DestinationZoneIds.'
    }
    $apiParams = @{ id = $IsoId }
    if ($PSBoundParameters.ContainsKey('DestinationZoneId')) { $apiParams['destzoneid'] = $DestinationZoneId }
    if ($PSBoundParameters.ContainsKey('DestinationZoneIds')) { $apiParams['destzoneids'] = $DestinationZoneIds -join ',' }
    if ($PSBoundParameters.ContainsKey('SourceZoneId')) { $apiParams['sourcezoneid'] = $SourceZoneId }
    Invoke-CSApiRequest -Command 'copyIso' -Parameters $apiParams
}

function Remove-CSISO {
    <#
    .SYNOPSIS
        Deletes an ISO from a zone or from all zones.

    .DESCRIPTION
        Wraps the deleteIso API. When ZoneId is omitted, CloudStack deletes the ISO
        from all zones where it is present.

    .PARAMETER IsoId
        The ISO to delete

    .PARAMETER ZoneId
        Delete from this zone only. Omit to delete from all zones.

    .EXAMPLE
        Remove-CSISO -IsoId 'iso-uuid' -ZoneId 'zone-uuid'

        Removes the ISO from one zone only.

    .EXAMPLE
        Remove-CSISO -IsoId 'iso-uuid'

        Removes the ISO from all zones. Use -WhatIf to preview this action.
    #>

    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')]
    param(
        [Parameter(Mandatory=$true)][string]$IsoId,
        [string]$ZoneId
    )
    $target = if ($PSBoundParameters.ContainsKey('ZoneId')) { "ISO $IsoId in zone $ZoneId" } else { "ISO $IsoId in all zones" }
    $apiParams = @{ id = $IsoId }
    if ($PSBoundParameters.ContainsKey('ZoneId')) { $apiParams['zoneid'] = $ZoneId }
    if ($PSCmdlet.ShouldProcess($target, 'Delete')) {
        Invoke-CSApiRequest -Command 'deleteIso' -Parameters $apiParams
    }
}

function Dismount-CSISO {
    <#
    .SYNOPSIS
        Detaches the currently mounted ISO from a virtual machine.

    .DESCRIPTION
        Wraps the detachIso API, ejecting whatever ISO is currently attached to the
        virtual machine. Use -Forced to request forced ejection on VMware.

    .PARAMETER VirtualMachineId
        The virtual machine to detach the ISO from

    .PARAMETER Forced
        Request forced ejection when detaching from VMware

    .EXAMPLE
        Dismount-CSISO -VirtualMachineId 'vm-uuid'

        Detaches any ISO currently attached to the VM.

    .EXAMPLE
        Dismount-CSISO -VirtualMachineId 'vm-uuid' -Forced

        Requests forced ejection when detaching from VMware.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$VirtualMachineId,
        [switch]$Forced
    )
    $apiParams = @{ virtualmachineid = $VirtualMachineId }
    if ($Forced) { $apiParams['forced'] = 'true' }
    Invoke-CSApiRequest -Command 'detachIso' -Parameters $apiParams
}

function Export-CSISO {
    <#
    .SYNOPSIS
        Extracts an ISO from CloudStack.

    .DESCRIPTION
        Wraps the extractIso API. HTTP_DOWNLOAD creates a download URL. FTP_UPLOAD
        uploads the extracted ISO to the supplied URL.

    .PARAMETER IsoId
        The ISO to extract

    .PARAMETER Mode
        HTTP_DOWNLOAD (get a download URL) or FTP_UPLOAD (push to -Url)

    .PARAMETER Url
        Destination URL. Required when -Mode is FTP_UPLOAD.

    .PARAMETER ZoneId
        The zone to extract the ISO from

    .EXAMPLE
        Export-CSISO -IsoId 'iso-uuid' -Mode HTTP_DOWNLOAD -ZoneId 'zone-uuid'

        Requests an HTTP download URL for the ISO.

    .EXAMPLE
        Export-CSISO -IsoId 'iso-uuid' -Mode FTP_UPLOAD -Url 'ftp://backup.example.com/isos/'

        Requests extraction to the specified FTP destination.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$IsoId,
        [Parameter(Mandatory=$true)][ValidateSet('HTTP_DOWNLOAD','FTP_UPLOAD')][string]$Mode,
        [string]$Url,
        [string]$ZoneId
    )
    if ($Mode -eq 'FTP_UPLOAD' -and -not $PSBoundParameters.ContainsKey('Url')) {
        throw 'Url is required when Mode is FTP_UPLOAD.'
    }
    $apiParams = @{ id = $IsoId; mode = $Mode }
    if ($PSBoundParameters.ContainsKey('Url')) { $apiParams['url'] = $Url }
    if ($PSBoundParameters.ContainsKey('ZoneId')) { $apiParams['zoneid'] = $ZoneId }
    Invoke-CSApiRequest -Command 'extractIso' -Parameters $apiParams
}

function Get-CSISOUploadParams {
    <#
    .SYNOPSIS
        Gets the signed upload parameters for uploading a new ISO.

    .DESCRIPTION
        Wraps the getUploadParamsForIso API. Returns CloudStack's upload URL,
        signature, metadata, and upload ID. The caller still needs to POST the ISO
        file using the returned values.

    .PARAMETER Format
        The ISO format (typically ISO)

    .PARAMETER Name
        A name for the ISO

    .PARAMETER ZoneId
        The zone to upload the ISO into

    .PARAMETER Account
        Upload on behalf of this account. Must be used with -DomainId.

    .PARAMETER Bootable
        Whether the ISO is bootable (requires -OsTypeId)

    .PARAMETER Checksum
        Checksum of the ISO, to verify the upload

    .PARAMETER DisplayText
        Description shown in the UI

    .PARAMETER DomainId
        The domain of -Account

    .PARAMETER IsExtractable
        Whether the ISO can be extracted/downloaded by its owner

    .PARAMETER IsFeatured
        Mark the ISO as featured

    .PARAMETER IsPublic
        Make the ISO public within the domain

    .PARAMETER OsTypeId
        The OS type ID (required for a bootable ISO; see Get-CSOsType)

    .PARAMETER ProjectId
        Upload into this project

    .EXAMPLE
        Get-CSISOUploadParams -Name 'Rocky Linux 9 ISO' -ZoneId 'zone-uuid' -Format ISO -OsTypeId 'os-type-uuid'

        Gets the values needed to upload a bootable ISO into the selected zone.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$Format,
        [Parameter(Mandatory=$true)][string]$Name,
        [Parameter(Mandatory=$true)][string]$ZoneId,
        [string]$Account,
        [bool]$Bootable,
        [string]$Checksum,
        [string]$DisplayText,
        [string]$DomainId,
        [bool]$IsExtractable,
        [bool]$IsFeatured,
        [bool]$IsPublic,
        [string]$OsTypeId,
        [string]$ProjectId
    )
    if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
        throw 'DomainId is required when Account is specified.'
    }
    if ($PSBoundParameters.ContainsKey('Bootable') -and $Bootable -and -not $PSBoundParameters.ContainsKey('OsTypeId')) {
        throw 'OsTypeId is required for a bootable ISO.'
    }
    $apiParams = @{ format = $Format; name = $Name; zoneid = $ZoneId }
    $parameterMap = @{
        Account='account'; Bootable='bootable'; Checksum='checksum'; DisplayText='displaytext';
        DomainId='domainid'; IsExtractable='isextractable'; IsFeatured='isfeatured';
        IsPublic='ispublic'; OsTypeId='ostypeid'; ProjectId='projectid'
    }
    foreach ($parameter in $parameterMap.Keys) {
        if ($PSBoundParameters.ContainsKey($parameter)) {
            $value = Get-Variable -Name $parameter -ValueOnly
            if ($value -is [bool]) { $value = $value.ToString().ToLowerInvariant() }
            $apiParams[$parameterMap[$parameter]] = $value
        }
    }
    Invoke-CSApiRequest -Command 'getUploadParamsForIso' -Parameters $apiParams
}

function Get-CSISOPermission {
    <#
    .SYNOPSIS
        Gets visibility and account permissions for an ISO.

    .DESCRIPTION
        Wraps the listIsoPermissions API, returning whether the ISO is public and
        which accounts have been granted access to it.

    .PARAMETER IsoId
        The ISO to inspect

    .EXAMPLE
        Get-CSISOPermission -IsoId 'iso-uuid'

        Returns whether the ISO is public and which accounts can access it.
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory=$true)][string]$IsoId)
    $response = Invoke-CSApiRequest -Command 'listIsoPermissions' -Parameters @{ id = $IsoId }
    if ($response.listisopermissionsresponse) { return $response.listisopermissionsresponse }
}

function New-CSISO {
    <#
    .SYNOPSIS
        Registers an existing ISO URL with CloudStack.

    .DESCRIPTION
        Wraps the registerIso API. The URL must be reachable by CloudStack secondary
        storage. Account must be paired with DomainId. For a bootable ISO, provide
        OsTypeId.

    .PARAMETER Name
        A name for the ISO

    .PARAMETER Url
        URL of the ISO image

    .PARAMETER ZoneId
        The zone to register the ISO into

    .PARAMETER Account
        Register on behalf of this account. Must be used with -DomainId.

    .PARAMETER Arch
        CPU architecture: x86_64 or aarch64

    .PARAMETER Bootable
        Whether the ISO is bootable (default $true; a bootable ISO needs -OsTypeId)

    .PARAMETER Checksum
        Checksum of the ISO, as {algorithm}hash

    .PARAMETER DirectDownload
        Download the ISO straight to primary storage on the host (KVM)

    .PARAMETER DisplayText
        Description shown in the UI

    .PARAMETER DomainId
        The domain of -Account

    .PARAMETER ImageStoreUuid
        Register into this specific image store

    .PARAMETER IsDynamicallyScalable
        The ISO supports dynamic scaling of CPU and memory

    .PARAMETER IsExtractable
        The ISO can be extracted/downloaded by its owner

    .PARAMETER IsFeatured
        Mark the ISO as featured

    .PARAMETER IsPublic
        Make the ISO public within the domain

    .PARAMETER OsTypeId
        The OS type ID (required for a bootable ISO; see Get-CSOsType)

    .PARAMETER PasswordEnabled
        The ISO has the password-reset script installed

    .PARAMETER ProjectId
        Register into this project

    .EXAMPLE
        New-CSISO -Name 'Rocky Linux 9' -Url 'https://images.example.com/rocky-9.iso' -ZoneId 'zone-uuid' -OsTypeId 'os-type-uuid'

        Registers a bootable ISO in a zone.

    .EXAMPLE
        New-CSISO -Name 'Rescue image' -Url 'https://images.example.com/rescue.iso' -ZoneId 'zone-uuid' -Bootable:$false -IsPublic

        Registers a public, non-bootable ISO.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$Name,
        [Parameter(Mandatory=$true)][string]$Url,
        [Parameter(Mandatory=$true)][string]$ZoneId,
        [string]$Account,
        [ValidateSet('x86_64','aarch64')][string]$Arch,
        [bool]$Bootable = $true,
        [string]$Checksum,
        [switch]$DirectDownload,
        [string]$DisplayText,
        [string]$DomainId,
        [string]$ImageStoreUuid,
        [switch]$IsDynamicallyScalable,
        [switch]$IsExtractable,
        [switch]$IsFeatured,
        [switch]$IsPublic,
        [string]$OsTypeId,
        [switch]$PasswordEnabled,
        [string]$ProjectId
    )
    if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
        throw 'DomainId is required when Account is specified.'
    }
    if ($Bootable -and -not $PSBoundParameters.ContainsKey('OsTypeId')) {
        throw 'OsTypeId is required for a bootable ISO. Use -Bootable:$false for a non-bootable ISO.'
    }
    $apiParams = @{ name = $Name; url = $Url; zoneid = $ZoneId; bootable = $Bootable.ToString().ToLowerInvariant() }
    $parameterMap = @{
        Account='account'; Arch='arch'; Checksum='checksum'; DirectDownload='directdownload';
        DisplayText='displaytext'; DomainId='domainid'; ImageStoreUuid='imagestoreuuid';
        OsTypeId='ostypeid'; ProjectId='projectid'
    }
    foreach ($parameter in $parameterMap.Keys) {
        if ($PSBoundParameters.ContainsKey($parameter)) { $apiParams[$parameterMap[$parameter]] = (Get-Variable -Name $parameter -ValueOnly) }
    }
    foreach ($parameter in @('DirectDownload','IsDynamicallyScalable','IsExtractable','IsFeatured','IsPublic','PasswordEnabled')) {
        if (Get-Variable -Name $parameter -ValueOnly) { $apiParams[$parameter.ToLowerInvariant()] = 'true' }
    }
    Invoke-CSApiRequest -Command 'registerIso' -Parameters $apiParams
}

function Set-CSISO {
    <#
    .SYNOPSIS
        Updates an ISO's metadata and deployment properties.

    .DESCRIPTION
        Wraps the updateIso API. Only the attributes you supply are changed; use
        -CleanupDetails to clear stored details.

    .PARAMETER IsoId
        The ISO to update

    .PARAMETER Arch
        CPU architecture: x86_64 or aarch64

    .PARAMETER Bootable
        Whether the ISO is bootable

    .PARAMETER CleanupDetails
        Clear all stored details instead of setting them

    .PARAMETER Details
        Replacement key/value details as a hashtable

    .PARAMETER DisplayText
        New description

    .PARAMETER Format
        New ISO format

    .PARAMETER IsDynamicallyScalable
        Whether the ISO supports dynamic scaling of CPU and memory

    .PARAMETER IsRouting
        Whether the ISO is a system (routing) ISO

    .PARAMETER Name
        New name

    .PARAMETER OsTypeId
        New OS type ID

    .PARAMETER PasswordEnabled
        Whether the password-reset script is installed

    .PARAMETER SortKey
        Ordering key used when listing ISOs

    .PARAMETER SshKeyEnabled
        Whether the ISO supports SSH key injection

    .EXAMPLE
        Set-CSISO -IsoId 'iso-uuid' -DisplayText 'Rocky Linux installer' -Bootable:$true -OsTypeId 'os-type-uuid'

        Updates the ISO display text and OS type.

    .EXAMPLE
        Set-CSISO -IsoId 'iso-uuid' -Name 'Rocky Linux 9.4' -CleanupDetails

        Renames the ISO and clears its stored details.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$IsoId,
        [ValidateSet('x86_64','aarch64')][string]$Arch,
        [bool]$Bootable,
        [switch]$CleanupDetails,
        [hashtable]$Details,
        [string]$DisplayText,
        [string]$Format,
        [switch]$IsDynamicallyScalable,
        [bool]$IsRouting,
        [string]$Name,
        [string]$OsTypeId,
        [bool]$PasswordEnabled,
        [int]$SortKey,
        [bool]$SshKeyEnabled
    )
    $apiParams = @{ id = $IsoId }
    $parameterMap = @{
        Arch='arch'; Bootable='bootable'; CleanupDetails='cleanupdetails'; DisplayText='displaytext';
        Format='format'; IsRouting='isrouting'; Name='name'; OsTypeId='ostypeid';
        PasswordEnabled='passwordenabled'; SortKey='sortkey'; SshKeyEnabled='sshkeyenabled'
    }
    foreach ($parameter in $parameterMap.Keys) {
        if ($PSBoundParameters.ContainsKey($parameter)) {
            $value = Get-Variable -Name $parameter -ValueOnly
            if ($value -is [bool]) { $value = $value.ToString().ToLowerInvariant() }
            $apiParams[$parameterMap[$parameter]] = $value
        }
    }
    foreach ($parameter in @('IsDynamicallyScalable')) {
        if (Get-Variable -Name $parameter -ValueOnly) { $apiParams['isdynamicallyscalable'] = 'true' }
    }
    if ($Details) {
        $index = 0
        foreach ($key in $Details.Keys) {
            $apiParams["details[$index].$key"] = $Details[$key]
            $index++
        }
    }
    Invoke-CSApiRequest -Command 'updateIso' -Parameters $apiParams
}

function Set-CSISOPermission {
    <#
    .SYNOPSIS
        Changes ISO visibility and sharing permissions.

    .DESCRIPTION
        Wraps the updateIsoPermissions API. When supplying Accounts or ProjectIds,
        also specify PermissionOperator (add, remove, or reset).

    .PARAMETER IsoId
        The ISO to change

    .PARAMETER Accounts
        Accounts to add or remove (with -PermissionOperator)

    .PARAMETER IsExtractable
        Whether the ISO can be extracted/downloaded

    .PARAMETER IsFeatured
        Whether the ISO is featured

    .PARAMETER IsPublic
        Whether the ISO is public within the domain

    .PARAMETER PermissionOperator
        add, remove, or reset the listed accounts/projects

    .PARAMETER ProjectIds
        Projects to add or remove (with -PermissionOperator)

    .EXAMPLE
        Set-CSISOPermission -IsoId 'iso-uuid' -IsPublic:$true

        Makes an ISO publicly visible.

    .EXAMPLE
        Set-CSISOPermission -IsoId 'iso-uuid' -Accounts @('build','qa') -PermissionOperator add

        Grants the listed accounts access to the ISO.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)][string]$IsoId,
        [string[]]$Accounts,
        [bool]$IsExtractable,
        [bool]$IsFeatured,
        [bool]$IsPublic,
        [ValidateSet('add','remove','reset')][string]$PermissionOperator,
        [string[]]$ProjectIds
    )
    if (($PSBoundParameters.ContainsKey('Accounts') -or $PSBoundParameters.ContainsKey('ProjectIds')) -and -not $PSBoundParameters.ContainsKey('PermissionOperator')) {
        throw 'PermissionOperator is required when Accounts or ProjectIds is specified.'
    }
    $apiParams = @{ id = $IsoId }
    if ($PSBoundParameters.ContainsKey('Accounts')) { $apiParams['accounts'] = $Accounts -join ',' }
    if ($PSBoundParameters.ContainsKey('ProjectIds')) { $apiParams['projectids'] = $ProjectIds -join ',' }
    if ($PSBoundParameters.ContainsKey('PermissionOperator')) { $apiParams['op'] = $PermissionOperator }
    foreach ($parameter in @('IsExtractable','IsFeatured','IsPublic')) {
        if ($PSBoundParameters.ContainsKey($parameter)) {
            $apiParams[$parameter.ToLowerInvariant()] = (Get-Variable -Name $parameter -ValueOnly).ToString().ToLowerInvariant()
        }
    }
    Invoke-CSApiRequest -Command 'updateIsoPermissions' -Parameters $apiParams
}