[TUTORIAL] API automation, Power ON/OFF vm and else.

Heres my Powershell 5.1 version with improved error handling. Might be useful to someone out there..
If you want to use it, make sure to set default values in the param() block at the top of the script unless you want to specify arguments manually every time.

Examples of how to call the script, supplying arguments manually:

.\VMControl.ps1 123 on
.\VMControl.ps1 123 off root@pam password nodename 192.168.1.10:8006
.\VMControl.ps1 -VM_ID "123" -OnOrOff "on" -Username "root@pam" -Password "password" -NodeName "nodename" -ProxmoxHost "192.168.1.10:8006"

Or just right-click and "Run with PowerShell" (given you have hardcoded default params that work)

Written in Powershell 5.1 using Proxmox 8.1

Code:
# Description: This script is used to start or stop a VM on a Proxmox server.

param(
    [string]$VM_ID="101",
    [ValidateSet("on", "off")] [string]$OnOrOff = "on",
    [string]$Username = "<your_user>",
    [string]$Password = "<your_password>",
    [string]$NodeName = "<your_node_name>",
    [string]$ProxmoxHost = "<your_ip_here>:8006"
)
$taskTimeout = 30 # How long to wait for the task to complete, before timing out.

function ExitAndWaitForKeypress {
    Write-Host "Press any key to continue..."
    $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
    exit 1
}


$curlCommand = "curl.exe -sS -k"; # -sS is to suppress the progress bar and show errors, -k is to ignore SSL certificate errors
$proxmoxApiBaseUrl = "https://$ProxmoxHost/api2/json"

# Authenticate and get the tickets
$getTicketJson = ConvertFrom-Json (Invoke-Expression "$curlCommand -d 'username=$Username' --data-urlencode 'password=$Password' '$proxmoxApiBaseUrl/access/ticket'")
$ticket = $getTicketJson.data.ticket
$preventionToken = $getTicketJson.data.CSRFPreventionToken
if (-not $ticket -or -not $preventionToken) {
    Write-Error "Failed to retrieve authentication ticket. Your login credentials may be incorrect."
    ExitAndWaitforKeypress
}

$curlCommandWithTicket = "curl.exe -sS -k -H `"Cookie: PVEAuthCookie=$ticket`" -H `"CSRFPreventionToken: $preventionToken`""

# Create the task to start or stop the VM
if ($OnOrOff -eq "on") {
    $curlPostTask = "$curlCommandWithTicket -X POST -i `"$proxmoxApiBaseUrl/nodes/$NodeName/qemu/$VM_ID/status/start`"" # -i to include the headers in output
} elseif ($OnOrOff -eq "off") {
    $curlPostTask = "$curlCommandWithTicket -X POST -i `"$proxmoxApiBaseUrl/nodes/$NodeName/qemu/$VM_ID/status/shutdown`""
} else {
    Write-Error "Invalid value for OnOrOff: $OnOrOff. OnOrOff must be 'on' or 'off'."
}

$postResponse = Invoke-Expression $curlPostTask
$responseParts = $postResponse.Split("`r`n");
$postTaskResponseStatus = $responseParts[0] # First line of the response contains the HTTP version followed by the status code and the reason phrase
$postTaskResponseJson = ConvertFrom-Json $responseParts[-1] # json body is at the end of the response

$upid = $postTaskResponseJson.data

if (-not $upid) {
    Write-Error "Failed to create the task. Proxmox returned response: '$postTaskResponseStatus'."
    ExitAndWaitForKeypress
}

