Public/New-PVEVMHardDisk.ps1

function New-PVEVMHardDisk {
  <#
  .SYNOPSIS
  Adds a hard disk to QEMU virtual machines.
  .DESCRIPTION
  Adds a new disk of -SizeGB GiB on -Storage, or attaches an existing volume given as -Volume (for
  example one listed as unusedN after Remove-PVEVMHardDisk), with POST /nodes/{node}/qemu/{vmid}/config.
  Writes the new PVEPS.HardDisk object.

  The disk goes on the -Bus bus at the lowest free index unless -Index is given. On a running VM, PVE
  hot-plugs the disk if the VM's hotplug settings allow it; otherwise the disk is pending until the VM is
  next started, and a warning says so.
  .PARAMETER VMId
  VM ID(s). Binds from piped PVEPS.VM objects.
  .PARAMETER VMName
  VM name(s). Wildcards allowed.
  .PARAMETER SizeGB
  Size of the new disk in GiB. Needs -Storage.
  .PARAMETER Storage
  Storage for the new disk.
  .PARAMETER Volume
  An existing volume to attach instead of creating a disk, for example local-lvm:vm-100-disk-3.
  .PARAMETER Bus
  scsi (the default), virtio, sata or ide.
  .PARAMETER Index
  Index on the bus, for example 2 for scsi2. Defaults to the lowest free index.
  .PARAMETER Format
  Disk format on file-based storage: raw, qcow2 or vmdk.
  .PARAMETER Cache
  Cache mode: none, writethrough, writeback, unsafe or directsync.
  .PARAMETER SSD
  Present the disk to the guest as an SSD.
  .PARAMETER Discard
  Pass discard/TRIM requests from the guest to the storage.
  .PARAMETER IOThread
  Give the disk its own I/O thread.
  .PARAMETER NoBackup
  Leave the disk out of backups (backup=0).
  .PARAMETER Parameter
  Further drive options by their API names, for example @{ iops_rd = 500 }.
  .PARAMETER Server
  Connection(s) to use. Defaults to the default connection; binds from piped objects.
  .EXAMPLE
  New-PVEVMHardDisk -VMId 100 -SizeGB 50 -Storage local-lvm -SSD -Discard
  .EXAMPLE
  Get-PVEVM db01 | New-PVEVMHardDisk -SizeGB 200 -Storage bulk -Bus virtio -IOThread
  #>

  [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'ShouldProcess is called on this cmdlet through $PSCmdlet by Invoke-PVEGuestChange.')]
  [CmdletBinding(DefaultParameterSetName = 'Id', SupportsShouldProcess, ConfirmImpact = 'Medium')]
  [OutputType('PVEPS.HardDisk')]
  param(
    [Parameter(Mandatory, ParameterSetName = 'Id', ValueFromPipelineByPropertyName)]
    [ValidateRange(100, 999999999)]
    [int[]]$VMId,

    [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0)]
    [SupportsWildcards()]
    [ValidateNotNullOrEmpty()]
    [string[]]$VMName,

    [ValidateRange(1, [int]::MaxValue)]
    [int]$SizeGB,

    [ValidateNotNullOrEmpty()]
    [string]$Storage,

    [ValidateNotNullOrEmpty()]
    [string]$Volume,

    [ValidateSet('scsi', 'virtio', 'sata', 'ide')]
    [string]$Bus = 'scsi',

    [ValidateRange(0, 30)]
    [int]$Index,

    [ValidateSet('raw', 'qcow2', 'vmdk')]
    [string]$Format,

    [ValidateSet('none', 'writethrough', 'writeback', 'unsafe', 'directsync')]
    [string]$Cache,

    [switch]$SSD,

    [switch]$Discard,

    [switch]$IOThread,

    [switch]$NoBackup,

    [hashtable]$Parameter,

    [Parameter(ValueFromPipelineByPropertyName)]
    [Alias('Connection')]
    [object[]]$Server
  )

  begin {
    if ($Volume -and ($SizeGB -or $Storage)) {
      $PSCmdlet.ThrowTerminatingError((New-PVEArgumentError -Message 'Give either -Volume, or -SizeGB with -Storage, not both.' -Target $Volume))
    }
    if (-not $Volume -and -not ($SizeGB -and $Storage)) {
      $PSCmdlet.ThrowTerminatingError((New-PVEArgumentError -Message 'Give -SizeGB with -Storage for a new disk, or -Volume to attach an existing one.' -Target $null))
    }
    $slots = @{ scsi = 31; virtio = 16; sata = 6; ide = 4 }
    if ($PSBoundParameters.ContainsKey('Index') -and $Index -ge $slots[$Bus]) {
      $PSCmdlet.ThrowTerminatingError((New-PVEArgumentError -Message "The $Bus bus has indexes 0 to $($slots[$Bus] - 1)." -Target $Index))
    }
    $option = [ordered]@{ file = if ($Volume) { $Volume } else { '{0}:{1}' -f $Storage, $SizeGB } }
    if ($Format) { $option.format = $Format }
    if ($Cache) { $option.cache = $Cache }
    if ($SSD) { $option.ssd = 1 }
    if ($Discard) { $option.discard = 'on' }
    if ($IOThread) { $option.iothread = 1 }
    if ($NoBackup) { $option.backup = 0 }
    if ($Parameter) {
      foreach ($key in $Parameter.Keys) {
        $option[$key] = $Parameter[$key]
      }
    }
    $value = ConvertTo-PVEPropertyString -Option $option -DefaultKey file
    $what = if ($Volume) { "volume $Volume" } else { "$SizeGB GiB on $Storage" }
    $targets = [System.Collections.Generic.List[psobject]]::new()
  }

  process {
    Add-PVEGuestTarget -Cmdlet $PSCmdlet -Target $targets -Server $Server -VMId $VMId -Name $VMName
  }

  end {
    foreach ($guest in @(Resolve-PVEGuestTarget -Cmdlet $PSCmdlet -Type qemu -Target $targets)) {
      $slot = if ($PSBoundParameters.ContainsKey('Index')) { $Index } else { -1 }
      Add-PVEGuestDevice -Cmdlet $PSCmdlet -Guest $guest -Prefix $Bus -Index $slot -Count $slots[$Bus] -Value $value -Kind HardDisk -Description "Add hard disk {key} ($what)"
    }
  }
}