Public/volume.ps1

function Get-CSVolume {
    <#
    .SYNOPSIS
        Lists disk volumes.

    .DESCRIPTION
        Retrieves volumes (listVolumes) with optional filtering. Accepts VM objects
        (or VM names) on the pipeline and lists the volumes attached to each VM,
        so 'Get-CSVM web-01 | Get-CSVolume' returns that VM's disks.

    .PARAMETER Id
        Filter by volume ID

    .PARAMETER Ids
        Filter by several volume IDs. Mutually exclusive with -Id.

    .PARAMETER Name
        Filter by volume name

    .PARAMETER Keyword
        Filter by keyword (partial match)

    .PARAMETER VM
        A VM name or VM object whose volumes to list. Binds from the pipeline.

    .PARAMETER VirtualMachineId
        List the volumes attached to this VM ID

    .PARAMETER Type
        Filter by disk type: ROOT or DATADISK

    .PARAMETER State
        Filter by volume state

    .PARAMETER ZoneId
        Filter by zone ID

    .PARAMETER PodId
        Filter by pod ID

    .PARAMETER ClusterId
        Filter by cluster ID

    .PARAMETER HostId
        Filter by host ID

    .PARAMETER StorageId
        Filter by primary storage pool ID (root admin only)

    .PARAMETER DiskOfferingId
        Filter by disk offering ID

    .PARAMETER ServiceOfferingId
        Filter by the service offering of the VM the volume belongs to. Ignored
        when -DiskOfferingId is also given.

    .PARAMETER Tags
        Filter by resource tags, as a hashtable of key = value pairs

    .PARAMETER Account
        Filter by account name. Must be used with -DomainId.

    .PARAMETER DomainId
        Filter by domain ID

    .PARAMETER ProjectId
        Filter by project ID (-1 for all projects)

    .PARAMETER DisplayVolume
        Filter by whether the volume is displayed to the end user (root admin only)

    .PARAMETER IsEncrypted
        Filter by whether the volume is encrypted

    .PARAMETER IsRecursive
        With -DomainId, also include volumes in subdomains

    .PARAMETER ListAll
        List every volume the caller is allowed to see

    .PARAMETER ListSystemVMs
        Include volumes that belong to system VMs (root admin only)

    .PARAMETER Page
        Page number of results to return

    .PARAMETER PageSize
        Number of results per page

    .EXAMPLE
        Get-CSVolume
        Lists every volume in your account.

    .EXAMPLE
        Get-CSVM -Name 'web-01' | Get-CSVolume
        Lists the volumes attached to a VM.

    .EXAMPLE
        Get-CSVolume -Type DATADISK -State Ready -ListAll | Where-Object { -not $_.virtualmachineid }
        Finds data disks that are not attached to any VM.

    .EXAMPLE
        Get-CSVolume -Tags @{ environment = 'production' }
        Lists volumes tagged environment=production.
    #>

    [CmdletBinding()]
    param(
        [string]$Id,

        [string[]]$Ids,

        [string]$Name,

        [string]$Keyword,

        [Parameter(ValueFromPipeline = $true)]
        [object]$VM,

        [string]$VirtualMachineId,

        [ValidateSet('ROOT', 'DATADISK')]
        [string]$Type,

        [ValidateSet('Ready', 'Allocated', 'Destroy', 'Expunging', 'Expunged')]
        [string]$State,

        [string]$ZoneId,

        [string]$PodId,

        [string]$ClusterId,

        [string]$HostId,

        [string]$StorageId,

        [string]$DiskOfferingId,

        [string]$ServiceOfferingId,

        [hashtable]$Tags,

        [string]$Account,

        [string]$DomainId,

        [string]$ProjectId,

        [bool]$DisplayVolume,

        [bool]$IsEncrypted,

        [switch]$IsRecursive,

        [switch]$ListAll,

        [switch]$ListSystemVMs,

        [int]$Page,

        [int]$PageSize
    )

    process {
        if ($PSBoundParameters.ContainsKey('Id') -and $PSBoundParameters.ContainsKey('Ids')) {
            throw 'Specify either -Id or -Ids, not both.'
        }
        if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
            throw 'DomainId is required when Account is specified.'
        }

        $apiParams = @{}
        Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
            Id = 'id'; Ids = 'ids'; Name = 'name'; Keyword = 'keyword'; VirtualMachineId = 'virtualmachineid'
            Type = 'type'; State = 'state'; ZoneId = 'zoneid'; PodId = 'podid'; ClusterId = 'clusterid'
            HostId = 'hostid'; StorageId = 'storageid'; DiskOfferingId = 'diskofferingid'
            ServiceOfferingId = 'serviceofferingid'; Account = 'account'; DomainId = 'domainid'
            ProjectId = 'projectid'; DisplayVolume = 'displayvolume'; IsEncrypted = 'isencrypted'
            IsRecursive = 'isrecursive'; ListAll = 'listall'; ListSystemVMs = 'listsystemvms'
            Page = 'page'; PageSize = 'pagesize'
        })
        if ($PSBoundParameters.ContainsKey('VM')) {
            if ($PSBoundParameters.ContainsKey('VirtualMachineId')) {
                throw 'Specify either -VM or -VirtualMachineId, not both.'
            }
            $apiParams['virtualmachineid'] = Resolve-CSVMTagId -VM $VM
        }
        Add-CSMapParameter -ApiParameters $apiParams -Name 'tags' -Map $Tags -KeyField 'key' -ValueField 'value'

        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'listVolumes' -Parameters $apiParams) -Command 'listVolumes'
    }
}

