[TUTORIAL] Powershell scripts to take snapshots off all VMs with a Tag and to delete the snapshots later

tscret

Member
Mar 27, 2023
36
14
13
Switzerland
Hi there

I'm usig Proxmox VE now for about 4 years on my Lab and had a step learning curve. Now after all VMware Broadcom story - I'm in to do some PoC to convince my Boss to migrate from ESXi to Proxmox. For this I had to fullfill few demonstrations to show that Proxmox is realy capable to cover our needs. As we patch our Customers VMs over Ivanti there was the Poit to make some easy tool to take and delete snapshots before and after patching. As I profited allready a lot of the proxmox i decided to share these three Powershellscripts. Maybe the could serve / help someone out there. Thanks to the well documented API of Proxmox that was quiet simple. (I know there are PS Modules out there ;-))

Script to take snapshots on VM with defined Tag:
code_language.shell:
<#
Proxmox API Snapshot Creator by TAG

Description: Das Script greift per API auf den Proxmox Cluster zu und erstellt bei jeder VM mit dem definierten $SearchTag einen Snapshot.
Die Ausführung des Snapshost wird mittels Task-API Kontrolle überwacht.
#>

# Proxmox API Details
$ProxmoxAPIURL = "https://<FQDN>:8006/api2/json"
$APITokenID = "<API-Token-Secret>"
$APITokenSecret = "<API-Token-Secret>"
$SearchTag = "<YourTag>"

# Log-Datei definieren
$LogFilePath = "C:\temp\ProxmoxSnapshotLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"

# Funktion zum Schreiben in die Log-Datei
function Write-Log {
    param (
        [string]$Message
    )
    $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $LogMessage = "$Timestamp - $Message"
    Write-Output $LogMessage
    Add-Content -Path $LogFilePath -Value $LogMessage
}


# Funktion zur Abfrage aller Nodes im Cluster
function Get-AllNodes {
    Invoke-RestMethod -Uri "$ProxmoxAPIURL/nodes" -Method Get -Headers $Headers
}

# Funktion zur Abfrage von VMs mit einem bestimmten Tag auf einer Node
function Get-VMsByTag {
    param (
        [string]$NodeName,
        [string]$Tag
    )

    $VMs = Invoke-RestMethod -Uri "$ProxmoxAPIURL/nodes/$NodeName/qemu" -Method Get -Headers $Headers
    return $VMs.data | Where-Object { $_.tags -Like $Tag }
}

# Funktion zur Erstellung eines Snapshots
function Create-Snapshot {
    param (
        [string]$NodeName,
        [string]$VMID,
        [string]$SnapshotName
    )
    $Uri = "$ProxmoxAPIURL/nodes/$NodeName/qemu/$VMID/snapshot"
    $Body = @{
        snapname = $SnapshotName
        vmstate = "0"  # Setze auf 1, um den RAM-Zustand zu speichern
        description = "Auto Snapshot für Tag $SearchTag"
    }
    $Response = Invoke-RestMethod -Uri $Uri -Method Post -Body $Body -Headers $Headers -ContentType "application/x-www-form-urlencoded"
    return $Response.data
}

# Funktion zur Überprüfung des Task-Status
function Wait-ForTaskCompletion {
    param (
        [string]$NodeName,
        [string]$TaskID
    )
    Write-Log "Überprüfe Snapshot-Task $VMID auf Node $NodeName..."
    while ($true) {
        $TaskStatus = Invoke-RestMethod -Uri "$ProxmoxAPIURL/nodes/$NodeName/tasks/$TaskID/status" -Method Get -Headers $Headers
        if ($TaskStatus.data.status -eq "stopped") {
            if ($TaskStatus.data.exitstatus -eq "OK") {
                Write-Log "Snapshot-Task $VMID erfolgreich abgeschlossen."
                break
            } else {
                Write-Log "Snapshot-Task $VMID ist fehlgeschlagen: $($TaskStatus.data.exitstatus)"
                throw "Snapshot-Task fehlgeschlagen."
            }
        }
        Start-Sleep -Milliseconds 500
    }
}


# Hauptskript
try {
$Counter = 0

Write-Log "Konfiguriere API-Header..."
$Headers = @{
    Authorization = "PVEAPIToken=$APITokenID=$APITokenSecret"
}

# Alle Nodes im Cluster abrufen
Write-Log "Hole alle Nodes im Cluster..."
$Nodes = Get-AllNodes

# Über alle Nodes iterieren
foreach ($Node in $Nodes.data) {
    $NodeName = $Node.node
    Write-Log "Verarbeite Node: $NodeName"

    # VMs mit Tag 'IVANTI' auf dieser Node abrufen
    Write-Log "Suche nach VMs mit dem Tag $SearchTag auf Node $NodeName..."
    $TaggedVMs = Get-VMsByTag -NodeName $NodeName -Tag $SearchTag

    if ($TaggedVMs.Count -eq 0) {
        Write-Log "Keine VMs mit dem Tag $SearchTag auf Node $NodeName gefunden."
    } else {
        # Snapshot erstellen
        foreach ($VM in $TaggedVMs) {
            $VMID = $VM.vmid
            $SnapshotName = "$($SearchTag)_Auto_Snapshot_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
            Write-Log "Erstelle Snapshot '$SnapshotName' für VMID $VMID auf Node $NodeName..."
            $Counter = $Counter+1
            $TaskData = Create-Snapshot -NodeName $NodeName -VMID $VMID -SnapshotName $SnapshotName
            Wait-ForTaskCompletion -NodeName $NodeName -TaskID $TaskData
        }
    }
}

Write-Log "Snapshots für alle Nodes abgeschlossen. Anzahl Snapshots $Counter erstellt."
}
catch {
    Write-Log "Ein Fehler ist aufgetreten: $_"
    throw
}

