feat: close API coverage gaps — 51 new cmdlets across 6 areas

Phase 1 — Missing Update/Set operations (10 cmdlets):
- SDN: Set-PveSdnZone/Vnet/Subnet/Controller/Ipam/Dns + Invoke-PveSdnApply
- Set-PveRole, Set-PveStorage, Set-PveApiToken

Phase 2 — Cluster operations (8 cmdlets):
- Get-PveClusterResource (cluster-wide inventory)
- Get-PveTaskList, Stop-PveTask
- Get/New/Set/Remove-PvePool
- Get-PveBackupInfo (unprotected VMs)

Phase 3 — VM & container gaps (14 cmdlets):
- Move-PveVmDisk, Remove-PveVmDisk
- Guest agent: Get-PveVmGuestOsInfo/FsInfo, Read/Write-PveVmGuestFile,
  Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim
- Suspend/Resume-PveContainer, Resize-PveContainerDisk,
  New-PveContainerTemplate, Move-PveContainerVolume,
  Get-PveContainerInterface

Phase 4 — Storage content management (4 cmdlets):
- Get-PveStorageStatus, Remove/Set-PveStorageContent, New-PveStorageDisk

Phase 5 — Node operations (6 cmdlets):
- Get/Set-PveNodeConfig, Get/Set-PveNodeDns, Start/Stop-PveNodeVms

Phase 6 — Access management (9 cmdlets):
- Get/New/Set/Remove-PveGroup, Get/New/Set/Remove-PveDomain, Set-PvePassword