function New-CSVolume {
    <#
    .SYNOPSIS
        Creates a disk volume.

    .DESCRIPTION
        Creates a DATA volume (createVolume) from a disk offering or from a volume
        snapshot. A volume created from a disk offering is left detached unless
        -VirtualMachineId/-VM is given; attach it later with Mount-CSVolume. This is
        an asynchronous job; use -Wait to get the new volume back instead of the
        job handle.

    .PARAMETER Name
        The name of the volume

    .PARAMETER DiskOfferingId
        The disk offering to create the volume from. Either a disk offering or
        -SnapshotId is required. Mutually exclusive with -DiskOfferingName.

    .PARAMETER DiskOfferingName
        The exact name of the disk offering. Mutually exclusive with -DiskOfferingId.

    .PARAMETER SnapshotId
        The volume snapshot to create the volume from

    .PARAMETER ZoneId
        The zone to create the volume in. Mutually exclusive with -ZoneName.

    .PARAMETER ZoneName
        The exact name of the zone. Mutually exclusive with -ZoneId.

    .PARAMETER Size
        Size in GB. Required when the disk offering is a custom-size offering.

    .PARAMETER MinIops
        Minimum IOPS (custom-IOPS disk offerings)

    .PARAMETER MaxIops
        Maximum IOPS (custom-IOPS disk offerings)

    .PARAMETER VirtualMachineId
        Attach the new volume to this VM once it has been created

    .PARAMETER VM
        A VM name or VM object to attach the new volume to. Alternative to -VirtualMachineId.

    .PARAMETER DisplayVolume
        Whether to display the volume to the end user

    .PARAMETER Account
        Account that will own the volume. Must be used with -DomainId.

    .PARAMETER DomainId
        Domain of the owning account

    .PARAMETER ProjectId
        Project that will own the volume. Mutually exclusive with -Account.

    .PARAMETER CustomId
        A custom UUID for the volume (root admin only)

    .PARAMETER Wait
        Wait for the async job to finish and return the new volume

    .EXAMPLE
        New-CSVolume -Name 'web-01-data' -DiskOfferingName '100GB Standard' -ZoneName 'us-east-1' -Wait
        Creates a 100 GB data volume and returns it once it exists.

    .EXAMPLE
        New-CSVolume -Name 'scratch' -DiskOfferingName 'Custom' -Size 250 -ZoneName 'us-east-1' -VM 'web-01' -Wait
        Creates a 250 GB volume from a custom-size offering and attaches it to web-01.

    .EXAMPLE
        New-CSVolume -Name 'restored-data' -SnapshotId snap-uuid -Wait | Mount-CSVolume -VM 'web-01' -Wait
        Recreates a volume from a snapshot, then attaches it to a VM.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param(
        [string]$Name,

        [string]$DiskOfferingId,

        [string]$DiskOfferingName,

        [string]$SnapshotId,

        [string]$ZoneId,

        [string]$ZoneName,

        [long]$Size,

        [long]$MinIops,

        [long]$MaxIops,

        [string]$VirtualMachineId,

        [object]$VM,

        [bool]$DisplayVolume,

        [string]$Account,

        [string]$DomainId,

        [string]$ProjectId,

        [string]$CustomId,

        [switch]$Wait
    )

    if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
        throw 'DomainId is required when Account is specified.'
    }
    if ($PSBoundParameters.ContainsKey('Account') -and $PSBoundParameters.ContainsKey('ProjectId')) {
        throw 'Specify either -Account or -ProjectId, not both.'
    }
    if ($PSBoundParameters.ContainsKey('VM') -and $PSBoundParameters.ContainsKey('VirtualMachineId')) {
        throw 'Specify either -VM or -VirtualMachineId, not both.'
    }

    $resolvedDiskOfferingId = Resolve-CSObjectId -Id $DiskOfferingId -Name $DiskOfferingName `
        -TypeName 'disk offering' -IdParameter 'DiskOfferingId' -NameParameter 'DiskOfferingName' `
        -Lookup { param($lookupName) Get-CSDiskOffering -Name $lookupName }
    if (-not $resolvedDiskOfferingId -and -not $PSBoundParameters.ContainsKey('SnapshotId')) {
        throw 'A disk offering (-DiskOfferingId/-DiskOfferingName) or -SnapshotId is required.'
    }
    $resolvedZoneId = Resolve-CSObjectId -Id $ZoneId -Name $ZoneName `
        -TypeName 'zone' -IdParameter 'ZoneId' -NameParameter 'ZoneName' `
        -Lookup { param($lookupName) Get-CSZone -Name $lookupName }

    $apiParams = @{}
    if ($resolvedDiskOfferingId) { $apiParams['diskofferingid'] = $resolvedDiskOfferingId }
    if ($resolvedZoneId) { $apiParams['zoneid'] = $resolvedZoneId }
    if ($PSBoundParameters.ContainsKey('VM')) { $apiParams['virtualmachineid'] = Resolve-CSVMTagId -VM $VM }
    Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
        Name = 'name'; SnapshotId = 'snapshotid'; Size = 'size'; MinIops = 'miniops'; MaxIops = 'maxiops'
        VirtualMachineId = 'virtualmachineid'; DisplayVolume = 'displayvolume'; Account = 'account'
        DomainId = 'domainid'; ProjectId = 'projectid'; CustomId = 'customid'
    })

    $target = if ($Name) { "volume $Name" } else { 'volume' }
    if ($PSCmdlet.ShouldProcess($target, 'Create')) {
        Invoke-CSAsyncApiRequest -Command 'createVolume' -Parameters $apiParams -Wait:$Wait
    }
}

