Full-clones a VM or template to a new name, taking the next free VM ID from the cluster when you do not pin one, and refusing to run when the source is ambiguous or the target name already exists. Because a full clone copies disks, it follows the Proxmox task until it finishes, reporting progress for up to 30 minutes, and can start the clone afterwards. The new name is handed to the next runbook step.
powershell
# Proxmox Clone VM
# Single use case: create a full clone of a template/VM (replaces the old
# untested 01-Deploy-VM-Proxmox.ps1)
#-----------------------------------------------------------------
function Write-Log {
param($Message)
Write-Host "<WRITE-LOG = `"*$Message*`">"
}
# --- Parameters (replace via ServerEngine API parameters if needed) ---
$ProxmoxHost = "pve.example.com"
$SourceVMName = "template-ubuntu" # template or VM to clone from
$NewVMName = "new-vm"
$NewVMID = 0 # 0 = next free ID from the cluster
$Storage = "" # empty = same storage as source
$StartAfter = $false
$Username = "SE-CredentialsStore.Username.(pve.example.com)"
$Password = "SE-CredentialsStore.Password.(pve.example.com)"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
[Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
$api = "https://${ProxmoxHost}:8006/api2/json"
$auth = Invoke-RestMethod -Method Post -Uri "$api/access/ticket" -Body @{ username = $Username; password = $Password } -ErrorAction SilentlyContinue
if ($null -eq $auth) { Write-Log "Authentication failed against $ProxmoxHost!"; return }
$headers = @{ Cookie = "PVEAuthCookie=$($auth.data.ticket)"; CSRFPreventionToken = $auth.data.CSRFPreventionToken }
$all = (Invoke-RestMethod -Uri "$api/cluster/resources?type=vm" -Headers $headers).data
$src = $all | Where-Object { $_.name -eq $SourceVMName }
if ($null -eq $src) { Write-Log "Source VM/template not found: $SourceVMName"; return }
if (@($src).Count -gt 1) { Write-Log "Source name '$SourceVMName' is ambiguous!"; return }
if ($all | Where-Object { $_.name -eq $NewVMName }) {
Write-Log "A VM named '$NewVMName' already exists - aborting."
return
}
if ([int]$NewVMID -le 0) {
$NewVMID = (Invoke-RestMethod -Uri "$api/cluster/nextid" -Headers $headers).data
}
Write-Log "Cloning '$SourceVMName' (ID $($src.vmid)) -> '$NewVMName' (ID $NewVMID) on node $($src.node)..."
$body = @{ newid = [int]$NewVMID; name = $NewVMName; full = 1 }
if (-not [string]::IsNullOrWhiteSpace($Storage)) { $body.storage = $Storage }
$task = Invoke-RestMethod -Method Post -Uri "$api/nodes/$($src.node)/qemu/$($src.vmid)/clone" -Headers $headers -Body $body
# Full clones copy disks - allow up to 30 minutes
$upid = $task.data
$waited = 0
do {
Start-Sleep 10
$waited += 10
$status = (Invoke-RestMethod -Uri "$api/nodes/$($src.node)/tasks/$upid/status" -Headers $headers).data
if ($waited % 60 -eq 0) { Write-Log "Cloning... (${waited}s elapsed)" }
} while ($status.status -eq "running" -and $waited -lt 1800)
if ($status.exitstatus -ne "OK") {
Write-Log "WARNING: clone task ended with: $($status.exitstatus)"
return
}
Write-Log "Clone completed: '$NewVMName' (ID $NewVMID)."
if ($StartAfter) {
Write-Log "Starting '$NewVMName'..."
Invoke-RestMethod -Method Post -Uri "$api/nodes/$($src.node)/qemu/$NewVMID/status/start" -Headers $headers | Out-Null
Write-Log "Start requested."
}
# Pass the new VM name to the next runbook script
$store = $NewVMNameRun it across your fleet
This script runs as-is on a single host. Paste it into ServerEngine to schedule it, run it on a whole server group in parallel, and keep the credentials out of the file — see the scripts documentation and the credential store.
More in Proxmox VE
Proxmox VM Inventory with PowerShell and the API
2026-08-17Create a Proxmox Snapshot with PowerShell
2026-08-17Start a Proxmox VM with PowerShell
2026-08-17Shut Down a Proxmox VM with PowerShell
2026-08-17Delete Old Proxmox Snapshots with PowerShell
2026-08-17Roll Back a Proxmox VM to a Snapshot
2026-08-17Ready when you are.
Try ServerEngine free for 7 days.