Public/Copy-PVEVM.ps1

function Copy-PVEVM {
  <#
  .SYNOPSIS
  Clones a VM or VM template into a new VM.
  .DESCRIPTION
  Clones the source with POST /nodes/{node}/qemu/{vmid}/clone, waits for the clone task, and writes the
  new VM with its live status. The new VM is created on the source's node.

  From the API documentation: a clone of a normal VM is always a full clone (a full copy of every
  disk); for a template, PVE tries to make a linked clone by default, whose disks share the template's
  base image. -Full asks for a full clone of a template. -Storage and -Format apply to full clones.

  The source can be named by ID or name (templates included), or piped in. Exactly one source is
  cloned per call.
  .PARAMETER Name
  Name of the source VM or template.
  .PARAMETER VMId
  ID of the source VM or template. Binds from a piped PVEPS.VM object.
  .PARAMETER NewName
  Name for the new VM.
  .PARAMETER NewVMId
  ID for the new VM. Defaults to the next free ID.
  .PARAMETER Full
  Make a full clone of a template instead of a linked clone.
  .PARAMETER Storage
  Target storage for a full clone.
  .PARAMETER Format
  Disk format for a full clone: raw, qcow2 or vmdk. If the target storage does not support it, PVE uses
  the storage's default format.
  .PARAMETER Pool
  Resource pool to add the new VM to.
  .PARAMETER Description
  Description for the new VM.
  .PARAMETER Snapshot
  Clone the source as it was at this snapshot.
  .PARAMETER RunAsync
  Return the clone task at once instead of waiting.
  .PARAMETER Server
  Connection(s) to use. Defaults to the default connection; binds from a piped object.
  .EXAMPLE
  Copy-PVEVM tmpl-debian13 -NewName web03
  .EXAMPLE
  Copy-PVEVM -VMId 9000 -NewName web04 -NewVMId 104 -Full -Storage local-lvm -Pool web | Start-PVEVM
  #>

  [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'ShouldProcess is called on this cmdlet through $PSCmdlet by Invoke-PVEGuestClone.')]
  [CmdletBinding(DefaultParameterSetName = 'Id', SupportsShouldProcess, ConfirmImpact = 'Medium')]
  [OutputType('PVEPS.VM', 'PVEPS.Task')]
  param(
    [Parameter(Mandatory, ParameterSetName = 'Name', Position = 0)]
    [ValidateNotNullOrEmpty()]
    [string]$Name,

    [Parameter(Mandatory, ParameterSetName = 'Id', ValueFromPipelineByPropertyName)]
    [ValidateRange(100, 999999999)]
    [int]$VMId,

    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string]$NewName,

    [ValidateRange(100, 999999999)]
    [int]$NewVMId,

    [switch]$Full,

    [ValidateNotNullOrEmpty()]
    [string]$Storage,

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

    [ValidateNotNullOrEmpty()]
    [string]$Pool,

    [string]$Description,

    [ValidateNotNullOrEmpty()]
    [string]$Snapshot,

    [switch]$RunAsync,

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

  begin {
    $targets = [System.Collections.Generic.List[psobject]]::new()
  }

  process {
    # -VMId and -Name are scalars here, so the one not in use holds 0 or '' rather than $null.
    if ($PSCmdlet.ParameterSetName -eq 'Id') {
      Add-PVEGuestTarget -Cmdlet $PSCmdlet -Target $targets -Server $Server -VMId $VMId
    }
    else {
      Add-PVEGuestTarget -Cmdlet $PSCmdlet -Target $targets -Server $Server -Name $Name
    }
  }

  end {
    $parameters = @{}
    if ($Full) {
      $parameters.full = 1
    }
    if ($Storage) {
      $parameters.storage = $Storage
    }
    if ($Format) {
      $parameters.format = $Format
    }
    if ($Pool) {
      $parameters.pool = $Pool
    }
    if ($PSBoundParameters.ContainsKey('Description')) {
      $parameters.description = $Description
    }
    if ($Snapshot) {
      $parameters.snapname = $Snapshot
    }
    Invoke-PVEGuestClone -Cmdlet $PSCmdlet -Type qemu -Target $targets -NewName $NewName -NewVMId $NewVMId -Parameters $parameters -RunAsync:$RunAsync
  }
}