function Remove-CSVolume {
    <#
    .SYNOPSIS
        Destroys a volume.

    .DESCRIPTION
        Destroys a volume (destroyVolume). Like Remove-CSVM, a destroyed volume
        can be brought back with Restore-CSDestroyedVolume until it is expunged;
        add -Expunge to remove it permanently in one step. Use Clear-CSVolume
        (deleteVolume) to delete a detached volume outright. This is an
        asynchronous job; use -Wait to block until it finishes. Accepts volume
        objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to destroy (binds from a piped volume's id)

    .PARAMETER Expunge
        Expunge the volume immediately instead of leaving it recoverable

    .PARAMETER Wait
        Wait for the async job to finish and return its result

    .EXAMPLE
        Remove-CSVolume -Id vol-uuid
        Destroys a volume after prompting for confirmation. It stays recoverable
        until the storage cleanup interval expunges it.

    .EXAMPLE
        Get-CSVolume -Keyword 'scratch-' -Type DATADISK | Remove-CSVolume -Expunge -Confirm:$false
        Permanently removes every scratch data volume without prompting.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [switch]$Expunge,

        [switch]$Wait
    )

    process {
        $apiParams = @{ id = $Id }
        if ($Expunge) { $apiParams['expunge'] = 'true' }
        $action = if ($Expunge) { 'Destroy and expunge' } else { 'Destroy' }
        if ($PSCmdlet.ShouldProcess("volume $Id", $action)) {
            Invoke-CSAsyncApiRequest -Command 'destroyVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Clear-CSVolume {
    <#
    .SYNOPSIS
        Deletes a detached volume permanently.

    .DESCRIPTION
        Deletes a detached disk volume (deleteVolume). Unlike Remove-CSVolume
        (destroyVolume), this cannot be undone with Restore-CSDestroyedVolume.
        Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to delete (binds from a piped volume's id)

    .EXAMPLE
        Clear-CSVolume -Id vol-uuid
        Permanently deletes a detached volume after prompting for confirmation.

    .EXAMPLE
        Get-CSVolume -State Destroy -ListAll | Clear-CSVolume -Confirm:$false
        Permanently deletes every already-destroyed volume.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id
    )

    process {
        if ($PSCmdlet.ShouldProcess("volume $Id", 'Delete permanently')) {
            ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'deleteVolume' -Parameters @{ id = $Id }) -Command 'deleteVolume'
        }
    }
}

function Restore-CSDestroyedVolume {
    <#
    .SYNOPSIS
        Recovers a destroyed volume.

    .DESCRIPTION
        Recovers a volume in the Destroy state (recoverVolume) that has not yet
        been expunged. Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to recover (binds from a piped volume's id)

    .EXAMPLE
        Restore-CSDestroyedVolume -Id vol-uuid
        Recovers a destroyed volume.

    .EXAMPLE
        Get-CSVolume -Name 'web-01-data' -State Destroy | Restore-CSDestroyedVolume
        Recovers a destroyed volume found by name.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id
    )

    process {
        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'recoverVolume' -Parameters @{ id = $Id }) -Command 'recoverVolume'
    }
}