# Wait for the task to complete, print the result.
$startTime = Get-Date
do {
    $curlGetTaskStatus = "$curlCommandWithTicket -X GET `"$proxmoxApiBaseUrl/nodes/$NodeName/tasks/$upid/status`""
    $taskStatusJson = ConvertFrom-Json (Invoke-Expression $curlGetTaskStatus)

    if ($taskStatusJson.data.status -eq "stopped") {
        Write-Host "TASK COMPLETED. | command='$($taskStatusJson.data.type)' | status='$($taskStatusJson.data.status)' | reason='$($taskStatusJson.data.exitstatus)' `r`n" -NoNewline
        break
    }

    Write-Host "TASK RUNNING... | command='$($taskStatusJson.data.type)' | status='$($taskStatusJson.data.status)'"

    $currentTime = Get-Date
    $elapsedTime = ($currentTime - $startTime).TotalSeconds

    if ($elapsedTime -ge $taskTimeout) {
        Write-Error "Timeout: Task is still running after $taskTimeout seconds."
        break
    }

    Start-Sleep -Milliseconds 250
} while ($true)

ExitAndWaitForKeypress
 
Last edited:
Hello everyone !

Sorry to post on an old thread, but it is still interesting.

I succesfully make the firsts scripts given working (thanks to the author !!).

It is perfectly what I wanted to do (starting a VM on demand without using proxmox hypervisor web GUI console) with a non privileged user (only rights to start a specific VM).

But I generate an API token that I would like to use instead of a password (that is in clear in the script, I don't like that to much).

If anyone knows how to modify the code given to use a token instead of a password, let me know ! Thanks by advance.

Nicolas
 
But I generate an API token that I would like to use instead of a password (that is in clear in the script, I don't like that to much).

If anyone knows how to modify the code given to use a token instead of a password, let me know ! Thanks by advance.
Firstly, I fail to understand how you are going to make it more secure. Where exactly are you going to provide the token used?
Secondly, in the above examples, you have the ability to provide username/password as inline arguments while running the scripts. So just remove hardcoded username/password from inside the script, and you are good to go.
 
Last edited:
Hello everyone !

Sorry to post on an old thread, but it is still interesting.

I succesfully make the firsts scripts given working (thanks to the author !!).

It is perfectly what I wanted to do (starting a VM on demand without using proxmox hypervisor web GUI console) with a non privileged user (only rights to start a specific VM).

But I generate an API token that I would like to use instead of a password (that is in clear in the script, I don't like that to much).

If anyone knows how to modify the code given to use a token instead of a password, let me know ! Thanks by advance.

Nicolas

Hi,

With a token it's much more simple. But keep in mind that your api token secret will be in clear text in your script.
And if you want to automate or run unattended, at some point there will be a credential in clear somewhere.
Like gfn mentioned, if you want to keep it secure, run it manually and input the credential yourself.

With the api token you do not need to ask for a ticket and then the whole cookie extraction from the original script is not needed.
This is what it look like with the api token

Code:
#!/bin/bash
#Host info
pve="PVE_API_HOST"
node="COMPUTE_HOST"
port=":8006"
vmid="101"

#API info
apiu="PVE_API_USER"
apitk="PVE_API_TOKEN_ID"
apitksec="PVE_API_TOKEN_SECRET"
url_base="https://$pve$port/api2/json"
# this is where you put what ever you want do
# https://pve.proxmox.com/pve-docs/api-viewer/
url_end="nodes/$node/qemu/$vmid/status/start"
urlqr="$url_base/$url_end"

#proxmox api query
curl -k -X POST -H "Authorization: PVEAPIToken=${apiu}!${apitk}=${apitksec}" "$urlqr"
 
Hi,

With a token it's much more simple. But keep in mind that your api token secret will be in clear text in your script.
And if you want to automate or run unattended, at some point there will be a credential in clear somewhere.
Like gfn mentioned, if you want to keep it secure, run it manually and input the credential yourself.

With the api token you do not need to ask for a ticket and then the whole cookie extraction from the original script is not needed.
This is what it look like with the api token

Code:
#!/bin/bash
#Host info
...
urlqr="$url_base/$url_end"

#proxmox api query
curl -k -X POST -H "Authorization: PVEAPIToken=${apiu}!${apitk}=${apitksec}" "$urlqr"
Well, you are both right, finally an API token is not more secure. But I will try your bash script.

The PowerShell script (I did not modified what you posted at the begining) is now in error. Do you think that it is because of an update ?

Error messages :

Code:
 powershell -ExecutionPolicy Bypass .\start_win11.ps1
Invoke-WebRequest : Impossible de trouver un paramètre positionnel acceptant l'argument « --data ».
Au caractère C:\Users\Nicolas\Desktop\start_win11.ps1:25 : 11
+ $ticket = curl --insecure --data "username=$apiu&password=$apip" $url ...
+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument : (:) [Invoke-WebRequest], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

Impossible d’appeler une méthode dans une expression Null.
Au caractère C:\Users\Nicolas\Desktop\start_win11.ps1:28 : 1
+ $Array = $ticket.Split('"')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation : (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

Invoke-RestMethod : Impossible de trouver un paramètre correspondant au nom « SkipCertificateCheck ».
Au caractère C:\Users\Nicolas\Desktop\start_win11.ps1:43 : 19
+ Invoke-RestMethod -SkipCertificateCheck -Uri $urlqr -Method Post -Hea ...
+                   ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument : (:) [Invoke-RestMethod], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
 

About

The Proxmox community has been around for many years and offers help and support for Proxmox VE, Proxmox Backup Server, and Proxmox Mail Gateway.
We think our community is one of the best thanks to people like you!

Get your subscription!

The Proxmox team works very hard to make sure you are running the best software and getting stable updates and security enhancements, as well as quick enterprise support. Tens of thousands of happy customers have a Proxmox subscription. Get yours easily in our online shop.

Buy now!