mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-08-11 23:06:53 +00:00
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:
@@ -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
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user