function Mount-CSVolume {
    <#
    .SYNOPSIS
        Attaches a volume to a virtual machine.

    .DESCRIPTION
        Attaches a disk volume to a VM (attachVolume). The VM can be given by ID,
        name, or object. This is an asynchronous job; use -Wait to get the
        attached volume back. Accepts volume objects from Get-CSVolume or
        New-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to attach (binds from a piped volume's id)

    .PARAMETER VirtualMachineId
        The ID of the VM to attach the volume to

    .PARAMETER VM
        A VM name or VM object to attach the volume to. Alternative to -VirtualMachineId.

    .PARAMETER DeviceId
        The device ID in the guest. Defaults to the next free slot; 0 attaches the
        volume as the ROOT disk.

    .PARAMETER Wait
        Wait for the async job to finish and return the attached volume

    .EXAMPLE
        Mount-CSVolume -Id vol-uuid -VM 'web-01' -Wait
        Attaches a volume to a VM found by name and waits for the result.

    .EXAMPLE
        Get-CSVolume -Name 'web-01-data' | Mount-CSVolume -VirtualMachineId vm-uuid
        Attaches a volume found by name to a VM.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [string]$VirtualMachineId,

        [object]$VM,

        [long]$DeviceId,

        [switch]$Wait
    )

    process {
        if ($PSBoundParameters.ContainsKey('VM') -and $PSBoundParameters.ContainsKey('VirtualMachineId')) {
            throw 'Specify either -VM or -VirtualMachineId, not both.'
        }
        $resolvedVmId = Resolve-CSVMTagId -VirtualMachineId $VirtualMachineId -VM $VM
        $apiParams = @{ id = $Id; virtualmachineid = $resolvedVmId }
        if ($PSBoundParameters.ContainsKey('DeviceId')) { $apiParams['deviceid'] = $DeviceId }
        if ($PSCmdlet.ShouldProcess("volume $Id", "Attach to VM $resolvedVmId")) {
            Invoke-CSAsyncApiRequest -Command 'attachVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Dismount-CSVolume {
    <#
    .SYNOPSIS
        Detaches a volume from a virtual machine.

    .DESCRIPTION
        Detaches a disk volume (detachVolume), identified either by its volume ID
        or by the VM and device ID it is attached at. This is an asynchronous job;
        use -Wait to get the detached volume back. Accepts volume objects from
        Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to detach (binds from a piped volume's id)

    .PARAMETER VirtualMachineId
        The VM to detach from, used together with -DeviceId

    .PARAMETER VM
        A VM name or VM object to detach from, used together with -DeviceId

    .PARAMETER DeviceId
        The device ID of the volume on the VM

    .PARAMETER Wait
        Wait for the async job to finish and return the detached volume

    .EXAMPLE
        Dismount-CSVolume -Id vol-uuid -Wait
        Detaches a volume and waits for the result.

    .EXAMPLE
        Get-CSVM -Name 'web-01' | Get-CSVolume -Type DATADISK | Dismount-CSVolume -Confirm:$false
        Detaches every data disk from a VM.

    .EXAMPLE
        Dismount-CSVolume -VM 'web-01' -DeviceId 1
        Detaches whatever volume is attached at device 1 on web-01.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ById')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ById')]
        [Alias('VolumeId')]
        [string]$Id,

        [Parameter(ParameterSetName = 'ByDevice')]
        [string]$VirtualMachineId,

        [Parameter(ParameterSetName = 'ByDevice')]
        [object]$VM,

        [Parameter(Mandatory = $true, ParameterSetName = 'ByDevice')]
        [long]$DeviceId,

        [switch]$Wait
    )

    process {
        if ($PSCmdlet.ParameterSetName -eq 'ById') {
            $apiParams = @{ id = $Id }
            $target = "volume $Id"
        }
        else {
            if ($PSBoundParameters.ContainsKey('VM') -and $PSBoundParameters.ContainsKey('VirtualMachineId')) {
                throw 'Specify either -VM or -VirtualMachineId, not both.'
            }
            $resolvedVmId = Resolve-CSVMTagId -VirtualMachineId $VirtualMachineId -VM $VM
            $apiParams = @{ virtualmachineid = $resolvedVmId; deviceid = $DeviceId }
            $target = "device $DeviceId on VM $resolvedVmId"
        }
        if ($PSCmdlet.ShouldProcess($target, 'Detach volume')) {
            Invoke-CSAsyncApiRequest -Command 'detachVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Resize-CSVolume {
    <#
    .SYNOPSIS
        Resizes a volume.

    .DESCRIPTION
        Resizes a volume (resizeVolume), either to a new size on a custom-size
        offering or by switching to a different disk offering. Shrinking requires
        -ShrinkOk. This is an asynchronous job; use -Wait to get the resized
        volume back. Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to resize (binds from a piped volume's id)

    .PARAMETER Size
        The new size in GB

    .PARAMETER DiskOfferingId
        A new disk offering to resize to. Mutually exclusive with -DiskOfferingName.

    .PARAMETER DiskOfferingName
        The exact name of the new disk offering. Mutually exclusive with -DiskOfferingId.

    .PARAMETER MinIops
        New minimum IOPS (custom-IOPS offerings)

    .PARAMETER MaxIops
        New maximum IOPS (custom-IOPS offerings)

    .PARAMETER ShrinkOk
        Allow the volume to shrink. Data past the new size is lost.

    .PARAMETER Wait
        Wait for the async job to finish and return the resized volume

    .EXAMPLE
        Resize-CSVolume -Id vol-uuid -Size 200 -Wait
        Grows a custom-size volume to 200 GB.

    .EXAMPLE
        Get-CSVM -Name 'web-01' | Get-CSVolume -Type ROOT | Resize-CSVolume -Size 80
        Grows a VM's root disk to 80 GB.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [long]$Size,

        [string]$DiskOfferingId,

        [string]$DiskOfferingName,

        [long]$MinIops,

        [long]$MaxIops,

        [switch]$ShrinkOk,

        [switch]$Wait
    )

    process {
        $resolvedDiskOfferingId = Resolve-CSObjectId -Id $DiskOfferingId -Name $DiskOfferingName `
            -TypeName 'disk offering' -IdParameter 'DiskOfferingId' -NameParameter 'DiskOfferingName' `
            -Lookup { param($lookupName) Get-CSDiskOffering -Name $lookupName }
        if (-not $resolvedDiskOfferingId -and -not $PSBoundParameters.ContainsKey('Size') -and
            -not $PSBoundParameters.ContainsKey('MinIops') -and -not $PSBoundParameters.ContainsKey('MaxIops')) {
            throw 'Specify a new -Size, a new disk offering, or new IOPS limits.'
        }

        $apiParams = @{ id = $Id }
        if ($resolvedDiskOfferingId) { $apiParams['diskofferingid'] = $resolvedDiskOfferingId }
        Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
            Size = 'size'; MinIops = 'miniops'; MaxIops = 'maxiops'; ShrinkOk = 'shrinkok'
        })
        $description = if ($PSBoundParameters.ContainsKey('Size')) { "Resize to $Size GB" } else { 'Resize' }
        if ($PSCmdlet.ShouldProcess("volume $Id", $description)) {
            Invoke-CSAsyncApiRequest -Command 'resizeVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Set-CSVolumeDiskOffering {
    <#
    .SYNOPSIS
        Changes a volume's disk offering.

    .DESCRIPTION
        Moves a volume to a different disk offering (changeOfferingForVolume),
        optionally migrating it to a storage pool that satisfies the new offering.
        This is an asynchronous job; use -Wait to get the updated volume back.
        Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume (binds from a piped volume's id)

    .PARAMETER DiskOfferingId
        The new disk offering. Mutually exclusive with -DiskOfferingName; one is required.

    .PARAMETER DiskOfferingName
        The exact name of the new disk offering. Mutually exclusive with -DiskOfferingId.

    .PARAMETER Size
        New size in GB (custom-size offerings)

    .PARAMETER MinIops
        New minimum IOPS (custom-IOPS offerings)

    .PARAMETER MaxIops
        New maximum IOPS (custom-IOPS offerings)

    .PARAMETER AutoMigrate
        Migrate the volume automatically if its current pool does not suit the new offering

    .PARAMETER ShrinkOk
        Allow the volume to shrink

    .PARAMETER Wait
        Wait for the async job to finish and return the updated volume

    .EXAMPLE
        Set-CSVolumeDiskOffering -Id vol-uuid -DiskOfferingName 'Premium SSD' -AutoMigrate -Wait
        Moves a volume to a faster offering, migrating it if necessary.

    .EXAMPLE
        Get-CSVolume -DiskOfferingId old-offering-uuid -ListAll | Set-CSVolumeDiskOffering -DiskOfferingId new-offering-uuid
        Moves every volume off a retired disk offering.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [string]$DiskOfferingId,

        [string]$DiskOfferingName,

        [long]$Size,

        [long]$MinIops,

        [long]$MaxIops,

        [switch]$AutoMigrate,

        [switch]$ShrinkOk,

        [switch]$Wait
    )

    begin {
        # Resolve once: every piped volume moves to the same offering.
        $resolvedDiskOfferingId = Resolve-CSObjectId -Id $DiskOfferingId -Name $DiskOfferingName -Required `
            -TypeName 'disk offering' -IdParameter 'DiskOfferingId' -NameParameter 'DiskOfferingName' `
            -Lookup { param($lookupName) Get-CSDiskOffering -Name $lookupName }
    }

    process {
        $apiParams = @{ id = $Id; diskofferingid = $resolvedDiskOfferingId }
        Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
            Size = 'size'; MinIops = 'miniops'; MaxIops = 'maxiops'; AutoMigrate = 'automigrate'; ShrinkOk = 'shrinkok'
        })
        if ($PSCmdlet.ShouldProcess("volume $Id", "Change disk offering to $resolvedDiskOfferingId")) {
            Invoke-CSAsyncApiRequest -Command 'changeOfferingForVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Move-CSVolume {
    <#
    .SYNOPSIS
        Migrates a volume to another primary storage pool.

    .DESCRIPTION
        Migrates a volume (migrateVolume) to a different storage pool. A volume
        attached to a running VM needs -LiveMigrate. This is an asynchronous job;
        use -Wait to get the migrated volume back. Accepts volume objects from
        Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to migrate (binds from a piped volume's id)

    .PARAMETER StorageId
        The destination storage pool ID. Never bound from the pipeline, because a
        piped volume's own storageid is its current pool.

    .PARAMETER LiveMigrate
        Live-migrate a volume that is attached to a running VM

    .PARAMETER NewDiskOfferingId
        A disk offering to switch the volume to as part of the migration

    .PARAMETER Wait
        Wait for the async job to finish and return the migrated volume

    .EXAMPLE
        Move-CSVolume -Id vol-uuid -StorageId pool-uuid -Wait
        Migrates a detached volume to another pool.

    .EXAMPLE
        Get-CSVolume -StorageId old-pool-uuid -ListAll | Move-CSVolume -StorageId new-pool-uuid -LiveMigrate
        Evacuates every volume from a storage pool, live-migrating attached ones.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [Parameter(Mandatory = $true)]
        [string]$StorageId,

        [switch]$LiveMigrate,

        [string]$NewDiskOfferingId,

        [switch]$Wait
    )

    process {
        $apiParams = @{ volumeid = $Id; storageid = $StorageId }
        Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
            LiveMigrate = 'livemigrate'; NewDiskOfferingId = 'newdiskofferingid'
        })
        if ($PSCmdlet.ShouldProcess("volume $Id", "Migrate to storage pool $StorageId")) {
            Invoke-CSAsyncApiRequest -Command 'migrateVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Set-CSVolume {
    <#
    .SYNOPSIS
        Updates a volume's properties.

    .DESCRIPTION
        Updates a volume (updateVolume): rename it, hide it from end users, or
        protect it from deletion. -Path, -State, -StorageId and -ChainInfo change
        CloudStack's record of where the volume lives and are meant for admins
        repairing the database. This is an asynchronous job; use -Wait to get the
        updated volume back. Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to update (binds from a piped volume's id)

    .PARAMETER Name
        A new name for the volume

    .PARAMETER DisplayVolume
        Whether to display the volume to the end user

    .PARAMETER DeleteProtection
        Protect the volume from deletion. Ignored for volumes managed by autoscale or CKS.

    .PARAMETER CustomId
        A custom UUID for the volume (root admin only)

    .PARAMETER Path
        The volume's path on primary storage (admin repair)

    .PARAMETER State
        The volume's state (admin repair)

    .PARAMETER StorageId
        The UUID of the storage pool the volume is on (admin repair). Never bound
        from the pipeline.

    .PARAMETER ChainInfo
        The volume's chain info (admin repair)

    .PARAMETER Wait
        Wait for the async job to finish and return the updated volume

    .EXAMPLE
        Set-CSVolume -Id vol-uuid -Name 'web-01-data'
        Renames a volume.

    .EXAMPLE
        Get-CSVM -Name 'db-01' | Get-CSVolume | Set-CSVolume -DeleteProtection $true -Wait
        Turns on delete protection for every volume on a VM.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [string]$Name,

        [bool]$DisplayVolume,

        [bool]$DeleteProtection,

        [string]$CustomId,

        [string]$Path,

        [string]$State,

        [string]$StorageId,

        [string]$ChainInfo,

        [switch]$Wait
    )

    process {
        $apiParams = @{ id = $Id }
        Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
            Name = 'name'; DisplayVolume = 'displayvolume'; DeleteProtection = 'deleteprotection'
            CustomId = 'customid'; Path = 'path'; State = 'state'; StorageId = 'storageid'; ChainInfo = 'chaininfo'
        })
        if ($apiParams.Count -eq 1) {
            throw 'Specify at least one property to update.'
        }
        if ($PSCmdlet.ShouldProcess("volume $Id", 'Update')) {
            Invoke-CSAsyncApiRequest -Command 'updateVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Set-CSVolumeOwner {
    <#
    .SYNOPSIS
        Changes the owner of a volume.

    .DESCRIPTION
        Reassigns a volume to another account or project (assignVolume). Accepts
        volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume (binds from a piped volume's id)

    .PARAMETER AccountId
        The ID of the account to give the volume to. Mutually exclusive with -ProjectId.

    .PARAMETER ProjectId
        The ID of the project to give the volume to. Mutually exclusive with -AccountId.

    .EXAMPLE
        Set-CSVolumeOwner -Id vol-uuid -AccountId account-uuid
        Gives a volume to another account.

    .EXAMPLE
        Get-CSVolume -Name 'shared-data' | Set-CSVolumeOwner -ProjectId project-uuid
        Moves a volume found by name into a project.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [string]$AccountId,

        [string]$ProjectId
    )

    process {
        $hasAccount = $PSBoundParameters.ContainsKey('AccountId')
        $hasProject = $PSBoundParameters.ContainsKey('ProjectId')
        if ($hasAccount -eq $hasProject) {
            throw 'Specify exactly one of -AccountId or -ProjectId.'
        }
        $apiParams = @{ volumeid = $Id }
        if ($hasAccount) { $apiParams['accountid'] = $AccountId; $newOwner = "account $AccountId" }
        else { $apiParams['projectid'] = $ProjectId; $newOwner = "project $ProjectId" }
        if ($PSCmdlet.ShouldProcess("volume $Id", "Assign to $newOwner")) {
            ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'assignVolume' -Parameters $apiParams) -Command 'assignVolume'
        }
    }
}

function Test-CSVolume {
    <#
    .SYNOPSIS
        Checks a volume for errors and leaks, optionally repairing them.

    .DESCRIPTION
        Runs a consistency check on a volume (checkVolume). KVM only; the volume
        must be detached or its VM stopped. With -Repair, CloudStack also fixes
        what it finds. This is an asynchronous job; use -Wait to get the check
        result back. Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to check (binds from a piped volume's id)

    .PARAMETER Repair
        Repair what the check finds: 'leaks' for leaked clusters only, 'all' for every error

    .PARAMETER Wait
        Wait for the async job to finish and return the result

    .EXAMPLE
        Test-CSVolume -Id vol-uuid -Wait
        Checks a volume and returns the result.

    .EXAMPLE
        Get-CSVM -Name 'web-01' | Get-CSVolume | Test-CSVolume -Repair leaks -Wait
        Checks every disk of a stopped VM and repairs leaked clusters.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [ValidateSet('leaks', 'all')]
        [string]$Repair,

        [switch]$Wait
    )

    process {
        $apiParams = @{ id = $Id }
        if ($PSBoundParameters.ContainsKey('Repair')) { $apiParams['repair'] = $Repair }
        # A read-only check needs no confirmation; only a repair changes the disk.
        if (-not $Repair -or $PSCmdlet.ShouldProcess("volume $Id", "Check and repair ($Repair)")) {
            Invoke-CSAsyncApiRequest -Command 'checkVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Export-CSVolume {
    <#
    .SYNOPSIS
        Extracts a volume for download or upload.

    .DESCRIPTION
        Extracts a volume (extractVolume). HTTP_DOWNLOAD produces a download URL;
        FTP_UPLOAD pushes the volume to -Url. This is an asynchronous job; use
        -Wait to get the extract result, which carries the download URL. Accepts
        volume objects from Get-CSVolume on the pipeline; the volume's zone binds
        to -ZoneId automatically.

    .PARAMETER Id
        The ID of the volume to extract (binds from a piped volume's id)

    .PARAMETER ZoneId
        The zone the volume is in (binds from a piped volume's zoneid)

    .PARAMETER Mode
        HTTP_DOWNLOAD or FTP_UPLOAD

    .PARAMETER Url
        Destination URL. Required for FTP_UPLOAD.

    .PARAMETER Wait
        Wait for the async job to finish and return the extract result

    .EXAMPLE
        (Export-CSVolume -Id vol-uuid -ZoneId zone-uuid -Mode HTTP_DOWNLOAD -Wait).url
        Gets a download URL for a volume.

    .EXAMPLE
        Get-CSVolume -Name 'web-01-data' | Export-CSVolume -Mode HTTP_DOWNLOAD -Wait
        Extracts a volume found by name; its zone comes from the volume object.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$ZoneId,

        [Parameter(Mandatory = $true)]
        [ValidateSet('HTTP_DOWNLOAD', 'FTP_UPLOAD')]
        [string]$Mode,

        [string]$Url,

        [switch]$Wait
    )

    process {
        if ($Mode -eq 'FTP_UPLOAD' -and -not $PSBoundParameters.ContainsKey('Url')) {
            throw 'Url is required when Mode is FTP_UPLOAD.'
        }
        $apiParams = @{ id = $Id; zoneid = $ZoneId; mode = $Mode }
        if ($PSBoundParameters.ContainsKey('Url')) { $apiParams['url'] = $Url }
        Invoke-CSAsyncApiRequest -Command 'extractVolume' -Parameters $apiParams -Wait:$Wait
    }
}

function Get-CSVolumePath {
    <#
    .SYNOPSIS
        Gets a volume's path on primary storage.

    .DESCRIPTION
        Returns the storage path CloudStack has recorded for a volume
        (getPathForVolume). Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume (binds from a piped volume's id)

    .EXAMPLE
        Get-CSVolumePath -Id vol-uuid
        Returns the volume's storage path.

    .EXAMPLE
        Get-CSVM -Name 'web-01' | Get-CSVolume | Get-CSVolumePath
        Returns the storage path of every disk on a VM.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id
    )

    process {
        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'getPathForVolume' -Parameters @{ volumeid = $Id }) -Command 'getPathForVolume'
    }
}

function Get-CSVolumeIscsiName {
    <#
    .SYNOPSIS
        Gets a volume's iSCSI name.

    .DESCRIPTION
        Returns the iSCSI name (IQN) of a volume on managed storage
        (getVolumeiScsiName). Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume (binds from a piped volume's id)

    .EXAMPLE
        Get-CSVolumeIscsiName -Id vol-uuid
        Returns the volume's iSCSI name.

    .EXAMPLE
        Get-CSVolume -StorageId solidfire-pool-uuid -ListAll | Get-CSVolumeIscsiName
        Returns the iSCSI name of every volume on a managed storage pool.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id
    )

    process {
        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'getVolumeiScsiName' -Parameters @{ volumeid = $Id }) -Command 'getVolumeiScsiName'
    }
}

function Get-CSVolumeUsageHistory {
    <#
    .SYNOPSIS
        Lists volume usage-history statistics.

    .DESCRIPTION
        Returns the stats CloudStack has collected for volumes
        (listVolumesUsageHistory). Accepts volume objects from Get-CSVolume on the
        pipeline.

    .PARAMETER Id
        The ID of the volume (binds from a piped volume's id)

    .PARAMETER Ids
        Several volume IDs. Mutually exclusive with -Id.

    .PARAMETER Name
        Filter by volume name (substring match)

    .PARAMETER Keyword
        Filter by keyword

    .PARAMETER StartDate
        Start of the period, "yyyy-MM-dd HH:mm:ss"

    .PARAMETER EndDate
        End of the period, "yyyy-MM-dd HH:mm:ss"

    .PARAMETER Page
        Page number of results to return

    .PARAMETER PageSize
        Number of results per page

    .EXAMPLE
        Get-CSVolumeUsageHistory -Id vol-uuid -StartDate '2026-09-01 00:00:00'
        Returns a volume's stats since the start of September.

    .EXAMPLE
        Get-CSVM -Name 'db-01' | Get-CSVolume | Get-CSVolumeUsageHistory
        Returns the stats for every disk on a VM.
    #>

    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [string[]]$Ids,

        [string]$Name,

        [string]$Keyword,

        [string]$StartDate,

        [string]$EndDate,

        [int]$Page,

        [int]$PageSize
    )

    process {
        if ($PSBoundParameters.ContainsKey('Id') -and $PSBoundParameters.ContainsKey('Ids')) {
            throw 'Specify either -Id or -Ids, not both.'
        }
        $apiParams = @{}
        Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
            Id = 'id'; Ids = 'ids'; Name = 'name'; Keyword = 'keyword'; StartDate = 'startdate'; EndDate = 'enddate'
            Page = 'page'; PageSize = 'pagesize'
        })
        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'listVolumesUsageHistory' -Parameters $apiParams) -Command 'listVolumesUsageHistory'
    }
}

