← script library

Shut Down a Proxmox VM with PowerShell

Proxmox VEAugust 17, 2026

Requests a guest shutdown with a timeout and waits for the VM to report stopped. If the guest ignores it — usually a missing guest agent — the script either forces a hard stop or refuses and says so, depending on one switch. It never forces silently.

powershell
# Proxmox Power OFF VM
# Single use case: gracefully shut down one VM (guest shutdown, no hard stop)
#-----------------------------------------------------------------

function Write-Log {
    param($Message)
    Write-Host "<WRITE-LOG = `"*$Message*`">"
}

# --- Parameters (replace via ServerEngine API parameters if needed) ---
$ProxmoxHost       = "pve.example.com"
$VMName            = "test-vm"
$TimeoutSeconds    = 120
$ForceAfterTimeout = $false   # $true = hard stop if the guest ignores shutdown

$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 }

$vm = (Invoke-RestMethod -Uri "$api/cluster/resources?type=vm" -Headers $headers).data | Where-Object { $_.name -eq $VMName }
if ($null -eq $vm) { Write-Log "VM not found: $VMName"; return }
if (@($vm).Count -gt 1) { Write-Log "VM name '$VMName' is ambiguous ($(@($vm).Count) matches)!"; return }

if ($vm.status -ne "running") {
    Write-Log "VM $VMName (ID $($vm.vmid)) is not running (status: $($vm.status)) - nothing to do."
    return
}

Write-Log "Sending guest shutdown to VM $VMName (ID $($vm.vmid), timeout ${TimeoutSeconds}s)..."
Invoke-RestMethod -Method Post -Uri "$api/nodes/$($vm.node)/qemu/$($vm.vmid)/status/shutdown" -Headers $headers -Body @{ timeout = [int]$TimeoutSeconds } | Out-Null

$waited = 0
while ($waited -lt ([int]$TimeoutSeconds + 10)) {
    Start-Sleep 5
    $waited += 5
    $state = (Invoke-RestMethod -Uri "$api/nodes/$($vm.node)/qemu/$($vm.vmid)/status/current" -Headers $headers).data.status
    if ($state -eq "stopped") { break }
}

if ($state -eq "stopped") {
    Write-Log "VM $VMName shut down gracefully after ${waited}s."
} elseif ($ForceAfterTimeout) {
    Write-Log "Guest did not shut down in time - forcing hard stop..."
    Invoke-RestMethod -Method Post -Uri "$api/nodes/$($vm.node)/qemu/$($vm.vmid)/status/stop" -Headers $headers | Out-Null
    Start-Sleep 5
    $state = (Invoke-RestMethod -Uri "$api/nodes/$($vm.node)/qemu/$($vm.vmid)/status/current" -Headers $headers).data.status
    Write-Log "VM $VMName state after force: $state"
} else {
    Write-Log "WARNING: VM $VMName still '$state' after ${TimeoutSeconds}s (guest agent missing or busy). Not forcing."
}

Run 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.

Ready when you are.

Try ServerEngine free for 7 days.