Total: 169 cmdlets (was 118). Includes models, services, format views,
Pester unit tests, manifest updates, README, CHANGELOG, API coverage docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-03-21 09:55:19 -05:00
parent ca4585f134
commit df4cae01bc
85 changed files with 7172 additions and 8 deletions
+13
View File
@@ -16,6 +16,19 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
- SDN Controller cmdlets (3): Get/New/Remove-PveSdnController for controller management
- PSGallery version badge in README
- Integration tests for firewall rules, aliases, IP sets, backup jobs, and OVA import
- SDN Update cmdlets (7): Set-PveSdnZone/Vnet/Subnet/Controller/Ipam/Dns + Invoke-PveSdnApply
- Set-PveRole, Set-PveStorage, Set-PveApiToken for missing update operations
- Get-PveClusterResource: single-call cluster-wide inventory of all VMs, containers, nodes, storage
- Task management: Get-PveTaskList (list tasks on node), Stop-PveTask (cancel running tasks)
- Pool management cmdlets (4): Get/New/Set/Remove-PvePool
- Get-PveBackupInfo: find VMs/containers not covered by backup jobs
- VM disk operations: Move-PveVmDisk (storage migration), Remove-PveVmDisk (detach/delete)
- Guest agent extensions (6): Get-PveVmGuestOsInfo, Get-PveVmGuestFsInfo, Read/Write-PveVmGuestFile, Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim
- Container gaps (6): Suspend/Resume-PveContainer, Resize-PveContainerDisk, New-PveContainerTemplate, Move-PveContainerVolume, Get-PveContainerInterface
- Storage content management (4): Get-PveStorageStatus, Remove/Set-PveStorageContent, New-PveStorageDisk
- Node operations (6): Get/Set-PveNodeConfig, Get/Set-PveNodeDns, Start/Stop-PveNodeVms
- Access management (9): Get/New/Set/Remove-PveGroup, Get/New/Set/Remove-PveDomain, Set-PvePassword
- Two-tier version gating: introduced vs default version with clear user messaging
### Fixed
+81
View File
@@ -359,19 +359,100 @@ SDN management requires Proxmox VE 8.0 or later. Connected server is version 7.4
| `New-PveBackupJob` | Create a scheduled backup job |
| `Set-PveBackupJob` | Update a scheduled backup job |
| `Remove-PveBackupJob` | Delete a scheduled backup job |
| `Get-PveBackupInfo` | Find VMs/containers not covered by backup jobs |
### SDN — IPAM / DNS / Controllers (PVE 8.0+)
| Cmdlet | Description |
|---|---|
| `Get-PveSdnIpam` | List SDN IPAM plugins |
| `New-PveSdnIpam` | Create an SDN IPAM plugin |
| `Set-PveSdnIpam` | Update an SDN IPAM plugin |
| `Remove-PveSdnIpam` | Remove an SDN IPAM plugin |
| `Get-PveSdnDns` | List SDN DNS plugins |
| `New-PveSdnDns` | Create an SDN DNS plugin |
| `Set-PveSdnDns` | Update an SDN DNS plugin |
| `Remove-PveSdnDns` | Remove an SDN DNS plugin |
| `Get-PveSdnController` | List SDN controllers |
| `New-PveSdnController` | Create an SDN controller |
| `Set-PveSdnController` | Update an SDN controller |
| `Remove-PveSdnController` | Remove an SDN controller |
| `Invoke-PveSdnApply` | Apply pending SDN configuration changes |
| `Set-PveSdnZone` | Update an SDN zone |
| `Set-PveSdnVnet` | Update an SDN VNet |
| `Set-PveSdnSubnet` | Update an SDN subnet |
### Cluster
| Cmdlet | Description |
|---|---|
| `Get-PveClusterResource` | List all resources (VMs, containers, nodes, storage) cluster-wide |
### Pools
| Cmdlet | Description |
|---|---|
| `Get-PvePool` | List resource pools |
| `New-PvePool` | Create a resource pool |
| `Set-PvePool` | Update a resource pool |
| `Remove-PvePool` | Delete a resource pool |
### VM Disk Operations
| Cmdlet | Description |
|---|---|
| `Move-PveVmDisk` | Move a VM disk to a different storage |
| `Remove-PveVmDisk` | Detach and optionally delete a VM disk |
### Guest Agent Extensions
| Cmdlet | Description |
|---|---|
| `Get-PveVmGuestOsInfo` | Get guest OS information via QEMU agent |
| `Get-PveVmGuestFsInfo` | Get guest filesystem information |
| `Read-PveVmGuestFile` | Read a file from inside a guest VM |
| `Write-PveVmGuestFile` | Write a file inside a guest VM |
| `Set-PveVmGuestPassword` | Change a user password inside a guest VM |
| `Invoke-PveVmGuestFsTrim` | TRIM guest VM filesystems |
### Additional Container Operations
| Cmdlet | Description |
|---|---|
| `Suspend-PveContainer` | Suspend (freeze) a container |
| `Resume-PveContainer` | Resume a suspended container |
| `Resize-PveContainerDisk` | Resize a container disk/volume |
| `New-PveContainerTemplate` | Convert a container to a template |
| `Move-PveContainerVolume` | Move a container volume to a different storage |
| `Get-PveContainerInterface` | Get container network interfaces |
### Storage Content
| Cmdlet | Description |
|---|---|
| `Get-PveStorageStatus` | Get storage usage statistics |
| `Remove-PveStorageContent` | Delete a volume, backup, or ISO from storage |
| `Set-PveStorageContent` | Update volume notes/properties |
| `New-PveStorageDisk` | Allocate a new empty disk image |
### Node Operations
| Cmdlet | Description |
|---|---|
| `Get-PveNodeConfig` | Get node configuration |
| `Set-PveNodeConfig` | Update node configuration |
| `Get-PveNodeDns` | Get node DNS configuration |
| `Set-PveNodeDns` | Update node DNS configuration |
| `Start-PveNodeVms` | Start all VMs on a node |
| `Stop-PveNodeVms` | Stop all VMs on a node |
### Access — Groups & Domains
| Cmdlet | Description |
|---|---|
| `Get-PveGroup` | List user groups |
| `New-PveGroup` | Create a user group |
| `Set-PveGroup` | Update a user group |
| `Remove-PveGroup` | Delete a user group |
| `Get-PveDomain` | List authentication realms (PAM, LDAP, AD, OpenID) |
| `New-PveDomain` | Create an authentication realm |
| `Set-PveDomain` | Update an authentication realm |
| `Remove-PveDomain` | Delete an authentication realm |
| `Set-PvePassword` | Change a user's password |
| `Set-PveRole` | Update a role's privileges |
| `Set-PveStorage` | Update a storage definition |
| `Set-PveApiToken` | Update an API token |
## Known Limitations (v1)
+20 -7
View File
@@ -4,7 +4,7 @@ This document tracks which PVE API areas are implemented in PSProxmoxVE and whic
**Last updated:** 2026-03-21
**Module version:** 0.1.0-preview
**Total cmdlets:** 110
**Total cmdlets:** 169
## Implemented
@@ -27,12 +27,26 @@ This document tracks which PVE API areas are implemented in PSProxmoxVE and whic
| **API Tokens** | 3 | `/access/users/{userid}/tokens/*` (CRUD) |
| **Templates** | 4 | VM template conversion, listing, cloning, removal |
| **Cloud-Init** | 3 | `/nodes/{node}/qemu/{vmid}/config` (cloud-init fields), `/nodes/{node}/qemu/{vmid}/cloudinit/regenerate` |
| **Tasks** | 2 | `/nodes/{node}/tasks/{upid}/status` (get, wait) |
| **Tasks** | 4 | `/nodes/{node}/tasks` (list, get status, stop, wait) |
| **Firewall** | 21 | `/cluster/firewall/*`, `/nodes/{node}/firewall/*`, `/nodes/{node}/qemu/{vmid}/firewall/*`, `/nodes/{node}/lxc/{vmid}/firewall/*` (rules, groups, aliases, IP sets, options, refs) |
| **Backup** | 5 | `/nodes/{node}/vzdump`, `/cluster/backup/*` (ad-hoc backup, scheduled job CRUD) |
| **SDN IPAM** | 3 | `/cluster/sdn/ipams/*` (CRUD) |
| **SDN DNS** | 3 | `/cluster/sdn/dns/*` (CRUD) |
| **SDN Controllers** | 3 | `/cluster/sdn/controllers/*` (CRUD) |
| **Backup** | 7 | `/nodes/{node}/vzdump`, `/cluster/backup/*`, `/cluster/backup-info/not-backed-up` |
| **SDN Zones** | 4 | `/cluster/sdn/zones/*` (CRUD + update) |
| **SDN VNets** | 4 | `/cluster/sdn/vnets/*` (CRUD + update) |
| **SDN Subnets** | 4 | `/cluster/sdn/vnets/{vnet}/subnets/*` (CRUD + update) |
| **SDN IPAM** | 4 | `/cluster/sdn/ipams/*` (CRUD + update) |
| **SDN DNS** | 4 | `/cluster/sdn/dns/*` (CRUD + update) |
| **SDN Controllers** | 4 | `/cluster/sdn/controllers/*` (CRUD + update) |
| **SDN Apply** | 1 | `PUT /cluster/sdn` (apply pending changes) |
| **Cluster** | 1 | `GET /cluster/resources` (cluster-wide inventory) |
| **Pools** | 4 | `/pools/*` (CRUD) |
| **VM Disk Ops** | 2 | `move_disk`, `unlink` |
| **Guest Agent (ext)** | 6 | `get-osinfo`, `get-fsinfo`, `file-read`, `file-write`, `set-user-password`, `fstrim` |
| **Container Ops** | 6 | `suspend`, `resume`, `resize`, `template`, `move_volume`, `interfaces` |
| **Storage Content** | 4 | `status`, `content` DELETE/PUT/POST |
| **Node Ops** | 6 | `config`, `dns`, `startall`, `stopall` |
| **Access Groups** | 4 | `/access/groups/*` (CRUD) |
| **Access Domains** | 4 | `/access/domains/*` (CRUD) |
| **Access Password** | 1 | `PUT /access/password` |
## Not Yet Implemented
@@ -40,7 +54,6 @@ This document tracks which PVE API areas are implemented in PSProxmoxVE and whic
| Area | Key Endpoints | Notes | Priority |
|------|--------------|-------|----------|
| **Pool Management** | `/pools/*` | Multi-tenant environments, resource grouping | Medium |
### Lower Priority Gaps
@@ -0,0 +1,129 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Cluster;
/// <summary>
/// Represents a resource entry returned by the /cluster/resources endpoint.
/// Resources can be VMs, containers, nodes, storage, or SDN objects.
/// </summary>
public class PveClusterResource
{
/// <summary>
/// The unique resource identifier (e.g., "node/pve1", "qemu/100", "storage/local").
/// </summary>
[JsonPropertyName("id")]
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>
/// The resource type: "vm" (QEMU), "lxc" (container), "node", "storage", or "sdn".
/// </summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string? Type { get; set; }
/// <summary>
/// The current status of the resource (e.g., "running", "stopped", "online").
/// </summary>
[JsonPropertyName("status")]
[JsonProperty("status")]
public string? Status { get; set; }
/// <summary>
/// The display name of the resource.
/// </summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string? Name { get; set; }
/// <summary>
/// The node on which this resource resides.
/// </summary>
[JsonPropertyName("node")]
[JsonProperty("node")]
public string? Node { get; set; }
/// <summary>
/// The pool this resource belongs to, if any.
/// </summary>
[JsonPropertyName("pool")]
[JsonProperty("pool")]
public string? Pool { get; set; }
/// <summary>
/// Uptime in seconds.
/// </summary>
[JsonPropertyName("uptime")]
[JsonProperty("uptime")]
public long? Uptime { get; set; }
/// <summary>
/// Maximum number of CPUs/cores available to this resource.
/// </summary>
[JsonPropertyName("maxcpu")]
[JsonProperty("maxcpu")]
public double? MaxCpu { get; set; }
/// <summary>
/// Current CPU usage as a fraction (0.0 to 1.0).
/// </summary>
[JsonPropertyName("cpu")]
[JsonProperty("cpu")]
public double? Cpu { get; set; }
/// <summary>
/// Maximum memory in bytes.
/// </summary>
[JsonPropertyName("maxmem")]
[JsonProperty("maxmem")]
public long? MaxMem { get; set; }
/// <summary>
/// Current memory usage in bytes.
/// </summary>
[JsonPropertyName("mem")]
[JsonProperty("mem")]
public long? Mem { get; set; }
/// <summary>
/// Maximum disk space in bytes.
/// </summary>
[JsonPropertyName("maxdisk")]
[JsonProperty("maxdisk")]
public long? MaxDisk { get; set; }
/// <summary>
/// Current disk usage in bytes.
/// </summary>
[JsonPropertyName("disk")]
[JsonProperty("disk")]
public long? Disk { get; set; }
/// <summary>
/// Whether this resource is a template (1) or not (0). Only for VM/LXC types.
/// </summary>
[JsonPropertyName("template")]
[JsonProperty("template")]
public int? Template { get; set; }
/// <summary>
/// The VM or container ID. Only for VM/LXC types.
/// </summary>
[JsonPropertyName("vmid")]
[JsonProperty("vmid")]
public int? VmId { get; set; }
/// <summary>
/// The HA (High Availability) state of the resource, if managed by HA.
/// </summary>
[JsonPropertyName("hastate")]
[JsonProperty("hastate")]
public string? HaState { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"{Type ?? "unknown"}: {Name ?? Id ?? "N/A"} | Node: {Node ?? "N/A"} | Status: {Status ?? "N/A"}";
}
}
@@ -0,0 +1,40 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace PSProxmoxVE.Core.Models.Cluster;
/// <summary>
/// Represents a Proxmox VE resource pool as returned by the /pools endpoint.
/// </summary>
public class PvePool
{
/// <summary>
/// The pool identifier.
/// </summary>
[JsonPropertyName("poolid")]
[JsonProperty("poolid")]
public string? PoolId { get; set; }
/// <summary>
/// An optional comment or description for the pool.
/// </summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <summary>
/// The pool members (VMs, containers, storage). Returned as a raw array
/// when querying a specific pool.
/// </summary>
[JsonPropertyName("members")]
[JsonProperty("members")]
public JArray? Members { get; set; }
/// <inheritdoc />
public override string ToString()
{
var memberCount = Members?.Count ?? 0;
return $"Pool: {PoolId ?? "N/A"} | Comment: {Comment ?? "-"} | Members: {memberCount}";
}
}
@@ -0,0 +1,37 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Containers;
/// <summary>
/// Represents a network interface on an LXC container.
/// </summary>
public class PveContainerInterface
{
/// <summary>The interface name (e.g., "eth0", "lo").</summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string? Name { get; set; }
/// <summary>The hardware (MAC) address of the interface.</summary>
[JsonPropertyName("hwaddr")]
[JsonProperty("hwaddr")]
public string? HwAddr { get; set; }
/// <summary>The IPv4 address assigned to this interface.</summary>
[JsonPropertyName("inet")]
[JsonProperty("inet")]
public string? Inet { get; set; }
/// <summary>The IPv6 address assigned to this interface.</summary>
[JsonPropertyName("inet6")]
[JsonProperty("inet6")]
public string? Inet6 { get; set; }
/// <inheritdoc />
public override string ToString()
{
var addr = Inet ?? Inet6 ?? "no address";
return $"{Name ?? "Unknown"} ({HwAddr ?? "N/A"}): {addr}";
}
}
@@ -0,0 +1,68 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Storage;
/// <summary>
/// Represents the status of a Proxmox VE storage on a specific node,
/// as returned by the /nodes/{node}/storage/{storage}/status endpoint.
/// </summary>
public class PveStorageStatus
{
/// <summary>Total capacity in bytes.</summary>
[JsonPropertyName("total")]
[JsonProperty("total")]
public long? Total { get; set; }
/// <summary>Used capacity in bytes.</summary>
[JsonPropertyName("used")]
[JsonProperty("used")]
public long? Used { get; set; }
/// <summary>Available (free) capacity in bytes.</summary>
[JsonPropertyName("avail")]
[JsonProperty("avail")]
public long? Available { get; set; }
/// <summary>Whether the storage is currently active (1) or not (0).</summary>
[JsonPropertyName("active")]
[JsonProperty("active")]
public int? Active { get; set; }
/// <summary>Comma-separated list of supported content types.</summary>
[JsonPropertyName("content")]
[JsonProperty("content")]
public string? Content { get; set; }
/// <summary>Whether the storage is enabled (1) or disabled (0).</summary>
[JsonPropertyName("enabled")]
[JsonProperty("enabled")]
public int? Enabled { get; set; }
/// <summary>Whether the storage is shared across cluster nodes (1) or node-local (0).</summary>
[JsonPropertyName("shared")]
[JsonProperty("shared")]
public int? Shared { get; set; }
/// <summary>The storage backend type.</summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string? Type { get; set; }
/// <summary>The storage identifier. Populated by the cmdlet after retrieval.</summary>
public string? Storage { get; set; }
/// <summary>The node name. Populated by the cmdlet after retrieval.</summary>
public string? Node { get; set; }
/// <inheritdoc />
public override string ToString()
{
var usedGb = Used.HasValue ? $"{Used.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A";
var totalGb = Total.HasValue ? $"{Total.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A";
var availGb = Available.HasValue ? $"{Available.Value / 1024.0 / 1024 / 1024:F1} GB" : "N/A";
var activeStr = Active is 1 ? "active" : "inactive";
return $"Storage: {Storage ?? "N/A"} | Type: {Type ?? "N/A"} | {activeStr} | "
+ $"Used: {usedGb}/{totalGb} | Free: {availGb}";
}
}
@@ -0,0 +1,42 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Users;
/// <summary>
/// Represents a Proxmox VE authentication domain/realm as returned by the /access/domains endpoint.
/// </summary>
public class PveDomain
{
/// <summary>The realm identifier (e.g., "pam", "pve", "myldap").</summary>
[JsonPropertyName("realm")]
[JsonProperty("realm")]
public string Realm { get; set; } = string.Empty;
/// <summary>The domain type (e.g., "pam", "pve", "ad", "ldap", "openid").</summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string Type { get; set; } = string.Empty;
/// <summary>Optional comment/description for the domain.</summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <summary>Whether this is the default realm (1) or not (0).</summary>
[JsonPropertyName("default")]
[JsonProperty("default")]
public int? Default { get; set; }
/// <summary>TFA configuration string, if any.</summary>
[JsonPropertyName("tfa")]
[JsonProperty("tfa")]
public string? Tfa { get; set; }
/// <inheritdoc />
public override string ToString()
{
var defaultStr = Default is 1 ? " [default]" : string.Empty;
return $"Realm: {Realm}{defaultStr} | Type: {Type} | Comment: {Comment ?? "N/A"}";
}
}
@@ -0,0 +1,36 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Users;
/// <summary>
/// Represents a Proxmox VE group as returned by the /access/groups endpoint.
/// </summary>
public class PveGroup
{
/// <summary>The unique group identifier.</summary>
[JsonPropertyName("groupid")]
[JsonProperty("groupid")]
public string GroupId { get; set; } = string.Empty;
/// <summary>Optional comment/description for the group.</summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <summary>Comma-separated list of user IDs that belong to this group.</summary>
[JsonPropertyName("users")]
[JsonProperty("users")]
public string? Users { get; set; }
/// <summary>Array of member user IDs. Populated when available.</summary>
[JsonPropertyName("members")]
[JsonProperty("members")]
public string[]? Members { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"Group: {GroupId} | Comment: {Comment ?? "N/A"} | Users: {Users ?? "none"}";
}
}
@@ -0,0 +1,49 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Vms;
/// <summary>
/// Represents filesystem information reported by the QEMU guest agent.
/// </summary>
public class PveGuestFsInfo
{
/// <summary>The filesystem name/device.</summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string? Name { get; set; }
/// <summary>The mount point path.</summary>
[JsonPropertyName("mountpoint")]
[JsonProperty("mountpoint")]
public string? MountPoint { get; set; }
/// <summary>The filesystem type (e.g., "ext4", "ntfs").</summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string? Type { get; set; }
/// <summary>Total size of the filesystem in bytes.</summary>
[JsonPropertyName("total-bytes")]
[JsonProperty("total-bytes")]
public long? TotalBytes { get; set; }
/// <summary>Used space on the filesystem in bytes.</summary>
[JsonPropertyName("used-bytes")]
[JsonProperty("used-bytes")]
public long? UsedBytes { get; set; }
/// <summary>List of disk devices backing this filesystem.</summary>
[JsonPropertyName("disk")]
[JsonProperty("disk")]
public string[]? DiskList { get; set; }
/// <inheritdoc />
public override string ToString()
{
var used = UsedBytes.HasValue && TotalBytes.HasValue && TotalBytes.Value > 0
? $" ({UsedBytes.Value * 100 / TotalBytes.Value}% used)"
: string.Empty;
return $"{MountPoint ?? Name ?? "Unknown"} [{Type ?? "?"}]{used}";
}
}
@@ -0,0 +1,68 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Vms;
/// <summary>
/// Represents OS information reported by the QEMU guest agent.
/// </summary>
public class PveGuestOsInfo
{
/// <summary>The OS identifier (e.g., "mswindows", "linux").</summary>
[JsonPropertyName("id")]
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>The OS name (e.g., "Microsoft Windows 10 Enterprise").</summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string? Name { get; set; }
/// <summary>The kernel release string.</summary>
[JsonPropertyName("kernel-release")]
[JsonProperty("kernel-release")]
public string? KernelRelease { get; set; }
/// <summary>The kernel version string.</summary>
[JsonPropertyName("kernel-version")]
[JsonProperty("kernel-version")]
public string? KernelVersion { get; set; }
/// <summary>The machine hardware name.</summary>
[JsonPropertyName("machine")]
[JsonProperty("machine")]
public string? Machine { get; set; }
/// <summary>The OS version details.</summary>
[JsonPropertyName("version")]
[JsonProperty("version")]
public PveGuestOsVersion? Version { get; set; }
/// <inheritdoc />
public override string ToString()
{
var ver = Version != null ? $" {Version.Major}.{Version.Minor}" : string.Empty;
return $"{Name ?? Id ?? "Unknown"}{ver} ({KernelRelease ?? "N/A"})";
}
}
/// <summary>
/// Represents a guest OS version with major and minor components.
/// </summary>
public class PveGuestOsVersion
{
/// <summary>The version identifier string.</summary>
[JsonPropertyName("id")]
[JsonProperty("id")]
public string? Id { get; set; }
/// <summary>The major version number.</summary>
[JsonPropertyName("major")]
[JsonProperty("major")]
public int? Major { get; set; }
/// <summary>The minor version number.</summary>
[JsonPropertyName("minor")]
[JsonProperty("minor")]
public int? Minor { get; set; }
}
@@ -106,6 +106,24 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Backup compliance info
// -------------------------------------------------------------------------
/// <summary>
/// Returns the list of guests not covered by any backup job.
/// </summary>
public JArray GetNotBackedUp(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
using var client = new PveHttpClient(session);
var response = client.GetAsync("cluster/backup-info/not-backed-up")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data as JArray ?? new JArray();
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
@@ -24,5 +24,24 @@ namespace PSProxmoxVE.Core.Services
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveClusterStatus[]>() ?? Array.Empty<PveClusterStatus>();
}
/// <summary>
/// Returns cluster resources, optionally filtered by type.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="type">Optional resource type filter (vm, lxc, node, storage, sdn).</param>
public PveClusterResource[] GetClusterResources(PveSession session, string? type = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
using var client = new PveHttpClient(session);
var resource = "cluster/resources";
if (!string.IsNullOrEmpty(type))
resource += $"?type={Uri.EscapeDataString(type!)}";
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveClusterResource[]>() ?? Array.Empty<PveClusterResource>();
}
}
}
@@ -318,6 +318,104 @@ namespace PSProxmoxVE.Core.Services
return ParseTask(response, node);
}
// -------------------------------------------------------------------------
// Suspend / Resume
// -------------------------------------------------------------------------
/// <summary>Suspends a container. Returns the task UPID.</summary>
public PveTask SuspendContainer(PveSession session, string node, int vmid)
=> PostStatus(session, node, vmid, "suspend");
/// <summary>Resumes a suspended container. Returns the task UPID.</summary>
public PveTask ResumeContainer(PveSession session, string node, int vmid)
=> PostStatus(session, node, vmid, "resume");
// -------------------------------------------------------------------------
// Disk / volume operations
// -------------------------------------------------------------------------
/// <summary>
/// Resizes a container disk/volume. Returns the task UPID.
/// </summary>
public PveTask ResizeContainerDisk(PveSession session, string node, int vmid, string disk, string size)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(disk)) throw new ArgumentNullException(nameof(disk));
if (string.IsNullOrWhiteSpace(size)) throw new ArgumentNullException(nameof(size));
var formData = new Dictionary<string, string>
{
["disk"] = disk,
["size"] = size
};
using var client = new PveHttpClient(session);
var response = client.PutAsync($"nodes/{node}/lxc/{vmid}/resize", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
/// <summary>
/// Moves a container volume to a different storage. Returns the task UPID.
/// </summary>
public PveTask MoveVolume(PveSession session, string node, int vmid, string volume, string storage, bool delete = true)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
var formData = new Dictionary<string, string>
{
["volume"] = volume,
["storage"] = storage,
["delete"] = delete ? "1" : "0"
};
using var client = new PveHttpClient(session);
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/move_volume", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
// -------------------------------------------------------------------------
// Template
// -------------------------------------------------------------------------
/// <summary>
/// Converts a container to a template. This is irreversible. Returns the task UPID.
/// </summary>
public PveTask ConvertToTemplate(PveSession session, string node, int vmid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
using var client = new PveHttpClient(session);
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/template")
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
// -------------------------------------------------------------------------
// Interfaces
// -------------------------------------------------------------------------
/// <summary>
/// Returns network interface information for a container.
/// </summary>
public PveContainerInterface[] GetInterfaces(PveSession session, string node, int vmid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"nodes/{node}/lxc/{vmid}/interfaces")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveContainerInterface[]>() ?? Array.Empty<PveContainerInterface>();
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
@@ -438,6 +438,108 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// SDN Update operations
// -------------------------------------------------------------------------
/// <summary>
/// Applies pending SDN configuration changes cluster-wide.
/// </summary>
public void ApplySdn(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
using var client = new PveHttpClient(session);
client.PutAsync("cluster/sdn", new Dictionary<string, string>())
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates an SDN zone configuration.
/// </summary>
public void UpdateSdnZone(PveSession session, string zone, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(zone)) throw new ArgumentNullException(nameof(zone));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"cluster/sdn/zones/{Uri.EscapeDataString(zone)}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates an SDN VNet configuration.
/// </summary>
public void UpdateSdnVnet(PveSession session, string vnet, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates an SDN subnet configuration.
/// </summary>
public void UpdateSdnSubnet(PveSession session, string vnet, string subnet, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(vnet)) throw new ArgumentNullException(nameof(vnet));
if (string.IsNullOrWhiteSpace(subnet)) throw new ArgumentNullException(nameof(subnet));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync(
$"cluster/sdn/vnets/{Uri.EscapeDataString(vnet)}/subnets/{Uri.EscapeDataString(subnet)}",
config).GetAwaiter().GetResult();
}
/// <summary>
/// Updates an SDN controller configuration.
/// </summary>
public void UpdateSdnController(PveSession session, string controller, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException(nameof(controller));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"cluster/sdn/controllers/{Uri.EscapeDataString(controller)}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates an SDN IPAM plugin configuration.
/// </summary>
public void UpdateSdnIpam(PveSession session, string ipam, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(ipam)) throw new ArgumentNullException(nameof(ipam));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"cluster/sdn/ipams/{Uri.EscapeDataString(ipam)}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates an SDN DNS plugin configuration.
/// </summary>
public void UpdateSdnDns(PveSession session, string dns, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(dns)) throw new ArgumentNullException(nameof(dns));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"cluster/sdn/dns/{Uri.EscapeDataString(dns)}", config)
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Client;
using PSProxmoxVE.Core.Models.Nodes;
using PSProxmoxVE.Core.Models.Vms;
namespace PSProxmoxVE.Core.Services
{
@@ -39,6 +41,104 @@ namespace PSProxmoxVE.Core.Services
return data?.ToObject<PveNodeStatus>() ?? new PveNodeStatus();
}
/// <summary>
/// Returns the configuration of a specific node.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
public JObject GetNodeConfig(PveSession session, string node)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"nodes/{node}/config").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data as JObject ?? new JObject();
}
/// <summary>
/// Updates the configuration of a specific node.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="config">Configuration parameters to update.</param>
public void SetNodeConfig(PveSession session, string node, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"nodes/{node}/config", config).GetAwaiter().GetResult();
}
/// <summary>
/// Returns the DNS configuration of a specific node.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
public JObject GetNodeDns(PveSession session, string node)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"nodes/{node}/dns").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data as JObject ?? new JObject();
}
/// <summary>
/// Updates the DNS configuration of a specific node.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="config">DNS configuration parameters to update.</param>
public void SetNodeDns(PveSession session, string node, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"nodes/{node}/dns", config).GetAwaiter().GetResult();
}
/// <summary>
/// Starts all VMs and containers on a node. Returns the task UPID.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="config">Optional parameters (e.g. vms to limit which VMs start).</param>
public PveTask StartAll(PveSession session, string node, Dictionary<string, string>? config = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
var formData = config ?? new Dictionary<string, string>();
using var client = new PveHttpClient(session);
var response = client.PostAsync($"nodes/{node}/startall", formData).GetAwaiter().GetResult();
return ParseTask(response, node);
}
/// <summary>
/// Stops all VMs and containers on a node. Returns the task UPID.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="config">Optional parameters (e.g. vms, force-stop).</param>
public PveTask StopAll(PveSession session, string node, Dictionary<string, string>? config = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
var formData = config ?? new Dictionary<string, string>();
using var client = new PveHttpClient(session);
var response = client.PostAsync($"nodes/{node}/stopall", formData).GetAwaiter().GetResult();
return ParseTask(response, node);
}
/// <summary>
/// Returns the Proxmox VE version running on the server.
/// </summary>
@@ -54,5 +154,20 @@ namespace PSProxmoxVE.Core.Services
throw new InvalidOperationException("Failed to retrieve PVE version from API response.");
return PveVersion.Parse(versionStr!);
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
private static PveTask ParseTask(string response, string node)
{
var data = JObject.Parse(response)["data"];
if (data?.Type == JTokenType.String)
return new PveTask { Upid = data.ToString(), Node = node };
var task = data?.ToObject<PveTask>() ?? new PveTask();
task.Node = node;
return task;
}
}
}
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Client;
using PSProxmoxVE.Core.Models.Cluster;
namespace PSProxmoxVE.Core.Services
{
/// <summary>
/// Service for Proxmox VE resource pool API operations.
/// </summary>
public class PoolService
{
/// <summary>
/// Returns all resource pools.
/// </summary>
public PvePool[] GetPools(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
using var client = new PveHttpClient(session);
var response = client.GetAsync("pools").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PvePool[]>() ?? Array.Empty<PvePool>();
}
/// <summary>
/// Returns a single resource pool by ID.
/// </summary>
public PvePool? GetPool(PveSession session, string poolId)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"pools/{Uri.EscapeDataString(poolId)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PvePool>();
}
/// <summary>
/// Creates a new resource pool.
/// </summary>
public void CreatePool(PveSession session, string poolId, string? comment = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
using var client = new PveHttpClient(session);
var config = new Dictionary<string, string> { { "poolid", poolId } };
if (!string.IsNullOrEmpty(comment))
config["comment"] = comment!;
client.PostAsync("pools", config).GetAwaiter().GetResult();
}
/// <summary>
/// Updates an existing resource pool.
/// </summary>
public void UpdatePool(PveSession session, string poolId, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"pools/{Uri.EscapeDataString(poolId)}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Removes a resource pool.
/// </summary>
public void RemovePool(PveSession session, string poolId)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(poolId)) throw new ArgumentNullException(nameof(poolId));
using var client = new PveHttpClient(session);
client.DeleteAsync($"pools/{Uri.EscapeDataString(poolId)}")
.GetAwaiter().GetResult();
}
}
}
@@ -187,6 +187,105 @@ namespace PSProxmoxVE.Core.Services
client.DeleteAsync($"storage/{storage}").GetAwaiter().GetResult();
}
/// <summary>
/// Updates a storage definition.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="storage">The storage identifier to update.</param>
/// <param name="config">Configuration parameters to update.</param>
public void UpdateStorage(PveSession session, string storage, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"storage/{Uri.EscapeDataString(storage)}", config)
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Storage status & content management
// -------------------------------------------------------------------------
/// <summary>
/// Returns the status of a specific storage on a node.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="storage">The storage identifier.</param>
public PveStorageStatus GetStorageStatus(PveSession session, string node, string storage)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"nodes/{node}/storage/{storage}/status").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveStorageStatus>() ?? new PveStorageStatus();
}
/// <summary>
/// Removes a content volume from a storage on a node.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="storage">The storage identifier.</param>
/// <param name="volume">The volume identifier to remove.</param>
public void RemoveContent(PveSession session, string node, string storage, string volume)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume));
using var client = new PveHttpClient(session);
client.DeleteAsync($"nodes/{node}/storage/{storage}/content/{Uri.EscapeDataString(volume)}")
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates properties (e.g. notes) of a content volume on a storage.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="storage">The storage identifier.</param>
/// <param name="volume">The volume identifier to update.</param>
/// <param name="config">Configuration parameters to update.</param>
public void UpdateContent(PveSession session, string node, string storage, string volume, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
if (string.IsNullOrWhiteSpace(volume)) throw new ArgumentNullException(nameof(volume));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"nodes/{node}/storage/{storage}/content/{Uri.EscapeDataString(volume)}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Allocates a new disk image on a storage. Returns the task UPID.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="storage">The storage identifier.</param>
/// <param name="config">Allocation parameters (filename, size, format).</param>
public PveTask AllocateDisk(PveSession session, string node, string storage, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
var response = client.PostAsync($"nodes/{node}/storage/{storage}/content", config)
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
@@ -103,5 +103,57 @@ namespace PSProxmoxVE.Core.Services
Thread.Sleep(effectivePoll);
}
}
/// <summary>
/// Returns a list of tasks on the specified node, with optional filters.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The node name.</param>
/// <param name="vmid">Optional VM ID filter.</param>
/// <param name="source">Optional source filter: "all" or "active".</param>
/// <param name="typeFilter">Optional task type filter (e.g., "qmstart").</param>
/// <param name="limit">Maximum number of tasks to return. Defaults to 50.</param>
public PveTask[] GetTasks(PveSession session, string node, int? vmid = null,
string? source = null, string? typeFilter = null, int limit = 50)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
using var client = new PveHttpClient(session);
var queryParts = new List<string> { $"limit={limit}" };
if (vmid.HasValue)
queryParts.Add($"vmid={vmid.Value}");
if (!string.IsNullOrEmpty(source))
queryParts.Add($"source={Uri.EscapeDataString(source!)}");
if (!string.IsNullOrEmpty(typeFilter))
queryParts.Add($"typefilter={Uri.EscapeDataString(typeFilter!)}");
var query = string.Join("&", queryParts);
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/tasks?{query}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
var tasks = data?.ToObject<PveTask[]>() ?? Array.Empty<PveTask>();
foreach (var t in tasks)
t.Node ??= node;
return tasks;
}
/// <summary>
/// Stops (cancels) a running task on the specified node.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The node name.</param>
/// <param name="upid">The UPID of the task to stop.</param>
public void StopTask(PveSession session, string node, string upid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(upid)) throw new ArgumentNullException(nameof(upid));
using var client = new PveHttpClient(session);
var encodedUpid = Uri.EscapeDataString(upid);
client.DeleteAsync($"nodes/{Uri.EscapeDataString(node)}/tasks/{encodedUpid}")
.GetAwaiter().GetResult();
}
}
}
@@ -189,6 +189,23 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates an API token's configuration.
/// </summary>
public void UpdateApiToken(PveSession session, string userId, string tokenId, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
if (string.IsNullOrWhiteSpace(tokenId)) throw new ArgumentNullException(nameof(tokenId));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
var encodedUser = Uri.EscapeDataString(userId);
var encodedToken = Uri.EscapeDataString(tokenId);
client.PutAsync($"access/users/{encodedUser}/token/{encodedToken}", config)
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Roles
// -------------------------------------------------------------------------
@@ -235,6 +252,165 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates a role's privileges.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="roleId">The role identifier to update.</param>
/// <param name="privileges">Comma-separated list of privileges.</param>
public void UpdateRole(PveSession session, string roleId, string privileges)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(roleId)) throw new ArgumentNullException(nameof(roleId));
if (string.IsNullOrWhiteSpace(privileges)) throw new ArgumentNullException(nameof(privileges));
var formData = new Dictionary<string, string> { ["privs"] = privileges };
using var client = new PveHttpClient(session);
client.PutAsync($"access/roles/{Uri.EscapeDataString(roleId)}", formData)
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Groups
// -------------------------------------------------------------------------
/// <summary>Returns all groups.</summary>
/// <param name="session">The authenticated PVE session.</param>
public PveGroup[] GetGroups(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
using var client = new PveHttpClient(session);
var response = client.GetAsync("access/groups").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveGroup[]>() ?? Array.Empty<PveGroup>();
}
/// <summary>Creates a new group.</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="groupId">The group identifier.</param>
/// <param name="comment">Optional comment/description.</param>
public void CreateGroup(PveSession session, string groupId, string? comment = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId));
var formData = new Dictionary<string, string> { ["groupid"] = groupId };
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
using var client = new PveHttpClient(session);
client.PostAsync("access/groups", formData).GetAwaiter().GetResult();
}
/// <summary>Updates a group's properties.</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="groupId">The group identifier to update.</param>
/// <param name="config">Configuration parameters to update.</param>
public void UpdateGroup(PveSession session, string groupId, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"access/groups/{Uri.EscapeDataString(groupId)}", config)
.GetAwaiter().GetResult();
}
/// <summary>Removes a group.</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="groupId">The group identifier to remove.</param>
public void RemoveGroup(PveSession session, string groupId)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(groupId)) throw new ArgumentNullException(nameof(groupId));
using var client = new PveHttpClient(session);
client.DeleteAsync($"access/groups/{Uri.EscapeDataString(groupId)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Domains / Realms
// -------------------------------------------------------------------------
/// <summary>Returns all authentication domains/realms.</summary>
/// <param name="session">The authenticated PVE session.</param>
public PveDomain[] GetDomains(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
using var client = new PveHttpClient(session);
var response = client.GetAsync("access/domains").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveDomain[]>() ?? Array.Empty<PveDomain>();
}
/// <summary>Creates a new authentication domain/realm.</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="config">Domain configuration parameters (must include realm and type).</param>
public void CreateDomain(PveSession session, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PostAsync("access/domains", config).GetAwaiter().GetResult();
}
/// <summary>Updates an authentication domain/realm.</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="realm">The realm identifier to update.</param>
/// <param name="config">Configuration parameters to update.</param>
public void UpdateDomain(PveSession session, string realm, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(realm)) throw new ArgumentNullException(nameof(realm));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"access/domains/{Uri.EscapeDataString(realm)}", config)
.GetAwaiter().GetResult();
}
/// <summary>Removes an authentication domain/realm.</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="realm">The realm identifier to remove.</param>
public void RemoveDomain(PveSession session, string realm)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(realm)) throw new ArgumentNullException(nameof(realm));
using var client = new PveHttpClient(session);
client.DeleteAsync($"access/domains/{Uri.EscapeDataString(realm)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Password
// -------------------------------------------------------------------------
/// <summary>Changes a user's password.</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="userId">The user ID in "username@realm" format.</param>
/// <param name="password">The new password.</param>
public void ChangePassword(PveSession session, string userId, string password)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException(nameof(password));
var formData = new Dictionary<string, string>
{
["userid"] = userId,
["password"] = password
};
using var client = new PveHttpClient(session);
client.PutAsync("access/password", formData).GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Permissions / ACLs
// -------------------------------------------------------------------------
+157
View File
@@ -521,6 +521,163 @@ namespace PSProxmoxVE.Core.Services
return JObject.Parse(response)["data"] as JObject ?? new JObject();
}
// -------------------------------------------------------------------------
// Disk operations
// -------------------------------------------------------------------------
/// <summary>
/// Moves a VM disk to a different storage. Returns the task UPID.
/// </summary>
public PveTask MoveDisk(PveSession session, string node, int vmid, string disk, string storage, string? format = null, bool delete = true)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(disk)) throw new ArgumentNullException(nameof(disk));
if (string.IsNullOrWhiteSpace(storage)) throw new ArgumentNullException(nameof(storage));
var formData = new Dictionary<string, string>
{
["disk"] = disk,
["storage"] = storage,
["delete"] = delete ? "1" : "0"
};
if (!string.IsNullOrEmpty(format))
formData["format"] = format!;
using var client = new PveHttpClient(session);
var response = client.PostAsync($"nodes/{node}/qemu/{vmid}/move_disk", formData)
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
/// <summary>
/// Unlinks (detaches) disks from a VM.
/// </summary>
public void UnlinkDisk(PveSession session, string node, int vmid, string idlist, bool force = false)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(idlist)) throw new ArgumentNullException(nameof(idlist));
var formData = new Dictionary<string, string>
{
["idlist"] = idlist
};
if (force)
formData["force"] = "1";
using var client = new PveHttpClient(session);
client.PutAsync($"nodes/{node}/qemu/{vmid}/unlink", formData)
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Guest agent — extended operations
// -------------------------------------------------------------------------
/// <summary>
/// Retrieves OS information from the QEMU guest agent.
/// </summary>
public PveGuestOsInfo? GetGuestOsInfo(PveSession session, string node, int vmid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"nodes/{node}/qemu/{vmid}/agent/get-osinfo")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
var result = data?["result"];
return result?.ToObject<PveGuestOsInfo>();
}
/// <summary>
/// Retrieves filesystem information from the QEMU guest agent.
/// </summary>
public PveGuestFsInfo[] GetGuestFsInfo(PveSession session, string node, int vmid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"nodes/{node}/qemu/{vmid}/agent/get-fsinfo")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
var result = data?["result"];
return result?.ToObject<PveGuestFsInfo[]>() ?? Array.Empty<PveGuestFsInfo>();
}
/// <summary>
/// Reads a file from the guest filesystem via the QEMU guest agent.
/// </summary>
public string ReadGuestFile(PveSession session, string node, int vmid, string file)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(file)) throw new ArgumentNullException(nameof(file));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"nodes/{node}/qemu/{vmid}/agent/file-read?file={Uri.EscapeDataString(file)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?["content"]?.ToString() ?? string.Empty;
}
/// <summary>
/// Writes content to a file on the guest filesystem via the QEMU guest agent.
/// </summary>
public void WriteGuestFile(PveSession session, string node, int vmid, string file, string content)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(file)) throw new ArgumentNullException(nameof(file));
var formData = new Dictionary<string, string>
{
["file"] = file,
["content"] = content
};
using var client = new PveHttpClient(session);
client.PostAsync($"nodes/{node}/qemu/{vmid}/agent/file-write", formData)
.GetAwaiter().GetResult();
}
/// <summary>
/// Sets a user password inside the guest via the QEMU guest agent.
/// </summary>
public void SetGuestPassword(PveSession session, string node, int vmid, string username, string password, bool crypted = false)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(username)) throw new ArgumentNullException(nameof(username));
var formData = new Dictionary<string, string>
{
["username"] = username,
["password"] = password
};
if (crypted)
formData["crypted"] = "1";
using var client = new PveHttpClient(session);
client.PostAsync($"nodes/{node}/qemu/{vmid}/agent/set-user-password", formData)
.GetAwaiter().GetResult();
}
/// <summary>
/// Triggers an fstrim operation inside the guest via the QEMU guest agent.
/// </summary>
public void GuestFsTrim(PveSession session, string node, int vmid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
using var client = new PveHttpClient(session);
client.PostAsync($"nodes/{node}/qemu/{vmid}/agent/fstrim")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// OVA Upload
// -------------------------------------------------------------------------
@@ -0,0 +1,39 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Backup
{
/// <summary>
/// <para type="synopsis">Lists guests not covered by any backup job.</para>
/// <para type="description">
/// Returns information about VMs and containers that are not included in any
/// scheduled backup job. Useful for backup compliance auditing.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveBackupInfo")]
[OutputType(typeof(PSObject))]
public sealed class GetPveBackupInfoCmdlet : PveCmdletBase
{
protected override void ProcessRecord()
{
var session = GetSession();
var service = new BackupService();
WriteVerbose("Getting guests not covered by backup jobs...");
var items = service.GetNotBackedUp(session);
foreach (var item in items)
{
var pso = new PSObject();
if (item is Newtonsoft.Json.Linq.JObject jObj)
{
foreach (var prop in jObj.Properties())
{
pso.Properties.Add(new PSNoteProperty(prop.Name, prop.Value?.ToObject<object>()));
}
}
WriteObject(pso);
}
}
}
}
@@ -0,0 +1,50 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Cluster;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Cluster
{
/// <summary>
/// <para type="synopsis">Lists resources across the Proxmox VE cluster.</para>
/// <para type="description">
/// Returns cluster-wide resources (VMs, containers, nodes, storage, SDN).
/// Optionally filter by resource type or node name.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveClusterResource")]
[OutputType(typeof(PveClusterResource))]
public sealed class GetPveClusterResourceCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">Filter by resource type.</para>
/// </summary>
[Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by resource type (vm, lxc, node, storage, sdn).")]
[ValidateSet("vm", "lxc", "node", "storage", "sdn")]
public string? Type { get; set; }
/// <summary>
/// <para type="description">Filter results to a specific node name.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Filter results to a specific node name.")]
public string? Node { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new ClusterService();
WriteVerbose($"Getting cluster resources{(Type != null ? $" of type '{Type}'" : "")}...");
var resources = service.GetClusterResources(session, Type);
foreach (var resource in resources)
{
if (!string.IsNullOrEmpty(Node) &&
!string.Equals(resource.Node, Node, StringComparison.OrdinalIgnoreCase))
continue;
WriteObject(resource);
}
}
}
}
@@ -0,0 +1,39 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Containers;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Containers
{
/// <summary>
/// <para type="synopsis">Gets network interface information for an LXC container.</para>
/// <para type="description">
/// Retrieves network interface information for the specified LXC container
/// via the Proxmox VE API, including interface names, MAC addresses, and IP addresses.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveContainerInterface")]
[OutputType(typeof(PveContainerInterface))]
public sealed class GetPveContainerInterfaceCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>The container identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
WriteVerbose($"Getting network interfaces for container {VmId}...");
var service = new ContainerService();
var interfaces = service.GetInterfaces(session, Node, VmId);
foreach (var iface in interfaces)
WriteObject(iface);
}
}
}
@@ -0,0 +1,76 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Containers
{
/// <summary>
/// <para type="synopsis">Moves a container volume to a different storage.</para>
/// <para type="description">
/// Moves the specified volume on an LXC container to a different storage backend
/// via the Proxmox VE API. Optionally deletes the original volume after a successful move.
/// Use -Wait to block until the move task completes.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Move, "PveContainerVolume", SupportsShouldProcess = true)]
[OutputType(typeof(PveTask))]
public sealed class MovePveContainerVolumeCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the container resides.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The ID of the container whose volume should be moved.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">The volume to move (e.g., "rootfs", "mp0").</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The volume to move (e.g. rootfs, mp0).")]
public string Volume { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The target storage to move the volume to.</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The target storage identifier.")]
public string Storage { get; set; } = string.Empty;
/// <summary>
/// <para type="description">When specified, deletes the original volume after a successful move. Default is true.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Delete the original volume after move (default true).")]
public SwitchParameter Delete { get; set; } = true;
/// <summary>
/// <para type="description">When specified, waits for the move task to complete before returning.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")]
public SwitchParameter Wait { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"Volume '{Volume}' on container {VmId} (node '{Node}') to storage '{Storage}'", "Move-PveContainerVolume"))
return;
var session = GetSession();
var containerService = new ContainerService();
WriteVerbose($"Moving volume '{Volume}' on container {VmId} to storage '{Storage}'...");
var task = containerService.MoveVolume(session, Node, VmId, Volume, Storage, Delete.IsPresent);
if (Wait.IsPresent)
{
var taskService = new TaskService();
task = taskService.WaitForTask(session, Node, task.Upid, null, null, null);
}
WriteObject(task);
}
}
}
@@ -0,0 +1,48 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Containers
{
/// <summary>
/// <para type="synopsis">Converts an LXC container to a template.</para>
/// <para type="description">
/// Converts the specified LXC container into a template via the Proxmox VE API.
/// This operation is irreversible — once converted, the container cannot be started
/// and can only be used as a base for cloning new containers.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveContainerTemplate",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(PveTask))]
public sealed class NewPveContainerTemplateCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the container resides.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The ID of the container to convert to a template.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"Container {VmId} on node '{Node}'", "New-PveContainerTemplate"))
return;
var session = GetSession();
var containerService = new ContainerService();
WriteVerbose($"Converting container {VmId} to template on node '{Node}'...");
var task = containerService.ConvertToTemplate(session, Node, VmId);
WriteObject(task);
}
}
}
@@ -0,0 +1,61 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Containers
{
/// <summary>
/// <para type="synopsis">Resizes a disk attached to an LXC container.</para>
/// <para type="description">
/// Resizes the specified disk/volume on an LXC container via the Proxmox VE API.
/// Use an absolute size (e.g., "50G") to set a fixed size, or a relative size
/// prefixed with "+" (e.g., "+5G") to grow the disk by that amount.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Resize, "PveContainerDisk", SupportsShouldProcess = true)]
[OutputType(typeof(PveTask))]
public sealed class ResizePveContainerDiskCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the container resides.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The ID of the container whose disk should be resized.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">The disk/volume to resize (e.g., "rootfs", "mp0").</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The disk/volume to resize (e.g. rootfs, mp0).")]
public string Disk { get; set; } = string.Empty;
/// <summary>
/// <para type="description">
/// The new size for the disk. Use an absolute value (e.g., "50G") to set a specific size,
/// or a "+" prefix (e.g., "+5G") to grow the disk by the specified amount.
/// </para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "New disk size (e.g. 50G or +5G to grow).")]
public string Size { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"Disk '{Disk}' on container {VmId} (node '{Node}') to size '{Size}'", "Resize-PveContainerDisk"))
return;
var session = GetSession();
var containerService = new ContainerService();
WriteVerbose($"Resizing disk '{Disk}' on container {VmId}...");
var task = containerService.ResizeContainerDisk(session, Node, VmId, Disk, Size);
WriteObject(task);
}
}
}
@@ -0,0 +1,62 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Containers
{
/// <summary>
/// <para type="synopsis">Resumes a suspended LXC container on a Proxmox VE node.</para>
/// <para type="description">
/// Sends a resume command to the specified LXC container via the Proxmox VE API.
/// Use -Wait to block until the resume task completes.
/// </para>
/// </summary>
[Cmdlet(VerbsLifecycle.Resume, "PveContainer", SupportsShouldProcess = true)]
[OutputType(typeof(PveTask))]
public sealed class ResumePveContainerCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the container resides.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The ID of the container to resume.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">When specified, waits for the resume task to complete before returning.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")]
public SwitchParameter Wait { get; set; }
/// <summary>Maximum seconds to wait for the status transition when -Wait is specified. Default 60.</summary>
[Parameter(Mandatory = false, HelpMessage = "Timeout in seconds for -Wait (default 60).")]
[ValidateRange(1, 3600)]
public int Timeout { get; set; } = 60;
protected override void ProcessRecord()
{
if (!ShouldProcess($"Container {VmId} on node '{Node}'", "Resume-PveContainer"))
return;
var session = GetSession();
var containerService = new ContainerService();
WriteVerbose($"Resuming container {VmId} on node '{Node}'...");
var task = containerService.ResumeContainer(session, Node, VmId);
if (Wait.IsPresent)
{
var taskService = new TaskService();
task = taskService.WaitForTask(session, Node, task.Upid, null, null, null);
}
WriteObject(task);
}
}
}
@@ -0,0 +1,62 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Containers
{
/// <summary>
/// <para type="synopsis">Suspends an LXC container on a Proxmox VE node.</para>
/// <para type="description">
/// Sends a suspend command to the specified LXC container via the Proxmox VE API.
/// Use -Wait to block until the suspend task completes.
/// </para>
/// </summary>
[Cmdlet(VerbsLifecycle.Suspend, "PveContainer", SupportsShouldProcess = true)]
[OutputType(typeof(PveTask))]
public sealed class SuspendPveContainerCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the container resides.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The ID of the container to suspend.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">When specified, waits for the suspend task to complete before returning.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")]
public SwitchParameter Wait { get; set; }
/// <summary>Maximum seconds to wait for the status transition when -Wait is specified. Default 60.</summary>
[Parameter(Mandatory = false, HelpMessage = "Timeout in seconds for -Wait (default 60).")]
[ValidateRange(1, 3600)]
public int Timeout { get; set; } = 60;
protected override void ProcessRecord()
{
if (!ShouldProcess($"Container {VmId} on node '{Node}'", "Suspend-PveContainer"))
return;
var session = GetSession();
var containerService = new ContainerService();
WriteVerbose($"Suspending container {VmId} on node '{Node}'...");
var task = containerService.SuspendContainer(session, Node, VmId);
if (Wait.IsPresent)
{
var taskService = new TaskService();
task = taskService.WaitForTask(session, Node, task.Upid, null, null, null);
}
WriteObject(task);
}
}
}
@@ -0,0 +1,29 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Applies pending SDN configuration changes in Proxmox VE.</para>
/// <para type="description">
/// Applies all pending Software-Defined Networking configuration changes cluster-wide.
/// Run this after making changes with Set-PveSdnZone, Set-PveSdnVnet, etc.
/// </para>
/// </summary>
[Cmdlet(VerbsLifecycle.Invoke, "PveSdnApply", SupportsShouldProcess = true)]
public class InvokePveSdnApplyCmdlet : PveCmdletBase
{
protected override void ProcessRecord()
{
if (!ShouldProcess("SDN configuration", "Apply pending changes"))
return;
var session = GetSession();
RequireVersion(session, "SDN", 6, 2, 8, 0);
var service = new NetworkService();
WriteVerbose("Applying pending SDN configuration changes...");
service.ApplySdn(session);
}
}
}
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Updates an SDN controller configuration in Proxmox VE.</para>
/// <para type="description">
/// Modifies the specified Software-Defined Networking controller configuration.
/// Changes are pending until Invoke-PveSdnApply is called.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveSdnController", SupportsShouldProcess = true)]
public class SetPveSdnControllerCmdlet : PveCmdletBase
{
/// <summary>The controller identifier.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN controller identifier.")]
public string Controller { get; set; } = string.Empty;
/// <summary>The Autonomous System Number for BGP/EVPN.</summary>
[Parameter(Mandatory = false, HelpMessage = "The Autonomous System Number for BGP/EVPN.")]
public int? Asn { get; set; }
/// <summary>Comma-separated list of BGP peer addresses.</summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of BGP peer addresses.")]
public string? Peers { get; set; }
/// <summary>The node this controller is configured on.</summary>
[Parameter(Mandatory = false, HelpMessage = "The node this controller is configured on.")]
public string? Node { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Controller, "Set PVE SDN Controller"))
return;
var session = GetSession();
RequireVersion(session, "SDN", 6, 2, 8, 0);
var service = new NetworkService();
WriteVerbose($"Updating SDN controller '{Controller}'...");
var config = new Dictionary<string, string>();
if (MyInvocation.BoundParameters.ContainsKey("Asn"))
config["asn"] = Asn!.Value.ToString();
if (!string.IsNullOrEmpty(Peers)) config["peers"] = Peers!;
if (!string.IsNullOrEmpty(Node)) config["node"] = Node!;
service.UpdateSdnController(session, Controller, config);
}
}
}
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Updates an SDN DNS plugin configuration in Proxmox VE.</para>
/// <para type="description">
/// Modifies the specified Software-Defined Networking DNS plugin configuration.
/// Changes are pending until Invoke-PveSdnApply is called.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveSdnDns", SupportsShouldProcess = true)]
public class SetPveSdnDnsCmdlet : PveCmdletBase
{
/// <summary>The DNS plugin identifier.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN DNS plugin identifier.")]
public string Dns { get; set; } = string.Empty;
/// <summary>The DNS server URL.</summary>
[Parameter(Mandatory = false, HelpMessage = "The DNS server URL.")]
public string? Url { get; set; }
/// <summary>The TSIG key for DNS updates.</summary>
[Parameter(Mandatory = false, HelpMessage = "The TSIG key for DNS updates.")]
public string? Key { get; set; }
/// <summary>The IPv6 reverse DNS mask length.</summary>
[Parameter(Mandatory = false, HelpMessage = "The IPv6 reverse DNS mask length.")]
public int? ReverseMaskV6 { get; set; }
/// <summary>The TTL for DNS records.</summary>
[Parameter(Mandatory = false, HelpMessage = "The TTL for DNS records.")]
public int? Ttl { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Dns, "Set PVE SDN DNS"))
return;
var session = GetSession();
RequireVersion(session, "SDN", 6, 2, 8, 0);
var service = new NetworkService();
WriteVerbose($"Updating SDN DNS plugin '{Dns}'...");
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Url)) config["url"] = Url!;
if (!string.IsNullOrEmpty(Key)) config["key"] = Key!;
if (MyInvocation.BoundParameters.ContainsKey("ReverseMaskV6"))
config["reversemaskv6"] = ReverseMaskV6!.Value.ToString();
if (MyInvocation.BoundParameters.ContainsKey("Ttl"))
config["ttl"] = Ttl!.Value.ToString();
service.UpdateSdnDns(session, Dns, config);
}
}
}
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Updates an SDN IPAM plugin configuration in Proxmox VE.</para>
/// <para type="description">
/// Modifies the specified Software-Defined Networking IPAM plugin configuration.
/// Changes are pending until Invoke-PveSdnApply is called.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveSdnIpam", SupportsShouldProcess = true)]
public class SetPveSdnIpamCmdlet : PveCmdletBase
{
/// <summary>The IPAM plugin identifier.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN IPAM plugin identifier.")]
public string Ipam { get; set; } = string.Empty;
/// <summary>The IPAM server URL.</summary>
[Parameter(Mandatory = false, HelpMessage = "The IPAM server URL.")]
public string? Url { get; set; }
/// <summary>The API token for the IPAM server.</summary>
[Parameter(Mandatory = false, HelpMessage = "The API token for the IPAM server.")]
public string? Token { get; set; }
/// <summary>The section ID in the IPAM server.</summary>
[Parameter(Mandatory = false, HelpMessage = "The section ID in the IPAM server.")]
public int? Section { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Ipam, "Set PVE SDN IPAM"))
return;
var session = GetSession();
RequireVersion(session, "SDN", 6, 2, 8, 0);
var service = new NetworkService();
WriteVerbose($"Updating SDN IPAM plugin '{Ipam}'...");
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Url)) config["url"] = Url!;
if (!string.IsNullOrEmpty(Token)) config["token"] = Token!;
if (MyInvocation.BoundParameters.ContainsKey("Section"))
config["section"] = Section!.Value.ToString();
service.UpdateSdnIpam(session, Ipam, config);
}
}
}
@@ -0,0 +1,62 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Updates an SDN subnet configuration in Proxmox VE.</para>
/// <para type="description">
/// Modifies the specified Software-Defined Networking subnet configuration on a VNet.
/// Changes are pending until Invoke-PveSdnApply is called.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveSdnSubnet", SupportsShouldProcess = true)]
public class SetPveSdnSubnetCmdlet : PveCmdletBase
{
/// <summary>The VNet this subnet belongs to.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The SDN VNet name.")]
public string Vnet { get; set; } = string.Empty;
/// <summary>The subnet CIDR.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The subnet CIDR.")]
public string Subnet { get; set; } = string.Empty;
/// <summary>Gateway address for this subnet.</summary>
[Parameter(Mandatory = false, HelpMessage = "Gateway address for this subnet.")]
public string? Gateway { get; set; }
/// <summary>Enable SNAT for this subnet.</summary>
[Parameter(Mandatory = false, HelpMessage = "Enable SNAT for this subnet.")]
public SwitchParameter Snat { get; set; }
/// <summary>DNS zone prefix for automatic registration.</summary>
[Parameter(Mandatory = false, HelpMessage = "DNS zone prefix for automatic registration.")]
public string? DnsZonePrefix { get; set; }
/// <summary>DHCP range for this subnet.</summary>
[Parameter(Mandatory = false, HelpMessage = "DHCP range for this subnet.")]
public string? DhcpRange { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Subnet, "Set PVE SDN Subnet"))
return;
var session = GetSession();
RequireVersion(session, "SDN", 6, 2, 8, 0);
var service = new NetworkService();
WriteVerbose($"Updating SDN subnet '{Subnet}' on VNet '{Vnet}'...");
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Gateway)) config["gateway"] = Gateway!;
if (MyInvocation.BoundParameters.ContainsKey("Snat"))
config["snat"] = Snat.IsPresent ? "1" : "0";
if (!string.IsNullOrEmpty(DnsZonePrefix)) config["dnszoneprefix"] = DnsZonePrefix!;
if (!string.IsNullOrEmpty(DhcpRange)) config["dhcp-range"] = DhcpRange!;
service.UpdateSdnSubnet(session, Vnet, Subnet, config);
}
}
}
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Updates an SDN VNet configuration in Proxmox VE.</para>
/// <para type="description">
/// Modifies the specified Software-Defined Networking VNet configuration.
/// Changes are pending until Invoke-PveSdnApply is called.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveSdnVnet", SupportsShouldProcess = true)]
public class SetPveSdnVnetCmdlet : PveCmdletBase
{
/// <summary>The VNet identifier.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN VNet name.")]
public string Vnet { get; set; } = string.Empty;
/// <summary>An alias for the VNet.</summary>
[Parameter(Mandatory = false, HelpMessage = "An alias for the VNet.")]
public string? Alias { get; set; }
/// <summary>The zone this VNet belongs to.</summary>
[Parameter(Mandatory = false, HelpMessage = "The zone this VNet belongs to.")]
public string? Zone { get; set; }
/// <summary>VLAN tag for this VNet.</summary>
[Parameter(Mandatory = false, HelpMessage = "VLAN tag for this VNet.")]
public int? Tag { get; set; }
/// <summary>Enable VLAN-aware bridging.</summary>
[Parameter(Mandatory = false, HelpMessage = "Enable VLAN-aware bridging.")]
public SwitchParameter VlanAware { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Vnet, "Set PVE SDN VNet"))
return;
var session = GetSession();
RequireVersion(session, "SDN", 6, 2, 8, 0);
var service = new NetworkService();
WriteVerbose($"Updating SDN VNet '{Vnet}'...");
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Alias)) config["alias"] = Alias!;
if (!string.IsNullOrEmpty(Zone)) config["zone"] = Zone!;
if (MyInvocation.BoundParameters.ContainsKey("Tag"))
config["tag"] = Tag!.Value.ToString();
if (MyInvocation.BoundParameters.ContainsKey("VlanAware"))
config["vlanaware"] = VlanAware.IsPresent ? "1" : "0";
service.UpdateSdnVnet(session, Vnet, config);
}
}
}
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Updates an SDN zone configuration in Proxmox VE.</para>
/// <para type="description">
/// Modifies the specified Software-Defined Networking zone configuration.
/// Changes are pending until Invoke-PveSdnApply is called.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveSdnZone", SupportsShouldProcess = true)]
public class SetPveSdnZoneCmdlet : PveCmdletBase
{
/// <summary>The zone identifier.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN zone name.")]
public string Zone { get; set; } = string.Empty;
/// <summary>DNS server for automatic DNS registration.</summary>
[Parameter(Mandatory = false, HelpMessage = "DNS server for automatic registration.")]
public string? Dns { get; set; }
/// <summary>Reverse DNS server.</summary>
[Parameter(Mandatory = false, HelpMessage = "Reverse DNS server.")]
public string? Reversedns { get; set; }
/// <summary>DNS zone name for registration.</summary>
[Parameter(Mandatory = false, HelpMessage = "DNS zone name for registration.")]
public string? DnsZone { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Zone, "Set PVE SDN Zone"))
return;
var session = GetSession();
RequireVersion(session, "SDN", 6, 2, 8, 0);
var service = new NetworkService();
WriteVerbose($"Updating SDN zone '{Zone}'...");
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Dns)) config["dns"] = Dns!;
if (!string.IsNullOrEmpty(Reversedns)) config["reversedns"] = Reversedns!;
if (!string.IsNullOrEmpty(DnsZone)) config["dnszone"] = DnsZone!;
service.UpdateSdnZone(session, Zone, config);
}
}
}
@@ -0,0 +1,39 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Nodes
{
/// <summary>
/// <para type="synopsis">Returns the configuration of a Proxmox VE node.</para>
/// <para type="description">
/// Retrieves the node configuration from the /nodes/{node}/config endpoint,
/// including description, wakeonlan settings and other node-level properties.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveNodeConfig")]
[OutputType(typeof(PSObject))]
public class GetPveNodeConfigCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
protected override void ProcessRecord()
{
var session = GetSession();
var service = new NodeService();
WriteVerbose($"Getting configuration for node '{Node}'...");
var config = service.GetNodeConfig(session, Node);
var psObj = new PSObject();
foreach (var prop in config.Properties())
{
psObj.Properties.Add(new PSNoteProperty(prop.Name, prop.Value.ToString()));
}
WriteObject(psObj);
}
}
}
@@ -0,0 +1,39 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Nodes
{
/// <summary>
/// <para type="synopsis">Returns the DNS configuration of a Proxmox VE node.</para>
/// <para type="description">
/// Retrieves the DNS settings (search domain and nameservers) for the specified
/// node from the /nodes/{node}/dns endpoint.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveNodeDns")]
[OutputType(typeof(PSObject))]
public class GetPveNodeDnsCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
protected override void ProcessRecord()
{
var session = GetSession();
var service = new NodeService();
WriteVerbose($"Getting DNS configuration for node '{Node}'...");
var dns = service.GetNodeDns(session, Node);
var psObj = new PSObject();
foreach (var prop in dns.Properties())
{
psObj.Properties.Add(new PSNoteProperty(prop.Name, prop.Value.ToString()));
}
WriteObject(psObj);
}
}
}
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Nodes
{
/// <summary>
/// <para type="synopsis">Updates the configuration of a Proxmox VE node.</para>
/// <para type="description">
/// Modifies node-level configuration properties such as description and wake-on-LAN
/// settings via the /nodes/{node}/config endpoint.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveNodeConfig", SupportsShouldProcess = true)]
public class SetPveNodeConfigCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
/// <summary>Node description/comment.</summary>
[Parameter(Mandatory = false, HelpMessage = "Node description or comment.")]
public string? Description { get; set; }
/// <summary>Wake-on-LAN MAC address or configuration.</summary>
[Parameter(Mandatory = false, HelpMessage = "Wake-on-LAN configuration.")]
public string? Wakeonlan { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Node, "Set PVE Node Config"))
return;
var config = new Dictionary<string, string>();
if (Description != null) config["description"] = Description;
if (!string.IsNullOrEmpty(Wakeonlan)) config["wakeonlan"] = Wakeonlan!;
var session = GetSession();
var service = new NodeService();
WriteVerbose($"Updating configuration for node '{Node}'...");
service.SetNodeConfig(session, Node, config);
}
}
}
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Nodes
{
/// <summary>
/// <para type="synopsis">Updates the DNS configuration of a Proxmox VE node.</para>
/// <para type="description">
/// Modifies the DNS settings (search domain and nameservers) for the specified
/// node via the /nodes/{node}/dns endpoint.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveNodeDns", SupportsShouldProcess = true)]
public class SetPveNodeDnsCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
/// <summary>DNS search domain.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The DNS search domain.")]
[ValidateNotNullOrEmpty]
public string Search { get; set; } = string.Empty;
/// <summary>Primary DNS server.</summary>
[Parameter(Mandatory = false, HelpMessage = "Primary DNS server address.")]
public string? Dns1 { get; set; }
/// <summary>Secondary DNS server.</summary>
[Parameter(Mandatory = false, HelpMessage = "Secondary DNS server address.")]
public string? Dns2 { get; set; }
/// <summary>Tertiary DNS server.</summary>
[Parameter(Mandatory = false, HelpMessage = "Tertiary DNS server address.")]
public string? Dns3 { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Node, "Set PVE Node DNS"))
return;
var config = new Dictionary<string, string>
{
["search"] = Search
};
if (!string.IsNullOrEmpty(Dns1)) config["dns1"] = Dns1!;
if (!string.IsNullOrEmpty(Dns2)) config["dns2"] = Dns2!;
if (!string.IsNullOrEmpty(Dns3)) config["dns3"] = Dns3!;
var session = GetSession();
var service = new NodeService();
WriteVerbose($"Updating DNS configuration for node '{Node}'...");
service.SetNodeDns(session, Node, config);
}
}
}
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Nodes
{
/// <summary>
/// <para type="synopsis">Starts all VMs and containers on a Proxmox VE node.</para>
/// <para type="description">
/// Triggers the start-all operation on the specified node, which starts all VMs and
/// containers that are configured to auto-start. Optionally limit to specific VM IDs.
/// </para>
/// </summary>
[Cmdlet(VerbsLifecycle.Start, "PveNodeVms", SupportsShouldProcess = true)]
[OutputType(typeof(PveTask))]
public class StartPveNodeVmsCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
/// <summary>Comma-separated list of VM IDs to start. If omitted, all VMs are started.</summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of VM IDs to start.")]
public string? VmIds { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Node, "Start all VMs"))
return;
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(VmIds)) config["vms"] = VmIds!;
var session = GetSession();
var service = new NodeService();
WriteVerbose($"Starting all VMs on node '{Node}'...");
var task = service.StartAll(session, Node, config);
WriteObject(task);
}
}
}
@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Nodes
{
/// <summary>
/// <para type="synopsis">Stops all VMs and containers on a Proxmox VE node.</para>
/// <para type="description">
/// Triggers the stop-all operation on the specified node, which stops all running VMs
/// and containers. Optionally limit to specific VM IDs or force-stop.
/// </para>
/// </summary>
[Cmdlet(VerbsLifecycle.Stop, "PveNodeVms",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(PveTask))]
public class StopPveNodeVmsCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
/// <summary>Comma-separated list of VM IDs to stop. If omitted, all VMs are stopped.</summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of VM IDs to stop.")]
public string? VmIds { get; set; }
/// <summary>Force stop VMs (do not wait for graceful shutdown).</summary>
[Parameter(Mandatory = false, HelpMessage = "Force stop VMs without waiting for graceful shutdown.")]
public SwitchParameter ForceStop { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Node, "Stop all VMs"))
return;
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(VmIds)) config["vms"] = VmIds!;
if (ForceStop.IsPresent) config["force-stop"] = "1";
var session = GetSession();
var service = new NodeService();
WriteVerbose($"Stopping all VMs on node '{Node}'...");
var task = service.StopAll(session, Node, config);
WriteObject(task);
}
}
}
@@ -0,0 +1,46 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Cluster;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Pools
{
/// <summary>
/// <para type="synopsis">Lists Proxmox VE resource pools.</para>
/// <para type="description">
/// Returns resource pool configurations from the Proxmox VE cluster.
/// Optionally filter by pool ID to retrieve a specific pool with its members.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PvePool")]
[OutputType(typeof(PvePool))]
public sealed class GetPvePoolCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The pool ID to retrieve. When omitted, all pools are returned.</para>
/// </summary>
[Parameter(Mandatory = false, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The pool ID to retrieve.")]
public string? PoolId { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new PoolService();
if (!string.IsNullOrEmpty(PoolId))
{
WriteVerbose($"Getting pool '{PoolId}'...");
var pool = service.GetPool(session, PoolId!);
WriteObject(pool);
}
else
{
WriteVerbose("Getting all pools...");
var pools = service.GetPools(session);
foreach (var pool in pools)
{
WriteObject(pool);
}
}
}
}
}
@@ -0,0 +1,40 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Pools
{
/// <summary>
/// <para type="synopsis">Creates a new resource pool in Proxmox VE.</para>
/// <para type="description">
/// Creates a new resource pool in the Proxmox VE cluster with the specified ID
/// and optional comment.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PvePool", SupportsShouldProcess = true)]
public sealed class NewPvePoolCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The pool ID to create.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The pool ID to create.")]
public string PoolId { get; set; } = string.Empty;
/// <summary>
/// <para type="description">An optional comment or description for the pool.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "An optional comment or description for the pool.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"pool '{PoolId}'", "New-PvePool"))
return;
var session = GetSession();
var service = new PoolService();
WriteVerbose($"Creating pool '{PoolId}'...");
service.CreatePool(session, PoolId, Comment);
}
}
}
@@ -0,0 +1,36 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Pools
{
/// <summary>
/// <para type="synopsis">Removes a resource pool from Proxmox VE.</para>
/// <para type="description">
/// Deletes a resource pool from the Proxmox VE cluster configuration.
/// This operation is destructive and requires confirmation unless -Force is specified.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PvePool",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
public sealed class RemovePvePoolCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The pool ID to remove.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The pool ID to remove.")]
public string PoolId { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"pool '{PoolId}'", "Remove-PvePool"))
return;
var session = GetSession();
var service = new PoolService();
WriteVerbose($"Removing pool '{PoolId}'...");
service.RemovePool(session, PoolId);
}
}
}
@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Pools
{
/// <summary>
/// <para type="synopsis">Updates a resource pool in Proxmox VE.</para>
/// <para type="description">
/// Modifies an existing resource pool configuration. You can update the comment,
/// add members, or remove members.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PvePool", SupportsShouldProcess = true)]
public sealed class SetPvePoolCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The pool ID to update.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The pool ID to update.")]
public string PoolId { get; set; } = string.Empty;
/// <summary>
/// <para type="description">An optional comment or description for the pool.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "An optional comment or description for the pool.")]
public string? Comment { get; set; }
/// <summary>
/// <para type="description">Comma-separated list of members to add (e.g., "100,200,storage1").</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of members to add (e.g., '100,200,storage1').")]
public string? Members { get; set; }
/// <summary>
/// <para type="description">Comma-separated list of members to remove.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of members to remove.")]
public string? Delete { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"pool '{PoolId}'", "Set-PvePool"))
return;
var session = GetSession();
var service = new PoolService();
var config = new Dictionary<string, string>();
if (Comment != null)
config["comment"] = Comment;
if (!string.IsNullOrEmpty(Members))
config["members"] = Members!;
if (!string.IsNullOrEmpty(Delete))
config["delete"] = Delete!;
WriteVerbose($"Updating pool '{PoolId}'...");
service.UpdatePool(session, PoolId, config);
}
}
}
@@ -0,0 +1,41 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Storage;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Storage
{
/// <summary>
/// <para type="synopsis">Returns the status of a Proxmox VE storage on a node.</para>
/// <para type="description">
/// Retrieves capacity, usage and activation status for the specified storage
/// on the given node from the /nodes/{node}/storage/{storage}/status endpoint.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveStorageStatus")]
[OutputType(typeof(PveStorageStatus))]
public class GetPveStorageStatusCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
/// <summary>The storage identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The storage pool name.")]
[ValidateNotNullOrEmpty]
public string Storage { get; set; } = string.Empty;
protected override void ProcessRecord()
{
var session = GetSession();
var service = new StorageService();
WriteVerbose($"Getting status for storage '{Storage}' on node '{Node}'...");
var status = service.GetStorageStatus(session, Node, Storage);
status.Storage = Storage;
status.Node = Node;
WriteObject(status);
}
}
}
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Storage
{
/// <summary>
/// <para type="synopsis">Allocates a new disk image on a Proxmox VE storage.</para>
/// <para type="description">
/// Creates a new disk image with the specified filename, size and optional format
/// on the given storage and node. Returns the volume ID of the newly created disk.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveStorageDisk", SupportsShouldProcess = true)]
[OutputType(typeof(string))]
public class NewPveStorageDiskCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
/// <summary>The storage identifier.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage pool name.")]
[ValidateNotNullOrEmpty]
public string Storage { get; set; } = string.Empty;
/// <summary>The filename for the new disk (e.g. "vm-100-disk-1").</summary>
[Parameter(Mandatory = true, Position = 2, HelpMessage = "The filename for the new disk (e.g. vm-100-disk-1).")]
[ValidateNotNullOrEmpty]
public string Filename { get; set; } = string.Empty;
/// <summary>The size of the disk (e.g. "32G", "100M").</summary>
[Parameter(Mandatory = true, Position = 3, HelpMessage = "The size of the disk (e.g. 32G, 100M).")]
[ValidateNotNullOrEmpty]
public string Size { get; set; } = string.Empty;
/// <summary>The disk image format.</summary>
[Parameter(Mandatory = false, HelpMessage = "The disk image format (raw, qcow2, vmdk).")]
[ValidateSet("raw", "qcow2", "vmdk", IgnoreCase = true)]
public string? Format { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"{Filename} ({Size})", "New PVE Storage Disk"))
return;
var config = new Dictionary<string, string>
{
["filename"] = Filename,
["size"] = Size
};
if (!string.IsNullOrEmpty(Format))
config["format"] = Format!;
var session = GetSession();
var service = new StorageService();
WriteVerbose($"Allocating disk '{Filename}' ({Size}) on storage '{Storage}' on node '{Node}'...");
var task = service.AllocateDisk(session, Node, Storage, config);
WriteObject(task.Upid ?? string.Empty);
}
}
}
@@ -0,0 +1,45 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Storage
{
/// <summary>
/// <para type="synopsis">Removes a content volume from a Proxmox VE storage.</para>
/// <para type="description">
/// Deletes the specified volume (disk image, ISO, template, backup, etc.) from the
/// storage on the given node.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveStorageContent",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
public class RemovePveStorageContentCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
/// <summary>The storage identifier.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage pool name.")]
[ValidateNotNullOrEmpty]
public string Storage { get; set; } = string.Empty;
/// <summary>The volume identifier to remove (e.g. "local:iso/ubuntu.iso").</summary>
[Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true, HelpMessage = "The volume identifier to remove.")]
[ValidateNotNullOrEmpty]
public string Volume { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess(Volume, "Remove PVE Storage Content"))
return;
var session = GetSession();
var service = new StorageService();
WriteVerbose($"Removing volume '{Volume}' from storage '{Storage}' on node '{Node}'...");
service.RemoveContent(session, Node, Storage, Volume);
}
}
}
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Storage
{
/// <summary>
/// <para type="synopsis">Updates a Proxmox VE storage definition.</para>
/// <para type="description">
/// Modifies the configuration of an existing storage definition.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveStorage", SupportsShouldProcess = true)]
public sealed class SetPveStorageCmdlet : PveCmdletBase
{
/// <summary>The storage identifier to update.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The storage identifier.")]
public string Storage { get; set; } = string.Empty;
/// <summary>Comma-separated list of content types (e.g. 'images,iso,backup').</summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated content types (e.g. 'images,iso,backup').")]
public string? Content { get; set; }
/// <summary>Comma-separated list of nodes that can access this storage.</summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of nodes.")]
public string? Nodes { get; set; }
/// <summary>Whether the storage is enabled.</summary>
[Parameter(Mandatory = false, HelpMessage = "Whether the storage is enabled.")]
public SwitchParameter Enabled { get; set; }
/// <summary>Whether the storage is shared across nodes.</summary>
[Parameter(Mandatory = false, HelpMessage = "Whether the storage is shared.")]
public SwitchParameter Shared { get; set; }
/// <summary>Additional configuration as a hashtable for parameters not covered by named parameters.</summary>
[Parameter(Mandatory = false, HelpMessage = "Additional configuration parameters as a hashtable.")]
public System.Collections.Hashtable? AdditionalConfig { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"storage '{Storage}'", "Set"))
return;
var session = GetSession();
var service = new StorageService();
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Content)) config["content"] = Content!;
if (!string.IsNullOrEmpty(Nodes)) config["nodes"] = Nodes!;
if (MyInvocation.BoundParameters.ContainsKey("Enabled"))
config["disable"] = Enabled.IsPresent ? "0" : "1";
if (MyInvocation.BoundParameters.ContainsKey("Shared"))
config["shared"] = Shared.IsPresent ? "1" : "0";
if (AdditionalConfig != null)
{
foreach (var key in AdditionalConfig.Keys)
config[key.ToString()!] = AdditionalConfig[key]?.ToString() ?? string.Empty;
}
WriteVerbose($"Updating storage '{Storage}'...");
service.UpdateStorage(session, Storage, config);
}
}
}
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Storage
{
/// <summary>
/// <para type="synopsis">Updates properties of a content volume in Proxmox VE storage.</para>
/// <para type="description">
/// Modifies metadata (such as notes/description) of an existing volume in the specified
/// storage on the given node.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveStorageContent", SupportsShouldProcess = true)]
public class SetPveStorageContentCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
[ValidateNotNullOrEmpty]
public string Node { get; set; } = string.Empty;
/// <summary>The storage identifier.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage pool name.")]
[ValidateNotNullOrEmpty]
public string Storage { get; set; } = string.Empty;
/// <summary>The volume identifier to update.</summary>
[Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true, HelpMessage = "The volume identifier to update.")]
[ValidateNotNullOrEmpty]
public string Volume { get; set; } = string.Empty;
/// <summary>Notes/description to set on the volume.</summary>
[Parameter(Mandatory = false, HelpMessage = "Notes or description for the volume.")]
public string? Notes { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Volume, "Set PVE Storage Content"))
return;
var config = new Dictionary<string, string>();
if (Notes != null) config["notes"] = Notes;
var session = GetSession();
var service = new StorageService();
WriteVerbose($"Updating volume '{Volume}' on storage '{Storage}' on node '{Node}'...");
service.UpdateContent(session, Node, Storage, Volume, config);
}
}
}
@@ -0,0 +1,65 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Tasks
{
/// <summary>
/// <para type="synopsis">Lists tasks on a Proxmox VE node.</para>
/// <para type="description">
/// Returns a list of recent tasks on the specified node. Use optional parameters
/// to filter by VM ID, source, or task type. Unlike Get-PveTask which retrieves
/// a single task by UPID, this cmdlet lists multiple tasks.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveTaskList")]
[OutputType(typeof(PveTask))]
public sealed class GetPveTaskListCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node to list tasks from.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">Filter tasks by VM ID.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Filter tasks by VM ID.")]
public int? VmId { get; set; }
/// <summary>
/// <para type="description">Filter by task source: all or active.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Filter by task source: all or active.")]
[ValidateSet("all", "active")]
public string? Source { get; set; }
/// <summary>
/// <para type="description">Filter by task type (e.g., qmstart, vzdump).</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Filter by task type (e.g., qmstart, vzdump).")]
public string? TypeFilter { get; set; }
/// <summary>
/// <para type="description">Maximum number of tasks to return. Defaults to 50.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Maximum number of tasks to return. Defaults to 50.")]
[ValidateRange(1, 10000)]
public int Limit { get; set; } = 50;
protected override void ProcessRecord()
{
var session = GetSession();
var service = new TaskService();
WriteVerbose($"Getting tasks on node '{Node}' (limit={Limit})...");
var tasks = service.GetTasks(session, Node, VmId, Source, TypeFilter, Limit);
foreach (var task in tasks)
{
WriteObject(task);
}
}
}
}
@@ -0,0 +1,42 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Tasks
{
/// <summary>
/// <para type="synopsis">Stops a running task on a Proxmox VE node.</para>
/// <para type="description">
/// Cancels a running task identified by its UPID on the specified node.
/// This is a destructive operation and requires confirmation.
/// </para>
/// </summary>
[Cmdlet(VerbsLifecycle.Stop, "PveTask",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
public sealed class StopPveTaskCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the task is running.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The UPID of the task to stop.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The task UPID.")]
public string Upid { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"task '{Upid}' on node '{Node}'", "Stop-PveTask"))
return;
var session = GetSession();
var service = new TaskService();
WriteVerbose($"Stopping task '{Upid}' on node '{Node}'...");
service.StopTask(session, Node, Upid);
}
}
}
@@ -0,0 +1,42 @@
using System.Linq;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Users;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Lists Proxmox VE authentication domains/realms.</para>
/// <para type="description">
/// Returns all authentication domains (realms) from the Proxmox VE access management
/// system. Optionally filter by a specific realm name.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveDomain")]
[OutputType(typeof(PveDomain))]
public class GetPveDomainCmdlet : PveCmdletBase
{
/// <summary>Optional realm name to filter by.</summary>
[Parameter(Mandatory = false, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "Optional realm name to filter results.")]
public string? Realm { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new UserService();
WriteVerbose("Getting authentication domains...");
var domains = service.GetDomains(session);
if (!string.IsNullOrEmpty(Realm))
{
domains = domains.Where(d => d.Realm == Realm).ToArray();
}
foreach (var domain in domains)
{
WriteObject(domain);
}
}
}
}
@@ -0,0 +1,42 @@
using System.Linq;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Users;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Lists Proxmox VE groups.</para>
/// <para type="description">
/// Returns all groups from the Proxmox VE access management system. Optionally
/// filter by a specific group ID.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveGroup")]
[OutputType(typeof(PveGroup))]
public class GetPveGroupCmdlet : PveCmdletBase
{
/// <summary>Optional group ID to filter by.</summary>
[Parameter(Mandatory = false, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "Optional group ID to filter results.")]
public string? GroupId { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new UserService();
WriteVerbose("Getting groups...");
var groups = service.GetGroups(session);
if (!string.IsNullOrEmpty(GroupId))
{
groups = groups.Where(g => g.GroupId == GroupId).ToArray();
}
foreach (var group in groups)
{
WriteObject(group);
}
}
}
}
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Users;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Creates a new Proxmox VE authentication domain/realm.</para>
/// <para type="description">
/// Adds a new authentication domain (realm) to the Proxmox VE access management system.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveDomain", SupportsShouldProcess = true)]
[OutputType(typeof(PveDomain))]
public class NewPveDomainCmdlet : PveCmdletBase
{
/// <summary>The realm identifier.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The realm identifier.")]
[ValidateNotNullOrEmpty]
public string Realm { get; set; } = string.Empty;
/// <summary>The domain type.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The domain type (pam, pve, ad, ldap, openid).")]
[ValidateSet("pam", "pve", "ad", "ldap", "openid", IgnoreCase = true)]
public string Type { get; set; } = string.Empty;
/// <summary>Optional comment/description for the domain.</summary>
[Parameter(Mandatory = false, HelpMessage = "Comment or description for the domain.")]
public string? Comment { get; set; }
/// <summary>Set this realm as the default authentication domain.</summary>
[Parameter(Mandatory = false, HelpMessage = "Set as the default authentication domain.")]
public SwitchParameter Default { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Realm, "Create PVE Domain"))
return;
var config = new Dictionary<string, string>
{
["realm"] = Realm,
["type"] = Type
};
if (!string.IsNullOrEmpty(Comment)) config["comment"] = Comment!;
if (Default.IsPresent) config["default"] = "1";
var session = GetSession();
var service = new UserService();
WriteVerbose($"Creating domain '{Realm}' of type '{Type}'...");
service.CreateDomain(session, config);
WriteObject(new PveDomain
{
Realm = Realm,
Type = Type,
Comment = Comment,
Default = Default.IsPresent ? 1 : 0
});
}
}
}
@@ -0,0 +1,44 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Users;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Creates a new Proxmox VE group.</para>
/// <para type="description">
/// Adds a new group to the Proxmox VE access management system.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveGroup", SupportsShouldProcess = true)]
[OutputType(typeof(PveGroup))]
public class NewPveGroupCmdlet : PveCmdletBase
{
/// <summary>The group identifier.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The group identifier.")]
[ValidateNotNullOrEmpty]
public string GroupId { get; set; } = string.Empty;
/// <summary>Optional comment/description for the group.</summary>
[Parameter(Mandatory = false, HelpMessage = "Comment or description for the group.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(GroupId, "Create PVE Group"))
return;
var session = GetSession();
var service = new UserService();
WriteVerbose($"Creating group '{GroupId}'...");
service.CreateGroup(session, GroupId, Comment);
WriteObject(new PveGroup
{
GroupId = GroupId,
Comment = Comment
});
}
}
}
@@ -0,0 +1,35 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Removes a Proxmox VE authentication domain/realm.</para>
/// <para type="description">
/// Deletes the specified authentication domain (realm) from the Proxmox VE
/// access management system.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveDomain",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
public class RemovePveDomainCmdlet : PveCmdletBase
{
/// <summary>The realm identifier to remove.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The realm identifier to remove.")]
[ValidateNotNullOrEmpty]
public string Realm { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess(Realm, "Remove PVE Domain"))
return;
var session = GetSession();
var service = new UserService();
WriteVerbose($"Removing domain '{Realm}'...");
service.RemoveDomain(session, Realm);
}
}
}
@@ -0,0 +1,34 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Removes a Proxmox VE group.</para>
/// <para type="description">
/// Deletes the specified group from the Proxmox VE access management system.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveGroup",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
public class RemovePveGroupCmdlet : PveCmdletBase
{
/// <summary>The group identifier to remove.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The group identifier to remove.")]
[ValidateNotNullOrEmpty]
public string GroupId { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess(GroupId, "Remove PVE Group"))
return;
var session = GetSession();
var service = new UserService();
WriteVerbose($"Removing group '{GroupId}'...");
service.RemoveGroup(session, GroupId);
}
}
}
@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Updates a Proxmox VE API token configuration.</para>
/// <para type="description">
/// Modifies properties of an existing API token such as its comment,
/// expiration, or privilege separation setting.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveApiToken", SupportsShouldProcess = true)]
public sealed class SetPveApiTokenCmdlet : PveCmdletBase
{
/// <summary>The user ID that owns the token (e.g. 'user@realm').</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The user ID (e.g. 'user@realm').")]
public string UserId { get; set; } = string.Empty;
/// <summary>The token identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The token identifier.")]
public string TokenId { get; set; } = string.Empty;
/// <summary>Optional comment for the token.</summary>
[Parameter(Mandatory = false, HelpMessage = "Comment describing the token.")]
public string? Comment { get; set; }
/// <summary>Token expiration as a Unix epoch timestamp. Set to 0 for no expiration.</summary>
[Parameter(Mandatory = false, HelpMessage = "Expiration as Unix epoch (0 = no expiration).")]
public long? Expire { get; set; }
/// <summary>Whether the token uses privilege separation.</summary>
[Parameter(Mandatory = false, HelpMessage = "Whether the token uses privilege separation.")]
public SwitchParameter PrivilegeSeparation { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"API token '{UserId}!{TokenId}'", "Set"))
return;
var session = GetSession();
var service = new UserService();
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Comment)) config["comment"] = Comment!;
if (Expire.HasValue) config["expire"] = Expire.Value.ToString();
if (MyInvocation.BoundParameters.ContainsKey("PrivilegeSeparation"))
config["privsep"] = PrivilegeSeparation.IsPresent ? "1" : "0";
WriteVerbose($"Updating API token '{UserId}!{TokenId}'...");
service.UpdateApiToken(session, UserId, TokenId, config);
}
}
}
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Updates a Proxmox VE authentication domain/realm.</para>
/// <para type="description">
/// Modifies properties of an existing authentication domain (realm) in the
/// Proxmox VE access management system.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveDomain", SupportsShouldProcess = true)]
public class SetPveDomainCmdlet : PveCmdletBase
{
/// <summary>The realm identifier to update.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The realm identifier.")]
[ValidateNotNullOrEmpty]
public string Realm { get; set; } = string.Empty;
/// <summary>Updated comment/description for the domain.</summary>
[Parameter(Mandatory = false, HelpMessage = "Updated comment or description for the domain.")]
public string? Comment { get; set; }
/// <summary>Set this realm as the default authentication domain.</summary>
[Parameter(Mandatory = false, HelpMessage = "Set as the default authentication domain.")]
public SwitchParameter Default { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(Realm, "Set PVE Domain"))
return;
var config = new Dictionary<string, string>();
if (Comment != null) config["comment"] = Comment;
if (Default.IsPresent) config["default"] = "1";
var session = GetSession();
var service = new UserService();
WriteVerbose($"Updating domain '{Realm}'...");
service.UpdateDomain(session, Realm, config);
}
}
}
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Updates a Proxmox VE group.</para>
/// <para type="description">
/// Modifies properties of an existing group in the Proxmox VE access management system.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveGroup", SupportsShouldProcess = true)]
public class SetPveGroupCmdlet : PveCmdletBase
{
/// <summary>The group identifier to update.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The group identifier.")]
[ValidateNotNullOrEmpty]
public string GroupId { get; set; } = string.Empty;
/// <summary>Updated comment/description for the group.</summary>
[Parameter(Mandatory = false, HelpMessage = "Updated comment or description for the group.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess(GroupId, "Set PVE Group"))
return;
var config = new Dictionary<string, string>();
if (Comment != null) config["comment"] = Comment;
var session = GetSession();
var service = new UserService();
WriteVerbose($"Updating group '{GroupId}'...");
service.UpdateGroup(session, GroupId, config);
}
}
}
@@ -0,0 +1,49 @@
using System.Management.Automation;
using System.Runtime.InteropServices;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Changes a Proxmox VE user's password.</para>
/// <para type="description">
/// Updates the password for the specified user via the /access/password endpoint.
/// The password is accepted as a SecureString for safe handling in PowerShell.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PvePassword", SupportsShouldProcess = true)]
public class SetPvePasswordCmdlet : PveCmdletBase
{
/// <summary>The user identifier in "user@realm" format.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The user ID in user@realm format.")]
[ValidateNotNullOrEmpty]
public string UserId { get; set; } = string.Empty;
/// <summary>The new password for the user.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The new password for the user.")]
public System.Security.SecureString Password { get; set; } = new System.Security.SecureString();
protected override void ProcessRecord()
{
if (!ShouldProcess(UserId, "Set PVE Password"))
return;
var ptr = Marshal.SecureStringToGlobalAllocUnicode(Password);
string plainPassword;
try
{
plainPassword = Marshal.PtrToStringUni(ptr) ?? string.Empty;
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(ptr);
}
var session = GetSession();
var service = new UserService();
WriteVerbose($"Changing password for user '{UserId}'...");
service.ChangePassword(session, UserId, plainPassword);
}
}
}
@@ -0,0 +1,35 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Users
{
/// <summary>
/// <para type="synopsis">Updates a Proxmox VE role's privileges.</para>
/// <para type="description">
/// Modifies the privilege set for an existing custom role.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveRole", SupportsShouldProcess = true)]
public sealed class SetPveRoleCmdlet : PveCmdletBase
{
/// <summary>The role identifier to update.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The role identifier.")]
public string RoleId { get; set; } = string.Empty;
/// <summary>Comma-separated list of privileges to assign to the role.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "Comma-separated list of privileges (e.g. 'VM.Audit,VM.Console').")]
public string Privileges { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"role '{RoleId}'", "Set"))
return;
var session = GetSession();
var service = new UserService();
WriteVerbose($"Updating role '{RoleId}'...");
service.UpdateRole(session, RoleId, Privileges);
}
}
}
@@ -0,0 +1,40 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms
{
/// <summary>
/// <para type="synopsis">Gets filesystem information from the QEMU guest agent.</para>
/// <para type="description">
/// Queries the QEMU guest agent running inside the specified VM for its filesystem
/// information, including mount points, filesystem types, and usage statistics.
/// The guest agent must be installed and running inside the VM.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveVmGuestFsInfo")]
[OutputType(typeof(PveGuestFsInfo))]
public sealed class GetPveVmGuestFsInfoCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>The VM identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
WriteVerbose($"Getting guest filesystem info for VM {VmId}...");
var service = new VmService();
var fsInfos = service.GetGuestFsInfo(session, Node, VmId);
foreach (var fs in fsInfos)
WriteObject(fs);
}
}
}
@@ -0,0 +1,40 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms
{
/// <summary>
/// <para type="synopsis">Gets OS information from the QEMU guest agent.</para>
/// <para type="description">
/// Queries the QEMU guest agent running inside the specified VM for its operating system
/// information, including OS name, kernel version, and machine type.
/// The guest agent must be installed and running inside the VM.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveVmGuestOsInfo")]
[OutputType(typeof(PveGuestOsInfo))]
public sealed class GetPveVmGuestOsInfoCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>The VM identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
WriteVerbose($"Getting guest OS info for VM {VmId}...");
var service = new VmService();
var osInfo = service.GetGuestOsInfo(session, Node, VmId);
if (osInfo != null)
WriteObject(osInfo);
}
}
}
@@ -0,0 +1,38 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms
{
/// <summary>
/// <para type="synopsis">Invokes an fstrim operation inside the guest via the QEMU guest agent.</para>
/// <para type="description">
/// Triggers an fstrim (filesystem trim) operation inside the guest operating system
/// using the QEMU guest agent. This reclaims unused space on thin-provisioned storage.
/// The guest agent must be installed and running inside the VM.
/// </para>
/// </summary>
[Cmdlet(VerbsLifecycle.Invoke, "PveVmGuestFsTrim", SupportsShouldProcess = true)]
public sealed class InvokePveVmGuestFsTrimCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>The VM identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"VM {VmId} on node '{Node}'", "Invoke-PveVmGuestFsTrim"))
return;
var session = GetSession();
WriteVerbose($"Invoking fstrim on VM {VmId} via guest agent...");
var service = new VmService();
service.GuestFsTrim(session, Node, VmId);
}
}
}
@@ -0,0 +1,83 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms
{
/// <summary>
/// <para type="synopsis">Moves a VM disk to a different storage.</para>
/// <para type="description">
/// Moves the specified disk on a QEMU/KVM virtual machine to a different storage backend
/// via the Proxmox VE API. Optionally deletes the original disk after a successful move.
/// Use -Wait to block until the move task completes.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Move, "PveVmDisk", SupportsShouldProcess = true)]
[OutputType(typeof(PveTask))]
public sealed class MovePveVmDiskCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the VM resides.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The ID of the VM whose disk should be moved.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">The disk slot to move (e.g., "scsi0", "virtio0").</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The disk slot to move (e.g. scsi0, virtio0).")]
public string Disk { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The target storage to move the disk to.</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The target storage identifier.")]
public string Storage { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The target disk format.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Target disk format (raw, qcow2, vmdk).")]
[ValidateSet("raw", "qcow2", "vmdk")]
public string? Format { get; set; }
/// <summary>
/// <para type="description">When specified, deletes the original disk after a successful move. Default is true.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Delete the original disk after move (default true).")]
public SwitchParameter Delete { get; set; } = true;
/// <summary>
/// <para type="description">When specified, waits for the move task to complete before returning.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")]
public SwitchParameter Wait { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"Disk '{Disk}' on VM {VmId} (node '{Node}') to storage '{Storage}'", "Move-PveVmDisk"))
return;
var session = GetSession();
var vmService = new VmService();
WriteVerbose($"Moving disk '{Disk}' on VM {VmId} to storage '{Storage}'...");
var task = vmService.MoveDisk(session, Node, VmId, Disk, Storage, Format, Delete.IsPresent);
if (Wait.IsPresent)
{
var taskService = new TaskService();
task = taskService.WaitForTask(session, Node, task.Upid, null, null, null);
}
WriteObject(task);
}
}
}
@@ -0,0 +1,44 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms
{
/// <summary>
/// <para type="synopsis">Reads a file from the guest filesystem via the QEMU guest agent.</para>
/// <para type="description">
/// Reads the contents of a file inside the guest operating system using the QEMU guest agent.
/// The guest agent must be installed and running inside the VM.
/// Returns the file content as a string.
/// </para>
/// </summary>
[Cmdlet(VerbsCommunications.Read, "PveVmGuestFile")]
[OutputType(typeof(string))]
public sealed class ReadPveVmGuestFileCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>The VM identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">The path to the file inside the guest to read.</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The file path inside the guest to read.")]
public string File { get; set; } = string.Empty;
protected override void ProcessRecord()
{
var session = GetSession();
WriteVerbose($"Reading file '{File}' from VM {VmId} via guest agent...");
var service = new VmService();
var content = service.ReadGuestFile(session, Node, VmId, File);
WriteObject(content);
}
}
}
@@ -0,0 +1,55 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms
{
/// <summary>
/// <para type="synopsis">Removes (unlinks) disks from a QEMU/KVM virtual machine.</para>
/// <para type="description">
/// Detaches and optionally destroys one or more disks from a virtual machine
/// via the Proxmox VE API. This operation is destructive when -Force is used.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveVmDisk",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
public sealed class RemovePveVmDiskCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the VM resides.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The ID of the VM whose disks should be removed.</para>
/// </summary>
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">Comma-separated list of disk IDs to unlink (e.g., "scsi0,scsi1").</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "Comma-separated list of disk IDs to unlink (e.g. scsi0,scsi1).")]
public string IdList { get; set; } = string.Empty;
/// <summary>
/// <para type="description">When specified, forces removal of the disk images from storage.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Force removal of disk images from storage.")]
public SwitchParameter Force { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"Disks '{IdList}' on VM {VmId} (node '{Node}')", "Remove-PveVmDisk"))
return;
var session = GetSession();
var vmService = new VmService();
WriteVerbose($"Unlinking disks '{IdList}' from VM {VmId}...");
vmService.UnlinkDisk(session, Node, VmId, IdList, Force.IsPresent);
}
}
}
@@ -0,0 +1,56 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms
{
/// <summary>
/// <para type="synopsis">Sets a user password inside the guest via the QEMU guest agent.</para>
/// <para type="description">
/// Changes a user's password inside the guest operating system using the QEMU guest agent.
/// The guest agent must be installed and running inside the VM.
/// Use -Crypted if the password is already in a hashed/crypted format.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveVmGuestPassword", SupportsShouldProcess = true)]
public sealed class SetPveVmGuestPasswordCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>The VM identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">The username whose password should be changed.</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The username whose password to set.")]
public string Username { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The new password for the user.</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The new password.")]
public string Password { get; set; } = string.Empty;
/// <summary>
/// <para type="description">When specified, indicates the password is already in crypted/hashed format.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Indicates the password is already crypted/hashed.")]
public SwitchParameter Crypted { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"User '{Username}' on VM {VmId} (node '{Node}')", "Set-PveVmGuestPassword"))
return;
var session = GetSession();
WriteVerbose($"Setting password for user '{Username}' on VM {VmId} via guest agent...");
var service = new VmService();
service.SetGuestPassword(session, Node, VmId, Username, Password, Crypted.IsPresent);
}
}
}
@@ -0,0 +1,49 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Vms
{
/// <summary>
/// <para type="synopsis">Writes content to a file on the guest filesystem via the QEMU guest agent.</para>
/// <para type="description">
/// Writes the specified content to a file inside the guest operating system using the QEMU guest agent.
/// The guest agent must be installed and running inside the VM.
/// </para>
/// </summary>
[Cmdlet(VerbsCommunications.Write, "PveVmGuestFile", SupportsShouldProcess = true)]
public sealed class WritePveVmGuestFileCmdlet : PveCmdletBase
{
/// <summary>The Proxmox VE node name.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")]
public string Node { get; set; } = string.Empty;
/// <summary>The VM identifier.</summary>
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
/// <summary>
/// <para type="description">The path to the file inside the guest to write.</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The file path inside the guest to write.")]
public string File { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The content to write to the file.</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The content to write to the file.")]
public string Content { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"File '{File}' on VM {VmId} (node '{Node}')", "Write-PveVmGuestFile"))
return;
var session = GetSession();
WriteVerbose($"Writing to file '{File}' on VM {VmId} via guest agent...");
var service = new VmService();
service.WriteGuestFile(session, Node, VmId, File, Content);
}
}
}
+412
View File
@@ -944,5 +944,417 @@
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Cluster.PveClusterResource -->
<View>
<Name>PSProxmoxVE.Core.Models.Cluster.PveClusterResource</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Cluster.PveClusterResource</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Type</Label>
<Width>8</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Name</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Status</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Node</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>VmId</Label>
<Width>8</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Cpu</Label>
<Width>8</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>MaxMem</Label>
<Width>12</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Pool</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Status</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Node</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>VmId</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.Cpu -ne $null) { [math]::Round($_.Cpu * 100, 1).ToString() + '%' } else { 'N/A' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.MaxMem) { [math]::Round($_.MaxMem / 1GB, 1).ToString() + 'G' } else { 'N/A' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Pool</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Cluster.PvePool -->
<View>
<Name>PSProxmoxVE.Core.Models.Cluster.PvePool</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Cluster.PvePool</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>PoolId</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>PoolId</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Vms.PveGuestOsInfo -->
<View>
<Name>PSProxmoxVE.Core.Models.Vms.PveGuestOsInfo</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Vms.PveGuestOsInfo</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>KernelRelease</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Machine</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>KernelRelease</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Machine</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Vms.PveGuestFsInfo -->
<View>
<Name>PSProxmoxVE.Core.Models.Vms.PveGuestFsInfo</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Vms.PveGuestFsInfo</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>MountPoint</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Type</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>TotalBytes</Label>
<Width>15</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>UsedBytes</Label>
<Width>15</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>MountPoint</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.TotalBytes) { [math]::Round($_.TotalBytes / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.UsedBytes) { [math]::Round($_.UsedBytes / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Containers.PveContainerInterface -->
<View>
<Name>PSProxmoxVE.Core.Models.Containers.PveContainerInterface</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Containers.PveContainerInterface</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>HwAddr</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Inet</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Inet6</Label>
<Width>30</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>HwAddr</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Inet</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Inet6</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Storage.PveStorageStatus -->
<View>
<Name>PSProxmoxVE.Core.Models.Storage.PveStorageStatus</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Storage.PveStorageStatus</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Type</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Total</Label>
<Width>15</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Used</Label>
<Width>15</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Available</Label>
<Width>15</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Active</Label>
<Width>8</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.Total) { [math]::Round($_.Total / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.Used) { [math]::Round($_.Used / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.Available) { [math]::Round($_.Available / 1GB, 1).ToString() + ' GB' } else { 'N/A' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.Active) { 'Yes' } else { 'No' }</ScriptBlock>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Users.PveGroup -->
<View>
<Name>PSProxmoxVE.Core.Models.Users.PveGroup</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Users.PveGroup</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>GroupId</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Users</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>GroupId</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Users</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Users.PveDomain -->
<View>
<Name>PSProxmoxVE.Core.Models.Users.PveDomain</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Users.PveDomain</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Realm</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Type</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Default</Label>
<Width>8</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Realm</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.Default) { 'Yes' } else { 'No' }</ScriptBlock>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
+80 -1
View File
@@ -215,7 +215,86 @@
# SDN — Controller
'Get-PveSdnController',
'New-PveSdnController',
'Remove-PveSdnController'
'Remove-PveSdnController',
# SDN — Update / Apply
'Set-PveSdnZone',
'Set-PveSdnVnet',
'Set-PveSdnSubnet',
'Set-PveSdnController',
'Set-PveSdnIpam',
'Set-PveSdnDns',
'Invoke-PveSdnApply',
# Role / Storage / Token — Update
'Set-PveRole',
'Set-PveStorage',
'Set-PveApiToken',
# Cluster
'Get-PveClusterResource',
# Tasks
'Get-PveTaskList',
'Stop-PveTask',
# Pools
'Get-PvePool',
'New-PvePool',
'Set-PvePool',
'Remove-PvePool',
# Backup Compliance
'Get-PveBackupInfo',
# VM Disk Operations
'Move-PveVmDisk',
'Remove-PveVmDisk',
# VM Guest Agent Extensions
'Get-PveVmGuestOsInfo',
'Get-PveVmGuestFsInfo',
'Read-PveVmGuestFile',
'Write-PveVmGuestFile',
'Set-PveVmGuestPassword',
'Invoke-PveVmGuestFsTrim',
# Container Gaps
'Suspend-PveContainer',
'Resume-PveContainer',
'Resize-PveContainerDisk',
'New-PveContainerTemplate',
'Move-PveContainerVolume',
'Get-PveContainerInterface',
# Storage Content Management
'Get-PveStorageStatus',
'Remove-PveStorageContent',
'Set-PveStorageContent',
'New-PveStorageDisk',
# Node Operations
'Get-PveNodeConfig',
'Set-PveNodeConfig',
'Get-PveNodeDns',
'Set-PveNodeDns',
'Start-PveNodeVms',
'Stop-PveNodeVms',
# Access — Groups
'Get-PveGroup',
'New-PveGroup',
'Set-PveGroup',
'Remove-PveGroup',
# Access — Domains / Realms
'Get-PveDomain',
'New-PveDomain',
'Set-PveDomain',
'Remove-PveDomain',
# Access — Password
'Set-PvePassword'
)
# Variables to export from this module
@@ -0,0 +1,58 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for backup info cmdlets:
Get-PveBackupInfo.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @('Get-PveBackupInfo')) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Get-PveBackupInfo
# ---------------------------------------------------------------------------
Describe 'Get-PveBackupInfo' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveBackupInfo' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveBackupInfo'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveBackupInfo'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Parameter metadata' {
It 'Should have Session parameter' {
Skip-IfMissing 'Get-PveBackupInfo'
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveBackupInfo'
{ Get-PveBackupInfo -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,66 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for cluster cmdlets:
Get-PveClusterResource.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @('Get-PveClusterResource')) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Get-PveClusterResource
# ---------------------------------------------------------------------------
Describe 'Get-PveClusterResource' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveClusterResource' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveClusterResource'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveClusterResource'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Parameter metadata' {
It 'Should have optional Type parameter' {
Skip-IfMissing 'Get-PveClusterResource'
$script:Cmd.Parameters.ContainsKey('Type') | Should -BeTrue
}
It 'Should have optional Node parameter' {
Skip-IfMissing 'Get-PveClusterResource'
$script:Cmd.Parameters.ContainsKey('Node') | Should -BeTrue
}
It 'Should have Session parameter' {
Skip-IfMissing 'Get-PveClusterResource'
$script:Cmd.Parameters.ContainsKey('Session') | Should -BeTrue
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveClusterResource'
{ Get-PveClusterResource -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,342 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for container gap cmdlets:
Suspend-PveContainer, Resume-PveContainer, Resize-PveContainerDisk,
New-PveContainerTemplate, Move-PveContainerVolume, Get-PveContainerInterface.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @(
'Suspend-PveContainer', 'Resume-PveContainer', 'Resize-PveContainerDisk',
'New-PveContainerTemplate', 'Move-PveContainerVolume', 'Get-PveContainerInterface'
)) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Suspend-PveContainer
# ---------------------------------------------------------------------------
Describe 'Suspend-PveContainer' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Suspend-PveContainer' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Suspend-PveContainer'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Suspend-PveContainer'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Suspend-PveContainer'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Suspend-PveContainer'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Suspend-PveContainer'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Suspend-PveContainer'
{ Suspend-PveContainer -Node 'pve' -VmId 100 -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Resume-PveContainer
# ---------------------------------------------------------------------------
Describe 'Resume-PveContainer' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Resume-PveContainer' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Resume-PveContainer'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Resume-PveContainer'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Resume-PveContainer'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Resume-PveContainer'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Resume-PveContainer'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Resume-PveContainer'
{ Resume-PveContainer -Node 'pve' -VmId 100 -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Resize-PveContainerDisk
# ---------------------------------------------------------------------------
Describe 'Resize-PveContainerDisk' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Resize-PveContainerDisk' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Resize-PveContainerDisk'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Resize-PveContainerDisk'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Resize-PveContainerDisk'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Resize-PveContainerDisk'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Resize-PveContainerDisk'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Disk should be Mandatory' {
Skip-IfMissing 'Resize-PveContainerDisk'
$isMandatory = $script:Cmd.Parameters['Disk'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Size should be Mandatory' {
Skip-IfMissing 'Resize-PveContainerDisk'
$isMandatory = $script:Cmd.Parameters['Size'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Resize-PveContainerDisk'
{ Resize-PveContainerDisk -Node 'pve' -VmId 100 -Disk 'rootfs' -Size '+5G' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# New-PveContainerTemplate
# ---------------------------------------------------------------------------
Describe 'New-PveContainerTemplate' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'New-PveContainerTemplate' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'New-PveContainerTemplate'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess / ConfirmImpact' {
It 'Should support WhatIf' {
Skip-IfMissing 'New-PveContainerTemplate'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should declare ConfirmImpact High' {
Skip-IfMissing 'New-PveContainerTemplate'
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
[System.Management.Automation.CmdletAttribute], $false) |
Select-Object -First 1
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'New-PveContainerTemplate'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'New-PveContainerTemplate'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'New-PveContainerTemplate'
{ New-PveContainerTemplate -Node 'pve' -VmId 100 -Confirm:$false -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Move-PveContainerVolume
# ---------------------------------------------------------------------------
Describe 'Move-PveContainerVolume' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Move-PveContainerVolume' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Move-PveContainerVolume'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Move-PveContainerVolume'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Move-PveContainerVolume'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Move-PveContainerVolume'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Move-PveContainerVolume'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Volume should be Mandatory' {
Skip-IfMissing 'Move-PveContainerVolume'
$isMandatory = $script:Cmd.Parameters['Volume'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Storage should be Mandatory' {
Skip-IfMissing 'Move-PveContainerVolume'
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Move-PveContainerVolume'
{ Move-PveContainerVolume -Node 'pve' -VmId 100 -Volume 'rootfs' -Storage 'local-lvm' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Get-PveContainerInterface
# ---------------------------------------------------------------------------
Describe 'Get-PveContainerInterface' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveContainerInterface' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveContainerInterface'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveContainerInterface'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Get-PveContainerInterface'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Get-PveContainerInterface'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveContainerInterface'
{ Get-PveContainerInterface -Node 'pve' -VmId 100 -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,424 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for SDN Set cmdlets and related mutating cmdlets:
Set-PveSdnZone, Set-PveSdnVnet, Set-PveSdnSubnet, Set-PveSdnController,
Set-PveSdnIpam, Set-PveSdnDns, Invoke-PveSdnApply, Set-PveRole, Set-PveApiToken.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @(
'Set-PveSdnZone', 'Set-PveSdnVnet', 'Set-PveSdnSubnet', 'Set-PveSdnController',
'Set-PveSdnIpam', 'Set-PveSdnDns', 'Invoke-PveSdnApply', 'Set-PveRole', 'Set-PveApiToken'
)) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Set-PveSdnZone
# ---------------------------------------------------------------------------
Describe 'Set-PveSdnZone' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnZone' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveSdnZone'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveSdnZone'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveSdnZone'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Zone should be Mandatory' {
Skip-IfMissing 'Set-PveSdnZone'
$isMandatory = $script:Cmd.Parameters['Zone'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveSdnZone'
{ Set-PveSdnZone -Zone 'testzone' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveSdnVnet
# ---------------------------------------------------------------------------
Describe 'Set-PveSdnVnet' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnVnet' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveSdnVnet'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveSdnVnet'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveSdnVnet'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Vnet should be Mandatory' {
Skip-IfMissing 'Set-PveSdnVnet'
$isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveSdnVnet'
{ Set-PveSdnVnet -Vnet 'testvnet' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveSdnSubnet
# ---------------------------------------------------------------------------
Describe 'Set-PveSdnSubnet' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnSubnet' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveSdnSubnet'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveSdnSubnet'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveSdnSubnet'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Vnet should be Mandatory' {
Skip-IfMissing 'Set-PveSdnSubnet'
$isMandatory = $script:Cmd.Parameters['Vnet'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Subnet should be Mandatory' {
Skip-IfMissing 'Set-PveSdnSubnet'
$isMandatory = $script:Cmd.Parameters['Subnet'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveSdnSubnet'
{ Set-PveSdnSubnet -Vnet 'testvnet' -Subnet '10.0.0.0/24' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveSdnController
# ---------------------------------------------------------------------------
Describe 'Set-PveSdnController' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnController' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveSdnController'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveSdnController'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveSdnController'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Controller should be Mandatory' {
Skip-IfMissing 'Set-PveSdnController'
$isMandatory = $script:Cmd.Parameters['Controller'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveSdnController'
{ Set-PveSdnController -Controller 'testctrl' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveSdnIpam
# ---------------------------------------------------------------------------
Describe 'Set-PveSdnIpam' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnIpam' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveSdnIpam'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveSdnIpam'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveSdnIpam'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Ipam should be Mandatory' {
Skip-IfMissing 'Set-PveSdnIpam'
$isMandatory = $script:Cmd.Parameters['Ipam'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveSdnIpam'
{ Set-PveSdnIpam -Ipam 'testipam' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveSdnDns
# ---------------------------------------------------------------------------
Describe 'Set-PveSdnDns' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveSdnDns' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveSdnDns'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveSdnDns'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveSdnDns'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Dns should be Mandatory' {
Skip-IfMissing 'Set-PveSdnDns'
$isMandatory = $script:Cmd.Parameters['Dns'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveSdnDns'
{ Set-PveSdnDns -Dns 'testdns' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Invoke-PveSdnApply
# ---------------------------------------------------------------------------
Describe 'Invoke-PveSdnApply' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Invoke-PveSdnApply' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Invoke-PveSdnApply'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Invoke-PveSdnApply'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Invoke-PveSdnApply'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Invoke-PveSdnApply'
{ Invoke-PveSdnApply -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveRole
# ---------------------------------------------------------------------------
Describe 'Set-PveRole' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveRole' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveRole'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveRole'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveRole'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'RoleId should be Mandatory' {
Skip-IfMissing 'Set-PveRole'
$isMandatory = $script:Cmd.Parameters['RoleId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Privileges should be Mandatory' {
Skip-IfMissing 'Set-PveRole'
$isMandatory = $script:Cmd.Parameters['Privileges'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveRole'
{ Set-PveRole -RoleId 'testrole' -Privileges 'VM.Audit' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveApiToken
# ---------------------------------------------------------------------------
Describe 'Set-PveApiToken' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveApiToken' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveApiToken'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveApiToken'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveApiToken'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'UserId should be Mandatory' {
Skip-IfMissing 'Set-PveApiToken'
$isMandatory = $script:Cmd.Parameters['UserId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'TokenId should be Mandatory' {
Skip-IfMissing 'Set-PveApiToken'
$isMandatory = $script:Cmd.Parameters['TokenId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveApiToken'
{ Set-PveApiToken -UserId 'user@pam' -TokenId 'testtoken' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,281 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for node operation cmdlets:
Get-PveNodeConfig, Set-PveNodeConfig, Get-PveNodeDns, Set-PveNodeDns,
Start-PveNodeVms, Stop-PveNodeVms.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @(
'Get-PveNodeConfig', 'Set-PveNodeConfig', 'Get-PveNodeDns', 'Set-PveNodeDns',
'Start-PveNodeVms', 'Stop-PveNodeVms'
)) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Get-PveNodeConfig
# ---------------------------------------------------------------------------
Describe 'Get-PveNodeConfig' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveNodeConfig' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveNodeConfig'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveNodeConfig'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Get-PveNodeConfig'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveNodeConfig'
{ Get-PveNodeConfig -Node 'pve' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveNodeConfig
# ---------------------------------------------------------------------------
Describe 'Set-PveNodeConfig' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveNodeConfig' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveNodeConfig'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveNodeConfig'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveNodeConfig'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Set-PveNodeConfig'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveNodeConfig'
{ Set-PveNodeConfig -Node 'pve' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Get-PveNodeDns
# ---------------------------------------------------------------------------
Describe 'Get-PveNodeDns' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveNodeDns' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveNodeDns'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveNodeDns'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Get-PveNodeDns'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveNodeDns'
{ Get-PveNodeDns -Node 'pve' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveNodeDns
# ---------------------------------------------------------------------------
Describe 'Set-PveNodeDns' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveNodeDns' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveNodeDns'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveNodeDns'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveNodeDns'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Set-PveNodeDns'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Search should be Mandatory' {
Skip-IfMissing 'Set-PveNodeDns'
$isMandatory = $script:Cmd.Parameters['Search'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveNodeDns'
{ Set-PveNodeDns -Node 'pve' -Search 'example.com' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Start-PveNodeVms
# ---------------------------------------------------------------------------
Describe 'Start-PveNodeVms' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Start-PveNodeVms' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Start-PveNodeVms'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Start-PveNodeVms'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Start-PveNodeVms'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Start-PveNodeVms'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Start-PveNodeVms'
{ Start-PveNodeVms -Node 'pve' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Stop-PveNodeVms
# ---------------------------------------------------------------------------
Describe 'Stop-PveNodeVms' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Stop-PveNodeVms' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Stop-PveNodeVms'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess / ConfirmImpact' {
It 'Should support WhatIf' {
Skip-IfMissing 'Stop-PveNodeVms'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should declare ConfirmImpact High' {
Skip-IfMissing 'Stop-PveNodeVms'
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
[System.Management.Automation.CmdletAttribute], $false) |
Select-Object -First 1
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Stop-PveNodeVms'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Stop-PveNodeVms'
{ Stop-PveNodeVms -Node 'pve' -Confirm:$false -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,182 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for pool cmdlets:
Get-PvePool, New-PvePool, Set-PvePool, Remove-PvePool.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @('Get-PvePool', 'New-PvePool', 'Set-PvePool', 'Remove-PvePool')) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Get-PvePool
# ---------------------------------------------------------------------------
Describe 'Get-PvePool' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PvePool' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PvePool'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PvePool'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Parameter metadata' {
It 'Should have optional PoolId parameter' {
Skip-IfMissing 'Get-PvePool'
$script:Cmd.Parameters.ContainsKey('PoolId') | Should -BeTrue
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PvePool'
{ Get-PvePool -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# New-PvePool
# ---------------------------------------------------------------------------
Describe 'New-PvePool' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'New-PvePool' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'New-PvePool'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'New-PvePool'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'PoolId should be Mandatory' {
Skip-IfMissing 'New-PvePool'
$isMandatory = $script:Cmd.Parameters['PoolId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'New-PvePool'
{ New-PvePool -PoolId 'testpool' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PvePool
# ---------------------------------------------------------------------------
Describe 'Set-PvePool' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PvePool' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PvePool'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PvePool'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'PoolId should be Mandatory' {
Skip-IfMissing 'Set-PvePool'
$isMandatory = $script:Cmd.Parameters['PoolId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PvePool'
{ Set-PvePool -PoolId 'testpool' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Remove-PvePool
# ---------------------------------------------------------------------------
Describe 'Remove-PvePool' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Remove-PvePool' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Remove-PvePool'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess / ConfirmImpact' {
It 'Should support WhatIf' {
Skip-IfMissing 'Remove-PvePool'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should declare ConfirmImpact High' {
Skip-IfMissing 'Remove-PvePool'
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
[System.Management.Automation.CmdletAttribute], $false) |
Select-Object -First 1
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
}
}
Context 'Required parameters' {
It 'PoolId should be Mandatory' {
Skip-IfMissing 'Remove-PvePool'
$isMandatory = $script:Cmd.Parameters['PoolId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Remove-PvePool'
{ Remove-PvePool -PoolId 'testpool' -Confirm:$false -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,287 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for storage management cmdlets:
Set-PveStorage, Get-PveStorageStatus, Remove-PveStorageContent,
Set-PveStorageContent, New-PveStorageDisk.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @(
'Set-PveStorage', 'Get-PveStorageStatus', 'Remove-PveStorageContent',
'Set-PveStorageContent', 'New-PveStorageDisk'
)) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Set-PveStorage
# ---------------------------------------------------------------------------
Describe 'Set-PveStorage' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveStorage' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveStorage'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveStorage'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveStorage'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Storage should be Mandatory' {
Skip-IfMissing 'Set-PveStorage'
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveStorage'
{ Set-PveStorage -Storage 'local' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Get-PveStorageStatus
# ---------------------------------------------------------------------------
Describe 'Get-PveStorageStatus' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveStorageStatus' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveStorageStatus'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveStorageStatus'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Get-PveStorageStatus'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Storage should be Mandatory' {
Skip-IfMissing 'Get-PveStorageStatus'
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveStorageStatus'
{ Get-PveStorageStatus -Node 'pve' -Storage 'local' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Remove-PveStorageContent
# ---------------------------------------------------------------------------
Describe 'Remove-PveStorageContent' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Remove-PveStorageContent' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Remove-PveStorageContent'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess / ConfirmImpact' {
It 'Should support WhatIf' {
Skip-IfMissing 'Remove-PveStorageContent'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should declare ConfirmImpact High' {
Skip-IfMissing 'Remove-PveStorageContent'
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
[System.Management.Automation.CmdletAttribute], $false) |
Select-Object -First 1
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Remove-PveStorageContent'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Storage should be Mandatory' {
Skip-IfMissing 'Remove-PveStorageContent'
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Volume should be Mandatory' {
Skip-IfMissing 'Remove-PveStorageContent'
$isMandatory = $script:Cmd.Parameters['Volume'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Remove-PveStorageContent'
{ Remove-PveStorageContent -Node 'pve' -Storage 'local' -Volume 'local:iso/test.iso' -Confirm:$false -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveStorageContent
# ---------------------------------------------------------------------------
Describe 'Set-PveStorageContent' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveStorageContent' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveStorageContent'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveStorageContent'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveStorageContent'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Set-PveStorageContent'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Storage should be Mandatory' {
Skip-IfMissing 'Set-PveStorageContent'
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Volume should be Mandatory' {
Skip-IfMissing 'Set-PveStorageContent'
$isMandatory = $script:Cmd.Parameters['Volume'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveStorageContent'
{ Set-PveStorageContent -Node 'pve' -Storage 'local' -Volume 'local:iso/test.iso' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# New-PveStorageDisk
# ---------------------------------------------------------------------------
Describe 'New-PveStorageDisk' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'New-PveStorageDisk' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'New-PveStorageDisk'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'New-PveStorageDisk'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'New-PveStorageDisk'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'New-PveStorageDisk'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Storage should be Mandatory' {
Skip-IfMissing 'New-PveStorageDisk'
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Filename should be Mandatory' {
Skip-IfMissing 'New-PveStorageDisk'
$isMandatory = $script:Cmd.Parameters['Filename'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Size should be Mandatory' {
Skip-IfMissing 'New-PveStorageDisk'
$isMandatory = $script:Cmd.Parameters['Size'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'New-PveStorageDisk'
{ New-PveStorageDisk -Node 'pve' -Storage 'local' -Filename 'vm-100-disk-1' -Size '32G' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,127 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for task list cmdlets:
Get-PveTaskList, Stop-PveTask.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @('Get-PveTaskList', 'Stop-PveTask')) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Get-PveTaskList
# ---------------------------------------------------------------------------
Describe 'Get-PveTaskList' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveTaskList' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveTaskList'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveTaskList'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Get-PveTaskList'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Parameter metadata' {
It 'Should have optional VmId parameter' {
Skip-IfMissing 'Get-PveTaskList'
$script:Cmd.Parameters.ContainsKey('VmId') | Should -BeTrue
}
It 'Should have optional Limit parameter' {
Skip-IfMissing 'Get-PveTaskList'
$script:Cmd.Parameters.ContainsKey('Limit') | Should -BeTrue
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveTaskList'
{ Get-PveTaskList -Node 'pve' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Stop-PveTask
# ---------------------------------------------------------------------------
Describe 'Stop-PveTask' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Stop-PveTask' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Stop-PveTask'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Stop-PveTask'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess / ConfirmImpact' {
It 'Should support WhatIf' {
Skip-IfMissing 'Stop-PveTask'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should declare ConfirmImpact High' {
Skip-IfMissing 'Stop-PveTask'
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
[System.Management.Automation.CmdletAttribute], $false) |
Select-Object -First 1
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Stop-PveTask'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Upid should be Mandatory' {
Skip-IfMissing 'Stop-PveTask'
$isMandatory = $script:Cmd.Parameters['Upid'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Stop-PveTask'
{ Stop-PveTask -Node 'pve' -Upid 'UPID:pve:00000001:00000000:00000000:test::root@pam:' -Confirm:$false -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,400 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for access management cmdlets:
Get-PveGroup, New-PveGroup, Set-PveGroup, Remove-PveGroup,
Get-PveDomain, New-PveDomain, Set-PveDomain, Remove-PveDomain, Set-PvePassword.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @(
'Get-PveGroup', 'New-PveGroup', 'Set-PveGroup', 'Remove-PveGroup',
'Get-PveDomain', 'New-PveDomain', 'Set-PveDomain', 'Remove-PveDomain', 'Set-PvePassword'
)) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Get-PveGroup
# ---------------------------------------------------------------------------
Describe 'Get-PveGroup' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveGroup' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveGroup'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveGroup'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Parameter metadata' {
It 'Should have optional GroupId parameter' {
Skip-IfMissing 'Get-PveGroup'
$script:Cmd.Parameters.ContainsKey('GroupId') | Should -BeTrue
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveGroup'
{ Get-PveGroup -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# New-PveGroup
# ---------------------------------------------------------------------------
Describe 'New-PveGroup' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'New-PveGroup' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'New-PveGroup'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'New-PveGroup'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'GroupId should be Mandatory' {
Skip-IfMissing 'New-PveGroup'
$isMandatory = $script:Cmd.Parameters['GroupId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'New-PveGroup'
{ New-PveGroup -GroupId 'testgroup' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveGroup
# ---------------------------------------------------------------------------
Describe 'Set-PveGroup' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveGroup' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveGroup'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveGroup'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'GroupId should be Mandatory' {
Skip-IfMissing 'Set-PveGroup'
$isMandatory = $script:Cmd.Parameters['GroupId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveGroup'
{ Set-PveGroup -GroupId 'testgroup' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Remove-PveGroup
# ---------------------------------------------------------------------------
Describe 'Remove-PveGroup' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Remove-PveGroup' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Remove-PveGroup'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess / ConfirmImpact' {
It 'Should support WhatIf' {
Skip-IfMissing 'Remove-PveGroup'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should declare ConfirmImpact High' {
Skip-IfMissing 'Remove-PveGroup'
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
[System.Management.Automation.CmdletAttribute], $false) |
Select-Object -First 1
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
}
}
Context 'Required parameters' {
It 'GroupId should be Mandatory' {
Skip-IfMissing 'Remove-PveGroup'
$isMandatory = $script:Cmd.Parameters['GroupId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Remove-PveGroup'
{ Remove-PveGroup -GroupId 'testgroup' -Confirm:$false -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Get-PveDomain
# ---------------------------------------------------------------------------
Describe 'Get-PveDomain' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveDomain' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveDomain'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveDomain'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Parameter metadata' {
It 'Should have optional Realm parameter' {
Skip-IfMissing 'Get-PveDomain'
$script:Cmd.Parameters.ContainsKey('Realm') | Should -BeTrue
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveDomain'
{ Get-PveDomain -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# New-PveDomain
# ---------------------------------------------------------------------------
Describe 'New-PveDomain' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'New-PveDomain' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'New-PveDomain'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'New-PveDomain'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Realm should be Mandatory' {
Skip-IfMissing 'New-PveDomain'
$isMandatory = $script:Cmd.Parameters['Realm'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Type should be Mandatory' {
Skip-IfMissing 'New-PveDomain'
$isMandatory = $script:Cmd.Parameters['Type'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'New-PveDomain'
{ New-PveDomain -Realm 'testrealm' -Type 'pve' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveDomain
# ---------------------------------------------------------------------------
Describe 'Set-PveDomain' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveDomain' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveDomain'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveDomain'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Realm should be Mandatory' {
Skip-IfMissing 'Set-PveDomain'
$isMandatory = $script:Cmd.Parameters['Realm'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveDomain'
{ Set-PveDomain -Realm 'testrealm' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Remove-PveDomain
# ---------------------------------------------------------------------------
Describe 'Remove-PveDomain' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Remove-PveDomain' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Remove-PveDomain'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess / ConfirmImpact' {
It 'Should support WhatIf' {
Skip-IfMissing 'Remove-PveDomain'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should declare ConfirmImpact High' {
Skip-IfMissing 'Remove-PveDomain'
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
[System.Management.Automation.CmdletAttribute], $false) |
Select-Object -First 1
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
}
}
Context 'Required parameters' {
It 'Realm should be Mandatory' {
Skip-IfMissing 'Remove-PveDomain'
$isMandatory = $script:Cmd.Parameters['Realm'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Remove-PveDomain'
{ Remove-PveDomain -Realm 'testrealm' -Confirm:$false -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PvePassword
# ---------------------------------------------------------------------------
Describe 'Set-PvePassword' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PvePassword' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PvePassword'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PvePassword'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PvePassword'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'UserId should be Mandatory' {
Skip-IfMissing 'Set-PvePassword'
$isMandatory = $script:Cmd.Parameters['UserId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Password should be Mandatory' {
Skip-IfMissing 'Set-PvePassword'
$isMandatory = $script:Cmd.Parameters['Password'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PvePassword'
$secPw = ConvertTo-SecureString 'dummypassword' -AsPlainText -Force
{ Set-PvePassword -UserId 'root@pam' -Password $secPw -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,331 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for extended guest agent cmdlets:
Get-PveVmGuestOsInfo, Get-PveVmGuestFsInfo, Read-PveVmGuestFile,
Write-PveVmGuestFile, Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @(
'Get-PveVmGuestOsInfo', 'Get-PveVmGuestFsInfo', 'Read-PveVmGuestFile',
'Write-PveVmGuestFile', 'Set-PveVmGuestPassword', 'Invoke-PveVmGuestFsTrim'
)) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Get-PveVmGuestOsInfo
# ---------------------------------------------------------------------------
Describe 'Get-PveVmGuestOsInfo' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveVmGuestOsInfo' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveVmGuestOsInfo'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveVmGuestOsInfo'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Get-PveVmGuestOsInfo'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Get-PveVmGuestOsInfo'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveVmGuestOsInfo'
{ Get-PveVmGuestOsInfo -Node 'pve' -VmId 100 -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Get-PveVmGuestFsInfo
# ---------------------------------------------------------------------------
Describe 'Get-PveVmGuestFsInfo' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Get-PveVmGuestFsInfo' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Get-PveVmGuestFsInfo'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Get-PveVmGuestFsInfo'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Get-PveVmGuestFsInfo'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Get-PveVmGuestFsInfo'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Get-PveVmGuestFsInfo'
{ Get-PveVmGuestFsInfo -Node 'pve' -VmId 100 -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Read-PveVmGuestFile
# ---------------------------------------------------------------------------
Describe 'Read-PveVmGuestFile' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Read-PveVmGuestFile' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Read-PveVmGuestFile'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Read-PveVmGuestFile'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Read-PveVmGuestFile'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Read-PveVmGuestFile'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'File should be Mandatory' {
Skip-IfMissing 'Read-PveVmGuestFile'
$isMandatory = $script:Cmd.Parameters['File'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Read-PveVmGuestFile'
{ Read-PveVmGuestFile -Node 'pve' -VmId 100 -File '/etc/hostname' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Write-PveVmGuestFile
# ---------------------------------------------------------------------------
Describe 'Write-PveVmGuestFile' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Write-PveVmGuestFile' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Write-PveVmGuestFile'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Write-PveVmGuestFile'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Write-PveVmGuestFile'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Write-PveVmGuestFile'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Write-PveVmGuestFile'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'File should be Mandatory' {
Skip-IfMissing 'Write-PveVmGuestFile'
$isMandatory = $script:Cmd.Parameters['File'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Content should be Mandatory' {
Skip-IfMissing 'Write-PveVmGuestFile'
$isMandatory = $script:Cmd.Parameters['Content'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Write-PveVmGuestFile'
{ Write-PveVmGuestFile -Node 'pve' -VmId 100 -File '/tmp/test.txt' -Content 'hello' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Set-PveVmGuestPassword
# ---------------------------------------------------------------------------
Describe 'Set-PveVmGuestPassword' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Set-PveVmGuestPassword' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Set-PveVmGuestPassword'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Set-PveVmGuestPassword'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Set-PveVmGuestPassword'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Set-PveVmGuestPassword'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Set-PveVmGuestPassword'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Username should be Mandatory' {
Skip-IfMissing 'Set-PveVmGuestPassword'
$isMandatory = $script:Cmd.Parameters['Username'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Password should be Mandatory' {
Skip-IfMissing 'Set-PveVmGuestPassword'
$isMandatory = $script:Cmd.Parameters['Password'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Set-PveVmGuestPassword'
{ Set-PveVmGuestPassword -Node 'pve' -VmId 100 -Username 'root' -Password 'dummy' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Invoke-PveVmGuestFsTrim
# ---------------------------------------------------------------------------
Describe 'Invoke-PveVmGuestFsTrim' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Invoke-PveVmGuestFsTrim' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Invoke-PveVmGuestFsTrim'
{ Invoke-PveVmGuestFsTrim -Node 'pve' -VmId 100 -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
@@ -0,0 +1,143 @@
#Requires -Module Pester
<#
.SYNOPSIS
Pester 5 tests for VM disk operation cmdlets:
Move-PveVmDisk, Remove-PveVmDisk.
All tests are fully offline — no live Proxmox VE target is required.
If a cmdlet is not yet compiled the test is marked Skipped.
#>
BeforeAll {
. $PSScriptRoot/../_TestHelper.ps1
$script:Availability = @{}
foreach ($name in @('Move-PveVmDisk', 'Remove-PveVmDisk')) {
$script:Availability[$name] = $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}
function Skip-IfMissing([string]$Name) {
if (-not $script:Availability[$Name]) {
Set-ItResult -Skipped -Because "$Name is not yet implemented in this build"
}
}
}
# ---------------------------------------------------------------------------
# Move-PveVmDisk
# ---------------------------------------------------------------------------
Describe 'Move-PveVmDisk' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Move-PveVmDisk' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Move-PveVmDisk'
$script:Cmd | Should -Not -BeNullOrEmpty
}
It 'Should be a CmdletInfo (binary cmdlet)' {
Skip-IfMissing 'Move-PveVmDisk'
$script:Cmd.CommandType | Should -Be 'Cmdlet'
}
}
Context 'ShouldProcess support' {
It 'Should support WhatIf' {
Skip-IfMissing 'Move-PveVmDisk'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Move-PveVmDisk'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Move-PveVmDisk'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Disk should be Mandatory' {
Skip-IfMissing 'Move-PveVmDisk'
$isMandatory = $script:Cmd.Parameters['Disk'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'Storage should be Mandatory' {
Skip-IfMissing 'Move-PveVmDisk'
$isMandatory = $script:Cmd.Parameters['Storage'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Move-PveVmDisk'
{ Move-PveVmDisk -Node 'pve' -VmId 100 -Disk 'scsi0' -Storage 'local-lvm' -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}
# ---------------------------------------------------------------------------
# Remove-PveVmDisk
# ---------------------------------------------------------------------------
Describe 'Remove-PveVmDisk' -Tag 'Unit' {
BeforeAll { $script:Cmd = Get-Command 'Remove-PveVmDisk' -ErrorAction SilentlyContinue }
Context 'Command existence' {
It 'Should be available after module import' {
Skip-IfMissing 'Remove-PveVmDisk'
$script:Cmd | Should -Not -BeNullOrEmpty
}
}
Context 'ShouldProcess / ConfirmImpact' {
It 'Should support WhatIf' {
Skip-IfMissing 'Remove-PveVmDisk'
$script:Cmd.Parameters.ContainsKey('WhatIf') | Should -BeTrue
}
It 'Should declare ConfirmImpact High' {
Skip-IfMissing 'Remove-PveVmDisk'
$attr = $script:Cmd.ImplementingType.GetCustomAttributes(
[System.Management.Automation.CmdletAttribute], $false) |
Select-Object -First 1
$attr.ConfirmImpact | Should -Be ([System.Management.Automation.ConfirmImpact]::High)
}
}
Context 'Required parameters' {
It 'Node should be Mandatory' {
Skip-IfMissing 'Remove-PveVmDisk'
$isMandatory = $script:Cmd.Parameters['Node'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'VmId should be Mandatory' {
Skip-IfMissing 'Remove-PveVmDisk'
$isMandatory = $script:Cmd.Parameters['VmId'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
It 'IdList should be Mandatory' {
Skip-IfMissing 'Remove-PveVmDisk'
$isMandatory = $script:Cmd.Parameters['IdList'].ParameterSets.Values |
Where-Object { $_.IsMandatory }
$isMandatory | Should -Not -BeNullOrEmpty
}
}
Context 'Without active session' {
It 'Should throw when no session is active' {
Skip-IfMissing 'Remove-PveVmDisk'
{ Remove-PveVmDisk -Node 'pve' -VmId 100 -IdList 'scsi0' -Confirm:$false -ErrorAction Stop } |
Should -Throw '*No active Proxmox VE session*'
}
}
}