function Get-CSVolumeUploadParams {
    <#
    .SYNOPSIS
        Gets the signed parameters for uploading a data disk from your machine.

    .DESCRIPTION
        Returns CloudStack's POST URL, signature, metadata, expiry and the new
        volume's ID (getUploadParamsForVolume). You still have to POST the disk
        image to the returned URL yourself. To have CloudStack fetch an image from
        a web server instead, use Import-CSVolumeFromUrl.

    .PARAMETER Name
        The name of the new volume

    .PARAMETER ZoneId
        The zone to create the volume in. Mutually exclusive with -ZoneName.

    .PARAMETER ZoneName
        The exact name of the zone. Mutually exclusive with -ZoneId.

    .PARAMETER Format
        Disk image format: QCOW2, OVA, or VHD

    .PARAMETER DiskOfferingId
        A custom-size disk offering to associate with the volume

    .PARAMETER Checksum
        Checksum of the image. MD5 by default; prefix others, e.g. '{SHA-256}<hex>'.

    .PARAMETER ImageStoreUuid
        The image store to upload to

    .PARAMETER Account
        Account that will own the volume. Must be used with -DomainId.

    .PARAMETER DomainId
        Domain of the owning account

    .PARAMETER ProjectId
        Project that will own the volume

    .EXAMPLE
        $upload = Get-CSVolumeUploadParams -Name 'imported-data' -ZoneName 'us-east-1' -Format QCOW2
        Gets the upload URL and signature for a QCOW2 data disk.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [string]$ZoneId,

        [string]$ZoneName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('QCOW2', 'OVA', 'VHD')]
        [string]$Format,

        [string]$DiskOfferingId,

        [string]$Checksum,

        [string]$ImageStoreUuid,

        [string]$Account,

        [string]$DomainId,

        [string]$ProjectId
    )

    if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
        throw 'DomainId is required when Account is specified.'
    }
    $resolvedZoneId = Resolve-CSObjectId -Id $ZoneId -Name $ZoneName -Required `
        -TypeName 'zone' -IdParameter 'ZoneId' -NameParameter 'ZoneName' `
        -Lookup { param($lookupName) Get-CSZone -Name $lookupName }

    $apiParams = @{ name = $Name; zoneid = $resolvedZoneId; format = $Format }
    Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
        DiskOfferingId = 'diskofferingid'; Checksum = 'checksum'; ImageStoreUuid = 'imagestoreuuid'
        Account = 'account'; DomainId = 'domainid'; ProjectId = 'projectid'
    })
    ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'getUploadParamsForVolume' -Parameters $apiParams) -Command 'getUploadParamsForVolume'
}

