[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
 
First I would like to complain about the simply terrible way Proxmox documentation explains how this works properly, by being to lazy to include a simple example.
Second, @Veeh if you try to help, please be PRECISE.
You start with stating that one needs to create a token under Datacenter, which is simply wrong, correct ?!
One EITHER uses the user and password which requires an access token OR creates an API token and uses a direct request.

Now I would like to thank you for the answer because just before I tried to get to the bottom of it myself I saw you posted a working version.

@gannick
There is definitely a BIG difference in the security.
If you use the normal user, which by default is the admin, it can once stolen do EVERYTHING.
Also, when you need to disable it, you have nothing and need a new account.
If you add a token to a user, you use the token and that token has it's own privileges.
So :
1 - you can lock that down to what you need
2 - you can disable the token without disabling the user

Regarding the reading of the password, there is no real way to avoid it anyway. Even when using curl and url_encode it, it may be seen.

Security wise, the best way is probably to create a separate user and depending on if you thing an extra request to get a ticket adds security, you could use it straight away or create a token for it.
Also, the token can be set to expire to add security if needed.

Here is the token based script with a small improvement so it can be used as ./script_name stop and ./script_name start :

Bash:
#!/bin/bash
#Host info
pve="192.168.68.58"
node="pve1"
port=":8006"
vmid="100"
param1=$1

#API info
apiu="root@pam"
apitk="token_id"
apitksec="12345aaa-aaa7-4c95-8efd-95b916cd651a"
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/$param1"
urlqr="$url_base/$url_end"

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

cheers
 
Last edited:
Second, @Veeh if you try to help, please be PRECISE.
You start with stating that one needs to create a token under Datacenter, which is simply wrong, correct ?!
One EITHER uses the user and password which requires an access token OR creates an API token and uses a direct request.

Now I would like to thank you for the answer because just before I tried to get to the bottom of it myself I saw you posted a working version.
I don't know what you understood or assumed from my post. But yes to create an API Token in the GUI one has to go in the Datacenter menu first:
"Datacenter > Permissions > API Tokens"

There are 2 ways to do this. API token or user.

- The user requires a username/password, and then you get a ticket cookie which allows you to pass whatever action your user permissions allow you to do, through the API.
https://pve.proxmox.com/wiki/Proxmox_VE_API#Ticket_Cookie
This is what the first script does. Bash and Powershell

- The API token is tied to a user. And will never get better permission than the user is tied to. You can separate the token permission to lower them. It's easier to script because you can send the token in the header no need to parse a ticket to send another call.
But API tokens are valid for a long time contrary to tickets that expire after 2h.

I'm mostly quoting the official doc here.

Finally regarding "I saw you posted a working version"
All 3 versions are working fine thank you very much.
The PowerShell version requires powershell7 to work as stated in the comments.
 
I don't know what you understood or assumed from my post. But yes to create an API Token in the GUI one has to go in the Datacenter menu first:
"Datacenter > Permissions > API Tokens"

There are 2 ways to do this. API token or user.

- The user requires a username/password, and then you get a ticket cookie which allows you to pass whatever action your user permissions allow you to do, through the API.
https://pve.proxmox.com/wiki/Proxmox_VE_API#Ticket_Cookie
This is what the first script does. Bash and Powershell

- The API token is tied to a user. And will never get better permission than the user is tied to. You can separate the token permission to lower them. It's easier to script because you can send the token in the header no need to parse a ticket to send another call.
But API tokens are valid for a long time contrary to tickets that expire after 2h.

I'm mostly quoting the official doc here.

Finally regarding "I saw you posted a working version"
All 3 versions are working fine thank you very much.
The PowerShell version requires powershell7 to work as stated in the comments.
hi, thank you for replying. I understood from your post that one needs to setup an API token when using the username, which is not needed, because as you write now indeed there is no need for a token to be setup, one needs a ticket cookie. You wrote here an API user is needed :
https://forum.proxmox.com/threads/api-automation-power-on-off-vm-and-else.92467/#post-450498 but the example doesn't match the actual working implementation.

For anyone who is still confused ,there are two ways:
1. Use existing user and password and ticket cookie - no API setup needed
2. Create an API token ( which is bound to a user ) and use the notation for that to do direct calls to the API.

I understand the mix up because of the limited documentation, just wanted to create some clarity, thank you.

By the way, I do not use Powershell, I use bash and Linux.
 
Last edited:

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!