Here the Script to Delete the automated taken snapshots
code_language.shell:
<#
Proxmox API Snapshot Creator by TAG
Datimo IT Solutions Winterthur
Autor: Reto Tschopp
Created: 31.12.2024

Description: Das Script greift per API auf den Proxmox Cluster zu und löscht bei jeder VM Snapshot die mit dem $SearchTag Wert beginnen.
Die Ausführung des Snapshost wird mittels Task-API Kontrolle überwacht.
#>


# Proxmox API Details
$ProxmoxAPIURL = "https://<FQDN>:8006/api2/json"
$APITokenID = "<API-Token-Secret>"
$APITokenSecret = "<API-Token-Secret>"
$SearchTag = "<YourTag>"

# Log-Datei definieren
$LogFilePath = "C:\temp\ProxmoxSnapshotLog_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"

# Funktion zum Schreiben in die Log-Datei
function Write-Log {
    param (
        [string]$Message
    )
    $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $LogMessage = "$Timestamp - $Message"
    Write-Output $LogMessage
    Add-Content -Path $LogFilePath -Value $LogMessage
}


# Header mit Authentifizierung
Write-Log "Konfiguriere API-Header..."
$headers = @{
    Authorization = "PVEAPIToken=$APITokenID=$APITokenSecret"
}

# Funktion zum Abrufen der VMIDs
function Get-VMList {
    $vmListUrl = "$ProxmoxApiUrl/nodes/$NodeName/qemu"
    $response = Invoke-RestMethod -Uri $vmListUrl -Headers $headers -Method Get
    return $response.data.vmid
}

# Funktion zum Abrufen der Snapshots einer VM
function Get-Snapshots {
    param (
        [string]$vmid,
        [string]$snapnameStart
    )
    $snapshotUrl = "$ProxmoxApiUrl/nodes/$NodeName/qemu/$vmid/snapshot"
    $response = Invoke-RestMethod -Uri $snapshotUrl -Headers $headers -Method Get
    $response.data | Where-Object { $_.name -Match $snapnameStart }
  
}

# Funktion zum Löschen eines Snapshots
function Delete-Snapshot {
    param (
        [string]$vmid,
        [string]$snapshotName
    )
    $deleteSnapshotUrl = "$ProxmoxApiUrl/nodes/$NodeName/qemu/$vmid/snapshot/$snapshotName"
    $Response = Invoke-RestMethod -Uri $deleteSnapshotUrl -Headers $headers -Method Delete
    return $Response.data
}

# Funktion zum holen aller Nodes im Cluster
function Get-AllNodes {
    Invoke-RestMethod -Uri "$ProxmoxAPIURL/nodes" -Method Get -Headers $headers
}

# Funktion zur Überprüfung des Task-Status
function Wait-ForTaskCompletion {
    param (
        [string]$NodeName,
        [string]$TaskID
    )
    Write-Log "Überprüfe Snapshot-Task $VMID auf Node $NodeName..."
    while ($true) {
        $TaskStatus = Invoke-RestMethod -Uri "$ProxmoxAPIURL/nodes/$NodeName/tasks/$TaskID/status" -Method Get -Headers $headers
        if ($TaskStatus.data.status -eq "stopped") {
            if ($TaskStatus.data.exitstatus -eq "OK") {
                Write-Log "Task für VM $VMID erfolgreich abgeschlossen."
                break
            } else {
                Write-Log "Task für VM $VMID ist fehlgeschlagen: $($TaskStatus.data.exitstatus)"
                throw "Snapshot-Task fehlgeschlagen."
            }
        }
        Start-Sleep -Milliseconds 500
    }
}

#Hauptscript
try {
$Counter = 0

# Abrufen aller Nodes
$Nodes = Get-AllNodes
foreach ($Node in $Nodes.data) {
    $NodeName = $Node.node
    Write-Log "Verarbeite Node: $NodeName"

# Abrufen der VMIDs
$vmList = Get-VMList

# Iterieren über jede VM
foreach ($vmid in $vmList) {
       # Abrufen der Snapshots für die VM
    $snapshots = Get-Snapshots -vmid $vmid -snapnameStart "$($SearchTag)*"
    
    # Wenn Snapshots vorhanden sind, lösche sie
    foreach ($snapshot in $snapshots) {
            Write-Log "Lösche Snapshot $($snapshot.name) für VMID $vmid..."
            $Counter = $Counter+1
            $TaskData = Delete-Snapshot -vmid $vmid -snapshotName $snapshot.name
            Write-Log "Snapshot $snapshotName für VMID $vmid gelöscht."
            Wait-ForTaskCompletion -NodeName $NodeName -TaskID $TaskData
            }
}
}
Write-Log "Snapshot-Löschung abgeschlossen. Anzahl gelöschte Snapshots $Counter"
} catch {
    Write-Log "Ein Fehler ist aufgetreten: $_"
    throw
}

Kind regards tscret