function Import-CSVolumeFromUrl {
    <#
    .SYNOPSIS
        Creates a data volume from a disk image hosted on a web server.

    .DESCRIPTION
        Has CloudStack download a disk image from an http(s) URL into a new data
        volume (uploadVolume). The volume is created detached; attach it with
        Mount-CSVolume once its state is Uploaded/Ready. This is an asynchronous
        job; use -Wait to get the new volume back.

    .PARAMETER Name
        The name of the new volume

    .PARAMETER Url
        The http:// or https:// URL of the disk image

    .PARAMETER ZoneId
        The zone to create the volume in. Mutually exclusive with -ZoneName.

    .PARAMETER ZoneName
        The exact name of the zone. Mutually exclusive with -ZoneId.

    .PARAMETER Format
        Disk image format: QCOW2, OVA, or VHD

    .PARAMETER DiskOfferingId
        A custom-size disk offering to associate with the volume

    .PARAMETER Checksum
        Checksum of the image. MD5 by default; prefix others, e.g. '{SHA-256}<hex>'.

    .PARAMETER ImageStoreUuid
        The image store to download to

    .PARAMETER Account
        Account that will own the volume. Must be used with -DomainId.

    .PARAMETER DomainId
        Domain of the owning account

    .PARAMETER ProjectId
        Project that will own the volume

    .PARAMETER Wait
        Wait for the async job to finish and return the new volume

    .EXAMPLE
        Import-CSVolumeFromUrl -Name 'dataset' -Url 'https://images.example.com/dataset.qcow2' -Format QCOW2 -ZoneName 'us-east-1' -Wait
        Creates a volume from a QCOW2 image on a web server.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter(Mandatory = $true)]
        [string]$Url,

        [string]$ZoneId,

        [string]$ZoneName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('QCOW2', 'OVA', 'VHD')]
        [string]$Format,

        [string]$DiskOfferingId,

        [string]$Checksum,

        [string]$ImageStoreUuid,

        [string]$Account,

        [string]$DomainId,

        [string]$ProjectId,

        [switch]$Wait
    )

    if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
        throw 'DomainId is required when Account is specified.'
    }
    $resolvedZoneId = Resolve-CSObjectId -Id $ZoneId -Name $ZoneName -Required `
        -TypeName 'zone' -IdParameter 'ZoneId' -NameParameter 'ZoneName' `
        -Lookup { param($lookupName) Get-CSZone -Name $lookupName }

    $apiParams = @{ name = $Name; url = $Url; zoneid = $resolvedZoneId; format = $Format }
    Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
        DiskOfferingId = 'diskofferingid'; Checksum = 'checksum'; ImageStoreUuid = 'imagestoreuuid'
        Account = 'account'; DomainId = 'domainid'; ProjectId = 'projectid'
    })
    if ($PSCmdlet.ShouldProcess("volume $Name", "Upload from $Url")) {
        Invoke-CSAsyncApiRequest -Command 'uploadVolume' -Parameters $apiParams -Wait:$Wait
    }
}

