Corsinvest.ProxmoxVE.Api.Extension
Helpers built on the client for the jobs that every tool repeats: reading data as typed objects, finding VMs and containers without knowing their node, changing their state, snapshots. The cv4pve tools are built on this package.
dotnet add package Corsinvest.ProxmoxVE.Api.ExtensionNuGet · brings in Api, Shared and Metadata.
using Corsinvest.ProxmoxVE.Api;using Corsinvest.ProxmoxVE.Api.Extension;Typed reads
Section titled “Typed reads”The package adds a GetAsync() to the endpoints that read data. It calls the API and returns the
answer as a model of the Shared package, with the same optional
arguments as the generated method:
var nodes = await client.Nodes.GetAsync();foreach (var node in nodes) { Console.WriteLine($"{node.Node} {node.Status}"); }
var config = await client.Nodes["pve01"].Qemu[100].Config.GetAsync();Console.WriteLine($"{config.Name}: {config.Cores} cores");
var snapshots = await client.Nodes["pve01"].Qemu[100].Snapshot.GetAsync();GetAsync() throws PveResultException when the call fails, unlike the generated methods: see
Errors. An endpoint without GetAsync() is read
with its generated method and ToModel<T>().
Cluster resources
Section titled “Cluster resources”These read /cluster/resources, one call for the whole cluster, and return typed items:
| Method | Returns |
|---|---|
GetResourcesAsync(ClusterResourceType.All) |
Every resource: nodes, guests, storages, pools, SDN |
GetNodesAsync(), GetNodeAsync("pve01") |
Nodes |
GetStoragesAsync(), GetStorageAsync("local") |
Storages |
GetVmsAsync() |
VMs and containers, sorted by node and id |
GetVmAsync(100), GetVmAsync("web01") |
One VM or container, by id or by name |
GetClusterInfoAsync() |
Whether the host is a cluster or a single node, and its name |
foreach (var vm in await client.GetVmsAsync()){ Console.WriteLine($"{vm.VmId} {vm.Name} {vm.Node} {vm.VmType} {vm.Status}");}
var web01 = await client.GetVmAsync("web01");var status = await client.GetVmStatusAsync(web01.Node, web01.VmType, web01.VmId);GetVmAsync, GetNodeAsync and GetStorageAsync throw ArgumentException when nothing has that
id or name.
VMs and containers by pattern
Section titled “VMs and containers by pattern”GetVmsAsync(pattern) selects guests with the same syntax the cv4pve tools accept in --vmid:
// every guest with the tag "production", except VM 105 and those on node pve03var vms = await client.GetVmsAsync("@tag-production,-105,-@node-pve03");Items are separated by commas. An item selects:
| Item | Selects |
|---|---|
100 |
The guest with that id |
web01 |
The guest with that name, without regard to case |
100:110 |
The ids in the range, ends included |
web%, %db, %test% |
Names that start with, end with, contain the text |
@all |
Every guest of the cluster |
@node-pve01 |
Every guest on that node (also @all-pve01) |
@pool-customer1 |
Every guest of that pool and of its nested pools |
@tag-production |
Every guest with that tag |
An item that starts with - removes what it selects from the result: @all,-@tag-test is every guest
without the tag test. Exclusions are applied after all the other items, whatever their position.
Power and state
Section titled “Power and state”Without knowing the node or whether the id is a VM or a container:
using Corsinvest.ProxmoxVE.Api.Shared.Models.Vm;
var result = await client.ChangeStatusVmAsync(100, VmStatus.Shutdown);if (result.IsSuccessStatusCode){ await client.WaitForTaskToFinishAsync(result, timeout: 120_000);}VmStatus is Start, Stop, Shutdown, Reboot, Suspend, Resume or Reset (Reset is not
available for containers). The call returns the Result of the request, with the task to wait for.
With node and type known, GetVmStatusAsync and GetVmConfigAsync return the status and the
configuration of a VM or a container through one method, and VmUnlockAsync removes a lock:
foreach (var vm in await client.GetVmsAsync("@pool-customer1")){ var current = await client.GetVmStatusAsync(vm.Node, vm.VmType, vm.VmId); var config = await client.GetVmConfigAsync(vm.Node, vm.VmType, vm.VmId); Console.WriteLine($"{vm.VmId} {vm.Name} {current.Status} {config.Memory} MB");}Snapshots
Section titled “Snapshots”SnapshotHelper does the same operation for VMs and containers, and waits for the task. The timeout
is in milliseconds:
using Corsinvest.ProxmoxVE.Api.Extension.Utils;
var vm = await client.GetVmAsync("web01");
var result = await SnapshotHelper.CreateSnapshotAsync(client, vm.Node, vm.VmType, vm.VmId, "before-update", "Before the update", state: false, timeout: 60_000);
foreach (var snapshot in await SnapshotHelper.GetSnapshotsAsync(client, vm.Node, vm.VmType, vm.VmId)){ Console.WriteLine($"{snapshot.Name} {snapshot.Date:g} {snapshot.Description}");}
result = await SnapshotHelper.RemoveSnapshotAsync(client, vm.Node, vm.VmType, vm.VmId, "before-update", timeout: 60_000);RollbackSnapshotAsync and UpdateSnapshotAsync complete the set. They return the Result of the
request: check it, and read the exit status of the task as in
Tasks.
Several nodes
Section titled “Several nodes”ClientHelper.GetClientAndTryLoginAsync takes a list of hosts and returns a client connected to the
first one that answers: see Connection.
Schedules
Section titled “Schedules”PveCalendarEvent reads the schedule syntax Proxmox VE uses for backup and replication jobs
(mon..fri 02:30, *:0/15, sat 03:00):
using Corsinvest.ProxmoxVE.Api.Extension.Scheduling;
var schedule = PveCalendarEvent.Parse("mon..fri 02:30");Console.WriteLine(schedule.NextOccurrence(DateTime.Now));Parse throws PveCalendarParseException for a text it cannot read; TryParse returns false.
Fixed dates (2026-12-25) are not supported.
Shell: API calls as data
Section titled “Shell: API calls as data”Corsinvest.ProxmoxVE.Api.Extension.Shell runs an API call written as text (method, path,
--key value parameters) and returns data. cv4pve-cli and the bots are built on it; formatting the
output and storing aliases are up to the caller.
using Corsinvest.ProxmoxVE.Api.Extension.Shell;
// --key value parameters, as typed on a command linevar (parameters, _) = ApiCommandLine.ParseParameters(["--type", "vm"]);var command = new ApiCommand(MethodType.Get, "/cluster/resources", parameters.ToDictionary(a => a.Key, a => (object)a.Value));
var response = await ApiRequest.ExecuteAsync(client, command);if (!response.IsSuccess){ Console.Error.WriteLine($"{response.StatusCode} {response.Error}"); foreach (var (name, error) in response.ParameterErrors) { Console.Error.WriteLine($"{name}: {error}"); }}An alias is a command with placeholders, filled by position or looked up in the cluster:
var alias = new ApiAlias("do start vm", "Start a VM", "create /nodes/{node}/qemu/{vmid}/status/start");var expanded = await ApiCommandLine.ExpandAliasAsync(alias, ["--guest", "web01"], client);if (expanded.Command != null){ var started = await ApiRequest.ExecuteAsync(client, expanded.Command, new ApiWaitOptions(TimeSpan.FromMinutes(5))); Console.WriteLine(!started.IsSuccess ? started.Error : started.Task?.Succeeded == true ? "started" : started.Task?.ExitStatus);}With ApiWaitOptions the call waits for the task it started and reports how it ended.
ApiSchema reads the API schema as data: the methods of a path with their parameters and returned
fields, the values a parameter accepts, and what is under a path.
using Corsinvest.ProxmoxVE.Api.Metadata;
var root = await GeneratorClassApi.GenerateAsync("pve01.example.com", 8006); // schema read from a node
foreach (var method in ApiSchema.GetMethods(root, "/nodes/pve01/qemu/100/config") ?? []){ Console.WriteLine($"{method.Method}: {method.Description}"); foreach (var parameter in method.Parameters) { Console.WriteLine($" --{parameter.Name} {string.Join(",", ApiSchema.GetAllowedValues(parameter))}"); }}
var children = await ApiSchema.GetChildrenAsync(client, root, "/nodes");foreach (var child in children.Children) { Console.WriteLine(child.Name); }if (children.Error != null) { Console.Error.WriteLine(children.Error); }ApiSchema.ToTable(response.Data, root, resource) turns the data of an answer into a
TableGenerator, as pvesh shows it: an object with
every key, a list with the columns the schema describes, or null for a single value.
ApiTableOptions chooses what to show: AllColumns: true shows every column of a list, and
HumanReadable: false keeps the values as the API returns them instead of sizes, percentages,
durations and dates as text.