function Get-CSVolumeForImport {
    <#
    .SYNOPSIS
        Lists unmanaged volumes on a storage pool.

    .DESCRIPTION
        Lists disks on a primary storage pool that CloudStack does not manage yet
        (listVolumesForImport). Pipe the results to Import-CSVolume to bring them
        under management.

    .PARAMETER StorageId
        The storage pool to scan (required; binds from a piped storage pool's id)

    .PARAMETER Path
        Only return the volume at this path

    .PARAMETER Keyword
        Filter by keyword

    .PARAMETER Page
        Page number of results to return

    .PARAMETER PageSize
        Number of results per page

    .EXAMPLE
        Get-CSVolumeForImport -StorageId pool-uuid
        Lists the unmanaged disks on a storage pool.

    .EXAMPLE
        Get-CSVolumeForImport -StorageId pool-uuid -Keyword 'legacy' | Import-CSVolume -DiskOfferingId offering-uuid -Wait
        Imports every unmanaged disk whose name contains 'legacy'.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [string]$StorageId,

        [string]$Path,

        [string]$Keyword,

        [int]$Page,

        [int]$PageSize
    )

    process {
        $apiParams = @{ storageid = $StorageId }
        Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
            Path = 'path'; Keyword = 'keyword'; Page = 'page'; PageSize = 'pagesize'
        })
        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'listVolumesForImport' -Parameters $apiParams) -Command 'listVolumesForImport'
    }
}

function Import-CSVolume {
    <#
    .SYNOPSIS
        Brings an unmanaged volume on a storage pool under CloudStack management.

    .DESCRIPTION
        Imports an existing disk from a primary storage pool (importVolume). The
        inverse of Unregister-CSVolume. Accepts objects from Get-CSVolumeForImport
        on the pipeline; their path and storageid bind automatically. This is an
        asynchronous job; use -Wait to get the imported volume back.

    .PARAMETER Path
        The path of the disk on the storage pool (binds from a piped object's path)

    .PARAMETER StorageId
        The storage pool holding the disk (binds from a piped object's storageid)

    .PARAMETER Name
        A name for the volume. Defaults to the path.

    .PARAMETER DiskOfferingId
        The disk offering to link the volume to

    .PARAMETER Account
        Account that will own the volume. Must be used with -DomainId.

    .PARAMETER DomainId
        Domain of the owning account

    .PARAMETER ProjectId
        Project that will own the volume

    .PARAMETER Wait
        Wait for the async job to finish and return the imported volume

    .EXAMPLE
        Import-CSVolume -StorageId pool-uuid -Path 'legacy-data.qcow2' -Name 'legacy-data' -Wait
        Imports one disk from a storage pool.

    .EXAMPLE
        Get-CSVolumeForImport -StorageId pool-uuid | Import-CSVolume -Account 'engineering' -DomainId domain-uuid
        Imports every unmanaged disk on a pool into an account.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Path,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$StorageId,

        [string]$Name,

        [string]$DiskOfferingId,

        [string]$Account,

        [string]$DomainId,

        [string]$ProjectId,

        [switch]$Wait
    )

    process {
        if ($PSBoundParameters.ContainsKey('Account') -and -not $PSBoundParameters.ContainsKey('DomainId')) {
            throw 'DomainId is required when Account is specified.'
        }
        $apiParams = @{ path = $Path; storageid = $StorageId }
        Add-CSOptionalParameter -ApiParameters $apiParams -BoundParameters $PSBoundParameters -Map ([ordered]@{
            Name = 'name'; DiskOfferingId = 'diskofferingid'; Account = 'account'; DomainId = 'domainid'; ProjectId = 'projectid'
        })
        if ($PSCmdlet.ShouldProcess("$Path on storage pool $StorageId", 'Import volume')) {
            Invoke-CSAsyncApiRequest -Command 'importVolume' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Unregister-CSVolume {
    <#
    .SYNOPSIS
        Removes a volume from CloudStack management without deleting the disk.

    .DESCRIPTION
        Unmanages a volume (unmanageVolume): CloudStack forgets it but the disk
        stays on the storage pool, where Get-CSVolumeForImport can find it again.
        This is an asynchronous job; use -Wait to block until it finishes. Accepts
        volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the volume to unmanage (binds from a piped volume's id)

    .PARAMETER Wait
        Wait for the async job to finish and return its result

    .EXAMPLE
        Unregister-CSVolume -Id vol-uuid
        Unmanages a volume after prompting for confirmation.

    .EXAMPLE
        Get-CSVolume -Name 'legacy-data' | Unregister-CSVolume -Confirm:$false -Wait
        Unmanages a volume found by name without prompting.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [switch]$Wait
    )

    process {
        if ($PSCmdlet.ShouldProcess("volume $Id", 'Unmanage')) {
            Invoke-CSAsyncApiRequest -Command 'unmanageVolume' -Parameters @{ id = $Id } -Wait:$Wait
        }
    }
}

function Restore-CSVolumeFromBackup {
    <#
    .SYNOPSIS
        Restores a single volume from a VM backup and attaches it to a VM.

    .DESCRIPTION
        Restores one backed-up volume and attaches it to a VM
        (restoreVolumeFromBackupAndAttachToVM). The VM can be given by ID, name,
        or object. This is an asynchronous job; use -Wait to block until it
        finishes. Accepts volume objects from Get-CSVolume on the pipeline.

    .PARAMETER Id
        The ID of the backed-up volume to restore (binds from a piped volume's id)

    .PARAMETER BackupId
        The ID of the VM backup to restore from

    .PARAMETER VirtualMachineId
        The VM to attach the restored volume to

    .PARAMETER VM
        A VM name or VM object to attach the restored volume to. Alternative to -VirtualMachineId.

    .PARAMETER Wait
        Wait for the async job to finish and return its result

    .EXAMPLE
        Restore-CSVolumeFromBackup -Id vol-uuid -BackupId backup-uuid -VM 'web-01' -Wait
        Restores a volume from a backup and attaches it to web-01.

    .EXAMPLE
        Get-CSVM -Name 'web-01' | Get-CSVolume -Type DATADISK | Restore-CSVolumeFromBackup -BackupId backup-uuid -VM 'web-01-restore'
        Restores each data disk of web-01 from a backup onto a recovery VM.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('VolumeId')]
        [string]$Id,

        [Parameter(Mandatory = $true)]
        [string]$BackupId,

        [string]$VirtualMachineId,

        [object]$VM,

        [switch]$Wait
    )

    begin {
        # Resolve once, rather than looking the VM up again for every piped volume.
        if ($PSBoundParameters.ContainsKey('VM') -and $PSBoundParameters.ContainsKey('VirtualMachineId')) {
            throw 'Specify either -VM or -VirtualMachineId, not both.'
        }
        $resolvedVmId = Resolve-CSVMTagId -VirtualMachineId $VirtualMachineId -VM $VM
    }

    process {
        $apiParams = @{ volumeid = $Id; backupid = $BackupId; virtualmachineid = $resolvedVmId }
        if ($PSCmdlet.ShouldProcess("volume $Id from backup $BackupId", "Restore and attach to VM $resolvedVmId")) {
            Invoke-CSAsyncApiRequest -Command 'restoreVolumeFromBackupAndAttachToVM' -Parameters $apiParams -Wait:$Wait
        }
    }
}

function Get-CSElastistorVolume {
    <#
    .SYNOPSIS
        Lists the volumes on a CloudByte ElastiStor account.

    .DESCRIPTION
        Wraps listElastistorVolume, which is only available when the ElastiStor
        storage plugin is installed.

    .PARAMETER Id
        The ElastiStor account ID (binds from a piped object's id)

    .EXAMPLE
        Get-CSElastistorVolume -Id elastistor-account-id
        Lists the volumes on an ElastiStor account.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$Id
    )

    process {
        ConvertFrom-CSResponse -Response (Invoke-CSApiRequest -Command 'listElastistorVolume' -Parameters @{ id = $Id }) -Command 'listElastistorVolume'
    }
}