feat: add firewall, backup, and SDN IPAM/DNS/controller cmdlets

Add 35 new cmdlets bringing the total to 118:
- Firewall (21): rules, groups, aliases, IP sets, options at
  cluster/node/VM/container levels
- Backup (5): ad-hoc vzdump and scheduled backup job CRUD
- SDN IPAM/DNS/Controller (9): plugin management for SDN subsystem

Also includes:
- Fix: Remove-PveRole now has ConfirmImpact.High
- Fix: URL-encode snapshot names in API paths
- Refactor: extract auth header strings to constants in PveHttpClient
- Add PSGallery version badge to README
- Full test coverage: 11 JSON fixtures, xUnit model tests, Pester
  unit tests, and integration tests for firewall/backup/OVA import
- Updated manifest, format file, CHANGELOG, README, API coverage docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-03-21 07:51:49 -05:00
parent 8d5f33f592
commit a72642d203
81 changed files with 7636 additions and 564 deletions
+7 -3
View File
@@ -31,6 +31,10 @@ namespace PSProxmoxVE.Core.Client
private readonly HttpClient _httpClient;
private bool _disposed;
private const string ApiTokenPrefix = "PVEAPIToken=";
private const string AuthCookieName = "PVEAuthCookie=";
private const string CsrfHeaderName = "CSRFPreventionToken";
/// <summary>
/// Creates an HTTP client authenticated with the specified PVE session.
/// </summary>
@@ -313,14 +317,14 @@ namespace PSProxmoxVE.Core.Client
if (_session.AuthMode == PveAuthMode.ApiToken)
{
request.Headers.TryAddWithoutValidation("Authorization", $"PVEAPIToken={_session.ApiToken}");
request.Headers.TryAddWithoutValidation("Authorization", $"{ApiTokenPrefix}{_session.ApiToken}");
}
else
{
// Ticket auth
request.Headers.Add("Cookie", $"PVEAuthCookie={_session.Ticket}");
request.Headers.Add("Cookie", $"{AuthCookieName}{_session.Ticket}");
if (mutating && !string.IsNullOrEmpty(_session.CsrfToken))
request.Headers.Add("CSRFPreventionToken", _session.CsrfToken);
request.Headers.Add(CsrfHeaderName, _session.CsrfToken);
}
return request;
@@ -0,0 +1,138 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Backup;
/// <summary>
/// Represents a Proxmox VE scheduled backup job as returned by
/// the /cluster/backup endpoint.
/// </summary>
public class PveBackupJob
{
/// <summary>
/// The backup job identifier.
/// </summary>
[JsonPropertyName("id")]
[JsonProperty("id")]
public string Id { get; set; } = string.Empty;
/// <summary>
/// The job type (typically "vzdump").
/// </summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string? Type { get; set; }
/// <summary>
/// Whether the job is enabled (1) or disabled (0).
/// </summary>
[JsonPropertyName("enabled")]
[JsonProperty("enabled")]
public int? Enabled { get; set; }
/// <summary>
/// The cron-style schedule (e.g. "0 2 * * *").
/// </summary>
[JsonPropertyName("schedule")]
[JsonProperty("schedule")]
public string? Schedule { get; set; }
/// <summary>
/// The target storage for backup files.
/// </summary>
[JsonPropertyName("storage")]
[JsonProperty("storage")]
public string? Storage { get; set; }
/// <summary>
/// Backup mode: "snapshot", "suspend", or "stop".
/// </summary>
[JsonPropertyName("mode")]
[JsonProperty("mode")]
public string? Mode { get; set; }
/// <summary>
/// Comma-separated list of VM/container IDs to back up, or empty for all.
/// </summary>
[JsonPropertyName("vmid")]
[JsonProperty("vmid")]
public string? VmId { get; set; }
/// <summary>
/// Whether to back up all VMs/containers (1 = all).
/// </summary>
[JsonPropertyName("all")]
[JsonProperty("all")]
public int? All { get; set; }
/// <summary>
/// Compression algorithm (e.g. "zstd", "lzo", "gzip").
/// </summary>
[JsonPropertyName("compress")]
[JsonProperty("compress")]
public string? Compress { get; set; }
/// <summary>
/// Deprecated — maximum number of backup files to keep. Use prune-backups instead.
/// </summary>
[JsonPropertyName("maxfiles")]
[JsonProperty("maxfiles")]
public int? MaxFiles { get; set; }
/// <summary>
/// Retention policy string (e.g. "keep-daily=7,keep-weekly=4").
/// </summary>
[JsonPropertyName("prune-backups")]
[JsonProperty("prune-backups")]
public string? PruneBackups { get; set; }
/// <summary>
/// Template for backup notes.
/// </summary>
[JsonPropertyName("notes-template")]
[JsonProperty("notes-template")]
public string? NotesTemplate { get; set; }
/// <summary>
/// Optional comment describing the backup job.
/// </summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <summary>
/// Mail notification setting (e.g. "always", "failure").
/// </summary>
[JsonPropertyName("mailnotification")]
[JsonProperty("mailnotification")]
public string? MailNotification { get; set; }
/// <summary>
/// Email address to send notifications to.
/// </summary>
[JsonPropertyName("mailto")]
[JsonProperty("mailto")]
public string? MailTo { get; set; }
/// <summary>
/// Specific node to run the backup on (empty = any node).
/// </summary>
[JsonPropertyName("node")]
[JsonProperty("node")]
public string? Node { get; set; }
/// <summary>
/// Comma-separated list of excluded VM/container IDs.
/// </summary>
[JsonPropertyName("exclude")]
[JsonProperty("exclude")]
public string? Exclude { get; set; }
/// <inheritdoc />
public override string ToString()
{
var state = Enabled == 1 ? "Enabled" : "Disabled";
return $"Backup Job: {Id} | {state} | Schedule: {Schedule ?? "N/A"} | "
+ $"Storage: {Storage ?? "N/A"} | Mode: {Mode ?? "N/A"}";
}
}
@@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Firewall;
/// <summary>
/// Represents a Proxmox VE firewall IP alias as returned by
/// the firewall/aliases endpoints.
/// </summary>
public class PveFirewallAlias
{
/// <summary>
/// The alias name.
/// </summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// The CIDR network or IP address (e.g. "10.0.0.0/8" or "192.168.1.1").
/// </summary>
[JsonPropertyName("cidr")]
[JsonProperty("cidr")]
public string Cidr { get; set; } = string.Empty;
/// <summary>
/// Optional comment describing the alias.
/// </summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"Alias: {Name} → {Cidr}";
}
}
@@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Firewall;
/// <summary>
/// Represents a Proxmox VE firewall security group as returned by
/// the /cluster/firewall/groups endpoint.
/// </summary>
public class PveFirewallGroup
{
/// <summary>
/// The security group name.
/// </summary>
[JsonPropertyName("group")]
[JsonProperty("group")]
public string Group { get; set; } = string.Empty;
/// <summary>
/// Optional comment describing the group.
/// </summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <summary>
/// Configuration digest for detecting concurrent modifications.
/// </summary>
[JsonPropertyName("digest")]
[JsonProperty("digest")]
public string? Digest { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"Firewall Group: {Group} | Comment: {Comment ?? "N/A"}";
}
}
@@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Firewall;
/// <summary>
/// Represents a Proxmox VE firewall IP set as returned by
/// the firewall/ipset endpoints.
/// </summary>
public class PveFirewallIpSet
{
/// <summary>
/// The IP set name.
/// </summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Optional comment describing the IP set.
/// </summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <summary>
/// Configuration digest for detecting concurrent modifications.
/// </summary>
[JsonPropertyName("digest")]
[JsonProperty("digest")]
public string? Digest { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"IP Set: {Name} | Comment: {Comment ?? "N/A"}";
}
}
@@ -0,0 +1,39 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Firewall;
/// <summary>
/// Represents an entry within a Proxmox VE firewall IP set as returned by
/// the firewall/ipset/{name} endpoints.
/// </summary>
public class PveFirewallIpSetEntry
{
/// <summary>
/// The CIDR network or IP address (e.g. "192.168.1.0/24" or "10.0.0.1").
/// </summary>
[JsonPropertyName("cidr")]
[JsonProperty("cidr")]
public string Cidr { get; set; } = string.Empty;
/// <summary>
/// Whether this is an exclusion entry (1 = exclude from set).
/// </summary>
[JsonPropertyName("nomatch")]
[JsonProperty("nomatch")]
public int? NoMatch { get; set; }
/// <summary>
/// Optional comment describing the entry.
/// </summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <inheritdoc />
public override string ToString()
{
var prefix = NoMatch == 1 ? "!" : "";
return $"{prefix}{Cidr}";
}
}
@@ -0,0 +1,88 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Firewall;
/// <summary>
/// Represents Proxmox VE firewall options as returned by
/// the firewall/options endpoints at cluster, node, VM, or container level.
/// </summary>
public class PveFirewallOptions
{
/// <summary>
/// Whether the firewall is enabled (1) or disabled (0).
/// </summary>
[JsonPropertyName("enable")]
[JsonProperty("enable")]
public int? Enable { get; set; }
/// <summary>
/// Default policy for incoming traffic (ACCEPT, DROP, REJECT).
/// </summary>
[JsonPropertyName("policy_in")]
[JsonProperty("policy_in")]
public string? PolicyIn { get; set; }
/// <summary>
/// Default policy for outgoing traffic (ACCEPT, DROP, REJECT).
/// </summary>
[JsonPropertyName("policy_out")]
[JsonProperty("policy_out")]
public string? PolicyOut { get; set; }
/// <summary>
/// Log level for incoming traffic (nolog, emerg, alert, crit, err, warning, notice, info, debug).
/// </summary>
[JsonPropertyName("log_level_in")]
[JsonProperty("log_level_in")]
public string? LogLevelIn { get; set; }
/// <summary>
/// Log level for outgoing traffic.
/// </summary>
[JsonPropertyName("log_level_out")]
[JsonProperty("log_level_out")]
public string? LogLevelOut { get; set; }
/// <summary>
/// Whether to allow DHCP traffic (VM/container level).
/// </summary>
[JsonPropertyName("dhcp")]
[JsonProperty("dhcp")]
public int? Dhcp { get; set; }
/// <summary>
/// Whether to allow NDP (IPv6 Neighbor Discovery Protocol).
/// </summary>
[JsonPropertyName("ndp")]
[JsonProperty("ndp")]
public int? Ndp { get; set; }
/// <summary>
/// Whether to allow Router Advertisement.
/// </summary>
[JsonPropertyName("radv")]
[JsonProperty("radv")]
public int? Radv { get; set; }
/// <summary>
/// Whether to enable MAC address filter (VM/container level).
/// </summary>
[JsonPropertyName("macfilter")]
[JsonProperty("macfilter")]
public int? MacFilter { get; set; }
/// <summary>
/// Whether to enable IP filter (VM/container level).
/// </summary>
[JsonPropertyName("ipfilter")]
[JsonProperty("ipfilter")]
public int? IpFilter { get; set; }
/// <inheritdoc />
public override string ToString()
{
var state = Enable == 1 ? "Enabled" : "Disabled";
return $"Firewall: {state} | In: {PolicyIn ?? "N/A"} | Out: {PolicyOut ?? "N/A"}";
}
}
@@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Firewall;
/// <summary>
/// Represents a Proxmox VE firewall reference item (alias, ipset, etc.)
/// as returned by the firewall/refs endpoints.
/// </summary>
public class PveFirewallRef
{
/// <summary>
/// The reference type (e.g. "alias", "ipset").
/// </summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// The reference name.
/// </summary>
[JsonPropertyName("name")]
[JsonProperty("name")]
public string Name { get; set; } = string.Empty;
/// <summary>
/// Optional comment.
/// </summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"{Type}: {Name}";
}
}
@@ -0,0 +1,111 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Firewall;
/// <summary>
/// Represents a Proxmox VE firewall rule as returned by
/// the firewall/rules endpoints at cluster, node, VM, or container level.
/// </summary>
public class PveFirewallRule
{
/// <summary>
/// Rule position (used for ordering and as identifier for updates/deletes).
/// </summary>
[JsonPropertyName("pos")]
[JsonProperty("pos")]
public int? Pos { get; set; }
/// <summary>
/// Rule type: "in", "out", or "group".
/// </summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// Action to take: ACCEPT, DROP, or REJECT.
/// </summary>
[JsonPropertyName("action")]
[JsonProperty("action")]
public string Action { get; set; } = string.Empty;
/// <summary>
/// Whether the rule is enabled (1) or disabled (0).
/// </summary>
[JsonPropertyName("enable")]
[JsonProperty("enable")]
public int? Enable { get; set; }
/// <summary>
/// Source address or alias.
/// </summary>
[JsonPropertyName("source")]
[JsonProperty("source")]
public string? Source { get; set; }
/// <summary>
/// Destination address or alias.
/// </summary>
[JsonPropertyName("dest")]
[JsonProperty("dest")]
public string? Dest { get; set; }
/// <summary>
/// Protocol (e.g. tcp, udp, icmp).
/// </summary>
[JsonPropertyName("proto")]
[JsonProperty("proto")]
public string? Proto { get; set; }
/// <summary>
/// Destination port or port range.
/// </summary>
[JsonPropertyName("dport")]
[JsonProperty("dport")]
public string? Dport { get; set; }
/// <summary>
/// Source port or port range.
/// </summary>
[JsonPropertyName("sport")]
[JsonProperty("sport")]
public string? Sport { get; set; }
/// <summary>
/// Optional comment describing the rule.
/// </summary>
[JsonPropertyName("comment")]
[JsonProperty("comment")]
public string? Comment { get; set; }
/// <summary>
/// Predefined macro name (e.g. "SSH", "HTTP", "DNS").
/// </summary>
[JsonPropertyName("macro")]
[JsonProperty("macro")]
public string? Macro { get; set; }
/// <summary>
/// Log level for matched packets (e.g. "nolog", "info", "warning").
/// </summary>
[JsonPropertyName("log")]
[JsonProperty("log")]
public string? Log { get; set; }
/// <summary>
/// Network interface to match (e.g. "net0").
/// </summary>
[JsonPropertyName("iface")]
[JsonProperty("iface")]
public string? Iface { get; set; }
/// <inheritdoc />
public override string ToString()
{
var action = Macro != null ? $"{Action} ({Macro})" : Action;
return $"Rule {Pos}: {Type} {action} | "
+ $"Src: {Source ?? "any"} → Dst: {Dest ?? "any"} | "
+ $"Proto: {Proto ?? "any"} DPort: {Dport ?? "any"}";
}
}
@@ -0,0 +1,52 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Network;
/// <summary>
/// Represents a Software-Defined Networking (SDN) controller as returned by
/// the /cluster/sdn/controllers endpoint. Requires Proxmox VE 8.0+.
/// </summary>
public class PveSdnController
{
/// <summary>
/// The controller identifier.
/// </summary>
[JsonPropertyName("controller")]
[JsonProperty("controller")]
public string Controller { get; set; } = string.Empty;
/// <summary>
/// The controller type (e.g. "evpn", "bgp").
/// </summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// The Autonomous System Number for BGP/EVPN.
/// </summary>
[JsonPropertyName("asn")]
[JsonProperty("asn")]
public int? Asn { get; set; }
/// <summary>
/// Comma-separated list of BGP peer addresses.
/// </summary>
[JsonPropertyName("peers")]
[JsonProperty("peers")]
public string? Peers { get; set; }
/// <summary>
/// The node this controller is configured on.
/// </summary>
[JsonPropertyName("node")]
[JsonProperty("node")]
public string? Node { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"SDN Controller: {Controller} | Type: {Type} | ASN: {Asn?.ToString() ?? "N/A"}";
}
}
@@ -0,0 +1,59 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Network;
/// <summary>
/// Represents a Software-Defined Networking (SDN) DNS plugin as returned by
/// the /cluster/sdn/dns endpoint. Requires Proxmox VE 8.0+.
/// </summary>
public class PveSdnDns
{
/// <summary>
/// The DNS plugin identifier.
/// </summary>
[JsonPropertyName("dns")]
[JsonProperty("dns")]
public string Dns { get; set; } = string.Empty;
/// <summary>
/// The DNS plugin type (e.g. "powerdns").
/// </summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// The URL of the DNS service API.
/// </summary>
[JsonPropertyName("url")]
[JsonProperty("url")]
public string? Url { get; set; }
/// <summary>
/// The API key for the DNS service.
/// </summary>
[JsonPropertyName("key")]
[JsonProperty("key")]
public string? Key { get; set; }
/// <summary>
/// The IPv6 reverse zone mask length.
/// </summary>
[JsonPropertyName("reversemaskv6")]
[JsonProperty("reversemaskv6")]
public int? ReverseMaskV6 { get; set; }
/// <summary>
/// The TTL (time-to-live) for DNS records.
/// </summary>
[JsonPropertyName("ttl")]
[JsonProperty("ttl")]
public int? Ttl { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"SDN DNS: {Dns} | Type: {Type} | URL: {Url ?? "N/A"}";
}
}
@@ -0,0 +1,52 @@
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace PSProxmoxVE.Core.Models.Network;
/// <summary>
/// Represents a Software-Defined Networking (SDN) IPAM plugin as returned by
/// the /cluster/sdn/ipams endpoint. Requires Proxmox VE 8.0+.
/// </summary>
public class PveSdnIpam
{
/// <summary>
/// The IPAM plugin identifier.
/// </summary>
[JsonPropertyName("ipam")]
[JsonProperty("ipam")]
public string Ipam { get; set; } = string.Empty;
/// <summary>
/// The IPAM type (e.g. "pve", "netbox", "phpipam").
/// </summary>
[JsonPropertyName("type")]
[JsonProperty("type")]
public string Type { get; set; } = string.Empty;
/// <summary>
/// The URL of the external IPAM service (for netbox/phpipam types).
/// </summary>
[JsonPropertyName("url")]
[JsonProperty("url")]
public string? Url { get; set; }
/// <summary>
/// The API token for the external IPAM service.
/// </summary>
[JsonPropertyName("token")]
[JsonProperty("token")]
public string? Token { get; set; }
/// <summary>
/// The configuration section identifier.
/// </summary>
[JsonPropertyName("section")]
[JsonProperty("section")]
public int? Section { get; set; }
/// <inheritdoc />
public override string ToString()
{
return $"SDN IPAM: {Ipam} | Type: {Type}";
}
}
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Client;
using PSProxmoxVE.Core.Models.Backup;
using PSProxmoxVE.Core.Models.Vms;
namespace PSProxmoxVE.Core.Services
{
/// <summary>
/// Service for Proxmox VE Backup (vzdump) and backup job API operations.
/// </summary>
public class BackupService
{
// -------------------------------------------------------------------------
// Ad-hoc backup (vzdump)
// -------------------------------------------------------------------------
/// <summary>
/// Creates an ad-hoc backup via vzdump. Returns the task UPID.
/// </summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The node to run the backup on.</param>
/// <param name="config">Backup configuration parameters.</param>
public PveTask CreateBackup(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);
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/vzdump", config)
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
// -------------------------------------------------------------------------
// Backup jobs (scheduled)
// -------------------------------------------------------------------------
/// <summary>
/// Returns all scheduled backup jobs.
/// </summary>
public PveBackupJob[] GetBackupJobs(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
using var client = new PveHttpClient(session);
var response = client.GetAsync("cluster/backup").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveBackupJob[]>() ?? Array.Empty<PveBackupJob>();
}
/// <summary>
/// Returns a single scheduled backup job by ID.
/// </summary>
public PveBackupJob? GetBackupJob(PveSession session, string id)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"cluster/backup/{Uri.EscapeDataString(id)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveBackupJob>();
}
/// <summary>
/// Creates a scheduled backup job.
/// </summary>
public void CreateBackupJob(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("cluster/backup", config).GetAwaiter().GetResult();
}
/// <summary>
/// Updates a scheduled backup job.
/// </summary>
public void UpdateBackupJob(PveSession session, string id, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"cluster/backup/{Uri.EscapeDataString(id)}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Removes a scheduled backup job.
/// </summary>
public void RemoveBackupJob(PveSession session, string id)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id));
using var client = new PveHttpClient(session);
client.DeleteAsync($"cluster/backup/{Uri.EscapeDataString(id)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// 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;
}
}
}
@@ -173,7 +173,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
using var client = new PveHttpClient(session);
var response = client.DeleteAsync($"nodes/{node}/lxc/{vmid}/snapshot/{snapname}")
var response = client.DeleteAsync($"nodes/{node}/lxc/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}")
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
@@ -192,7 +192,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
using var client = new PveHttpClient(session);
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/snapshot/{snapname}/rollback")
var response = client.PostAsync($"nodes/{node}/lxc/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}/rollback")
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
@@ -0,0 +1,478 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Client;
using PSProxmoxVE.Core.Models.Firewall;
namespace PSProxmoxVE.Core.Services
{
/// <summary>
/// Service for Proxmox VE Firewall API operations.
/// Supports firewall management at cluster, node, VM, and container levels.
/// </summary>
public class FirewallService
{
// -------------------------------------------------------------------------
// Rules
// -------------------------------------------------------------------------
/// <summary>
/// Returns firewall rules at the specified level.
/// </summary>
public PveFirewallRule[] GetRules(PveSession session, string level, string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
var response = client.GetAsync($"{basePath}/rules").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveFirewallRule[]>() ?? Array.Empty<PveFirewallRule>();
}
/// <summary>
/// Creates a firewall rule at the specified level.
/// </summary>
public void CreateRule(PveSession session, string level, Dictionary<string, string> config,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
client.PostAsync($"{basePath}/rules", config).GetAwaiter().GetResult();
}
/// <summary>
/// Updates a firewall rule at the specified position.
/// </summary>
public void UpdateRule(PveSession session, string level, int pos, Dictionary<string, string> config,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
client.PutAsync($"{basePath}/rules/{pos}", config).GetAwaiter().GetResult();
}
/// <summary>
/// Removes a firewall rule at the specified position.
/// </summary>
public void RemoveRule(PveSession session, string level, int pos,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
client.DeleteAsync($"{basePath}/rules/{pos}").GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Security Groups (cluster level only)
// -------------------------------------------------------------------------
/// <summary>
/// Returns all firewall security groups.
/// </summary>
public PveFirewallGroup[] GetGroups(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
using var client = new PveHttpClient(session);
var response = client.GetAsync("cluster/firewall/groups").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveFirewallGroup[]>() ?? Array.Empty<PveFirewallGroup>();
}
/// <summary>
/// Creates a firewall security group.
/// </summary>
public void CreateGroup(PveSession session, string name, string? comment = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var formData = new Dictionary<string, string> { ["group"] = name };
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
using var client = new PveHttpClient(session);
client.PostAsync("cluster/firewall/groups", formData).GetAwaiter().GetResult();
}
/// <summary>
/// Removes a firewall security group.
/// </summary>
public void RemoveGroup(PveSession session, string name)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
using var client = new PveHttpClient(session);
client.DeleteAsync($"cluster/firewall/groups/{Uri.EscapeDataString(name)}")
.GetAwaiter().GetResult();
}
/// <summary>
/// Returns firewall rules within a security group.
/// </summary>
public PveFirewallRule[] GetGroupRules(PveSession session, string group)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
using var client = new PveHttpClient(session);
var response = client.GetAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveFirewallRule[]>() ?? Array.Empty<PveFirewallRule>();
}
/// <summary>
/// Creates a rule within a security group.
/// </summary>
public void CreateGroupRule(PveSession session, string group, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PostAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates a rule within a security group.
/// </summary>
public void UpdateGroupRule(PveSession session, string group, int pos, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PutAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}/{pos}", config)
.GetAwaiter().GetResult();
}
/// <summary>
/// Removes a rule from a security group.
/// </summary>
public void RemoveGroupRule(PveSession session, string group, int pos)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(group)) throw new ArgumentNullException(nameof(group));
using var client = new PveHttpClient(session);
client.DeleteAsync($"cluster/firewall/groups/{Uri.EscapeDataString(group)}/{pos}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Aliases
// -------------------------------------------------------------------------
/// <summary>
/// Returns firewall aliases at the specified level.
/// </summary>
public PveFirewallAlias[] GetAliases(PveSession session, string level, string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
var response = client.GetAsync($"{basePath}/aliases").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveFirewallAlias[]>() ?? Array.Empty<PveFirewallAlias>();
}
/// <summary>
/// Creates a firewall alias.
/// </summary>
public void CreateAlias(PveSession session, string level, string name, string cidr,
string? comment = null, string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
var basePath = BuildBasePath(level, node, vmid);
var formData = new Dictionary<string, string>
{
["name"] = name,
["cidr"] = cidr
};
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
using var client = new PveHttpClient(session);
client.PostAsync($"{basePath}/aliases", formData).GetAwaiter().GetResult();
}
/// <summary>
/// Updates a firewall alias.
/// </summary>
public void UpdateAlias(PveSession session, string level, string name,
string? cidr = null, string? comment = null, string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var basePath = BuildBasePath(level, node, vmid);
var formData = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(cidr))
formData["cidr"] = cidr!;
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
using var client = new PveHttpClient(session);
client.PutAsync($"{basePath}/aliases/{Uri.EscapeDataString(name)}", formData)
.GetAwaiter().GetResult();
}
/// <summary>
/// Removes a firewall alias.
/// </summary>
public void RemoveAlias(PveSession session, string level, string name,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
client.DeleteAsync($"{basePath}/aliases/{Uri.EscapeDataString(name)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// IP Sets
// -------------------------------------------------------------------------
/// <summary>
/// Returns firewall IP sets at the specified level.
/// </summary>
public PveFirewallIpSet[] GetIpSets(PveSession session, string level, string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
var response = client.GetAsync($"{basePath}/ipset").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveFirewallIpSet[]>() ?? Array.Empty<PveFirewallIpSet>();
}
/// <summary>
/// Creates a firewall IP set.
/// </summary>
public void CreateIpSet(PveSession session, string level, string name,
string? comment = null, string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var basePath = BuildBasePath(level, node, vmid);
var formData = new Dictionary<string, string> { ["name"] = name };
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
using var client = new PveHttpClient(session);
client.PostAsync($"{basePath}/ipset", formData).GetAwaiter().GetResult();
}
/// <summary>
/// Removes a firewall IP set.
/// </summary>
public void RemoveIpSet(PveSession session, string level, string name,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
client.DeleteAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// IP Set Entries
// -------------------------------------------------------------------------
/// <summary>
/// Returns entries within an IP set.
/// </summary>
public PveFirewallIpSetEntry[] GetIpSetEntries(PveSession session, string level, string name,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
var response = client.GetAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveFirewallIpSetEntry[]>() ?? Array.Empty<PveFirewallIpSetEntry>();
}
/// <summary>
/// Adds an entry to an IP set.
/// </summary>
public void AddIpSetEntry(PveSession session, string level, string name, string cidr,
bool nomatch = false, string? comment = null, string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
var basePath = BuildBasePath(level, node, vmid);
var formData = new Dictionary<string, string> { ["cidr"] = cidr };
if (nomatch)
formData["nomatch"] = "1";
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
using var client = new PveHttpClient(session);
client.PostAsync($"{basePath}/ipset/{Uri.EscapeDataString(name)}", formData)
.GetAwaiter().GetResult();
}
/// <summary>
/// Updates an entry in an IP set.
/// </summary>
public void UpdateIpSetEntry(PveSession session, string level, string name, string cidr,
bool? nomatch = null, string? comment = null, string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
var basePath = BuildBasePath(level, node, vmid);
var formData = new Dictionary<string, string>();
if (nomatch.HasValue)
formData["nomatch"] = nomatch.Value ? "1" : "0";
if (!string.IsNullOrEmpty(comment))
formData["comment"] = comment!;
using var client = new PveHttpClient(session);
client.PutAsync(
$"{basePath}/ipset/{Uri.EscapeDataString(name)}/{Uri.EscapeDataString(cidr)}",
formData).GetAwaiter().GetResult();
}
/// <summary>
/// Removes an entry from an IP set.
/// </summary>
public void RemoveIpSetEntry(PveSession session, string level, string name, string cidr,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));
if (string.IsNullOrWhiteSpace(cidr)) throw new ArgumentNullException(nameof(cidr));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
client.DeleteAsync(
$"{basePath}/ipset/{Uri.EscapeDataString(name)}/{Uri.EscapeDataString(cidr)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Options
// -------------------------------------------------------------------------
/// <summary>
/// Returns firewall options at the specified level.
/// </summary>
public PveFirewallOptions GetOptions(PveSession session, string level,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
var response = client.GetAsync($"{basePath}/options").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveFirewallOptions>() ?? new PveFirewallOptions();
}
/// <summary>
/// Sets firewall options at the specified level.
/// </summary>
public void SetOptions(PveSession session, string level, Dictionary<string, string> config,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (config == null) throw new ArgumentNullException(nameof(config));
var basePath = BuildBasePath(level, node, vmid);
using var client = new PveHttpClient(session);
client.PutAsync($"{basePath}/options", config).GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Refs
// -------------------------------------------------------------------------
/// <summary>
/// Returns firewall references (aliases, IP sets) at the specified level.
/// </summary>
public PveFirewallRef[] GetRefs(PveSession session, string level, string? type = null,
string? node = null, int? vmid = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
var basePath = BuildBasePath(level, node, vmid);
var resource = $"{basePath}/refs";
if (!string.IsNullOrEmpty(type))
resource += $"?type={Uri.EscapeDataString(type!)}";
using var client = new PveHttpClient(session);
var response = client.GetAsync(resource).GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveFirewallRef[]>() ?? Array.Empty<PveFirewallRef>();
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
/// <summary>
/// Builds the API base path for the specified firewall level.
/// </summary>
private static string BuildBasePath(string level, string? node, int? vmid)
{
switch (level.ToLowerInvariant())
{
case "cluster":
return "cluster/firewall";
case "node":
if (string.IsNullOrWhiteSpace(node))
throw new ArgumentException("Node is required for node-level firewall operations.", nameof(node));
return $"nodes/{Uri.EscapeDataString(node)}/firewall";
case "vm":
if (string.IsNullOrWhiteSpace(node))
throw new ArgumentException("Node is required for VM-level firewall operations.", nameof(node));
if (!vmid.HasValue)
throw new ArgumentException("VmId is required for VM-level firewall operations.", nameof(vmid));
return $"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid.Value}/firewall";
case "container":
if (string.IsNullOrWhiteSpace(node))
throw new ArgumentException("Node is required for container-level firewall operations.", nameof(node));
if (!vmid.HasValue)
throw new ArgumentException("VmId is required for container-level firewall operations.", nameof(vmid));
return $"nodes/{Uri.EscapeDataString(node)}/lxc/{vmid.Value}/firewall";
default:
throw new ArgumentException($"Invalid firewall level: {level}. Must be Cluster, Node, Vm, or Container.", nameof(level));
}
}
}
}
@@ -304,6 +304,141 @@ namespace PSProxmoxVE.Core.Services
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// SDN IPAM
// -------------------------------------------------------------------------
/// <summary>
/// Returns all SDN IPAM plugins. Requires PVE 8.0+.
/// </summary>
public PveSdnIpam[] GetSdnIpams(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
using var client = new PveHttpClient(session);
var response = client.GetAsync("cluster/sdn/ipams").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveSdnIpam[]>() ?? Array.Empty<PveSdnIpam>();
}
/// <summary>
/// Creates an SDN IPAM plugin. Requires PVE 8.0+.
/// </summary>
public void CreateSdnIpam(PveSession session, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PostAsync("cluster/sdn/ipams", config).GetAwaiter().GetResult();
}
/// <summary>
/// Removes an SDN IPAM plugin. Requires PVE 8.0+.
/// </summary>
public void RemoveSdnIpam(PveSession session, string ipam)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
if (string.IsNullOrWhiteSpace(ipam)) throw new ArgumentNullException(nameof(ipam));
using var client = new PveHttpClient(session);
client.DeleteAsync($"cluster/sdn/ipams/{Uri.EscapeDataString(ipam)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// SDN DNS
// -------------------------------------------------------------------------
/// <summary>
/// Returns all SDN DNS plugins. Requires PVE 8.0+.
/// </summary>
public PveSdnDns[] GetSdnDnsPlugins(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
using var client = new PveHttpClient(session);
var response = client.GetAsync("cluster/sdn/dns").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveSdnDns[]>() ?? Array.Empty<PveSdnDns>();
}
/// <summary>
/// Creates an SDN DNS plugin. Requires PVE 8.0+.
/// </summary>
public void CreateSdnDnsPlugin(PveSession session, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PostAsync("cluster/sdn/dns", config).GetAwaiter().GetResult();
}
/// <summary>
/// Removes an SDN DNS plugin. Requires PVE 8.0+.
/// </summary>
public void RemoveSdnDnsPlugin(PveSession session, string dns)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
if (string.IsNullOrWhiteSpace(dns)) throw new ArgumentNullException(nameof(dns));
using var client = new PveHttpClient(session);
client.DeleteAsync($"cluster/sdn/dns/{Uri.EscapeDataString(dns)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// SDN Controllers
// -------------------------------------------------------------------------
/// <summary>
/// Returns all SDN controllers. Requires PVE 8.0+.
/// </summary>
public PveSdnController[] GetSdnControllers(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
using var client = new PveHttpClient(session);
var response = client.GetAsync("cluster/sdn/controllers").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveSdnController[]>() ?? Array.Empty<PveSdnController>();
}
/// <summary>
/// Creates an SDN controller. Requires PVE 8.0+.
/// </summary>
public void CreateSdnController(PveSession session, Dictionary<string, string> config)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
if (config == null) throw new ArgumentNullException(nameof(config));
using var client = new PveHttpClient(session);
client.PostAsync("cluster/sdn/controllers", config).GetAwaiter().GetResult();
}
/// <summary>
/// Removes an SDN controller. Requires PVE 8.0+.
/// </summary>
public void RemoveSdnController(PveSession session, string controller)
{
if (session == null) throw new ArgumentNullException(nameof(session));
RequireSdn(session);
if (string.IsNullOrWhiteSpace(controller)) throw new ArgumentNullException(nameof(controller));
using var client = new PveHttpClient(session);
client.DeleteAsync($"cluster/sdn/controllers/{Uri.EscapeDataString(controller)}")
.GetAwaiter().GetResult();
}
// -------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------
@@ -84,7 +84,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
using var client = new PveHttpClient(session);
var response = client.DeleteAsync($"nodes/{node}/qemu/{vmid}/snapshot/{snapname}")
var response = client.DeleteAsync($"nodes/{node}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}")
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
@@ -107,7 +107,7 @@ namespace PSProxmoxVE.Core.Services
if (string.IsNullOrWhiteSpace(snapname)) throw new ArgumentNullException(nameof(snapname));
using var client = new PveHttpClient(session);
var response = client.PostAsync($"nodes/{node}/qemu/{vmid}/snapshot/{snapname}/rollback")
var response = client.PostAsync($"nodes/{node}/qemu/{vmid}/snapshot/{Uri.EscapeDataString(snapname)}/rollback")
.GetAwaiter().GetResult();
return ParseTask(response, node);
}
@@ -0,0 +1,46 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Backup;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Backup
{
/// <summary>
/// <para type="synopsis">Lists Proxmox VE backup jobs.</para>
/// <para type="description">
/// Returns scheduled backup job configurations from the Proxmox VE cluster.
/// Optionally filter by job ID.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveBackupJob")]
[OutputType(typeof(PveBackupJob))]
public sealed class GetPveBackupJobCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The backup job ID to retrieve. When omitted, all jobs are returned.</para>
/// </summary>
[Parameter(Mandatory = false, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The backup job ID.")]
public string? Id { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new BackupService();
if (!string.IsNullOrEmpty(Id))
{
WriteVerbose($"Getting backup job '{Id}'...");
var job = service.GetBackupJob(session, Id!);
WriteObject(job);
}
else
{
WriteVerbose("Getting all backup jobs...");
var jobs = service.GetBackupJobs(session);
foreach (var job in jobs)
{
WriteObject(job);
}
}
}
}
}
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Backup
{
/// <summary>
/// <para type="synopsis">Creates an ad-hoc backup of a virtual machine via vzdump.</para>
/// <para type="description">
/// Triggers an immediate backup (vzdump) of the specified VM on the given node.
/// Returns a PveTask representing the backup operation. Use -Wait to block until
/// the backup completes.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveBackup", SupportsShouldProcess = true)]
[OutputType(typeof(PveTask))]
public sealed class NewPveBackupCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The node on which the VM resides.</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">The ID of the VM to back up.</para>
/// </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 target storage for the backup.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The target storage for the backup.")]
public string? Storage { get; set; }
/// <summary>
/// <para type="description">The backup mode (snapshot, suspend, or stop).</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The backup mode.")]
[ValidateSet("snapshot", "suspend", "stop")]
public string? Mode { get; set; }
/// <summary>
/// <para type="description">The compression algorithm to use.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The compression algorithm.")]
[ValidateSet("zstd", "lzo", "gzip", "none")]
public string? Compress { get; set; }
/// <summary>
/// <para type="description">When specified, waits for the backup 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>
/// <para type="description">Maximum seconds to wait when -Wait is specified. Default 3600.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Timeout in seconds for -Wait (default 3600).")]
public int Timeout { get; set; } = 3600;
protected override void ProcessRecord()
{
if (!ShouldProcess($"VM {VmId} on node '{Node}'", "New-PveBackup"))
return;
var session = GetSession();
var service = new BackupService();
var formData = new Dictionary<string, string>
{
["vmid"] = VmId.ToString()
};
if (!string.IsNullOrEmpty(Storage)) formData["storage"] = Storage!;
if (!string.IsNullOrEmpty(Mode)) formData["mode"] = Mode!;
if (!string.IsNullOrEmpty(Compress)) formData["compress"] = Compress!;
WriteVerbose($"Creating backup of VM {VmId} on node '{Node}'...");
var task = service.CreateBackup(session, Node, formData);
if (Wait.IsPresent)
{
var taskService = new TaskService();
task = taskService.WaitForTask(session, task.Node ?? Node, task.Upid, TimeSpan.FromSeconds(Timeout));
}
WriteObject(task);
}
}
}
@@ -0,0 +1,122 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Backup
{
/// <summary>
/// <para type="synopsis">Creates a scheduled backup job on Proxmox VE.</para>
/// <para type="description">
/// Creates a new scheduled backup job in the Proxmox VE cluster configuration.
/// Specify a cron-style schedule and target storage. Use VmId for specific VMs
/// or -All to back up all VMs.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveBackupJob", SupportsShouldProcess = true)]
public sealed class NewPveBackupJobCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">Cron-style schedule expression (e.g., "0 2 * * *").</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "Cron-style schedule expression (e.g., '0 2 * * *').")]
public string Schedule { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The target storage for backups.</para>
/// </summary>
[Parameter(Mandatory = true, HelpMessage = "The target storage for backups.")]
public string Storage { get; set; } = string.Empty;
/// <summary>
/// <para type="description">The backup mode (snapshot, suspend, or stop).</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The backup mode.")]
[ValidateSet("snapshot", "suspend", "stop")]
public string? Mode { get; set; }
/// <summary>
/// <para type="description">Comma-separated list of VM IDs to include in the backup job.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of VM IDs to back up.")]
public string? VmId { get; set; }
/// <summary>
/// <para type="description">When specified, backs up all VMs on the node.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Back up all VMs.")]
public SwitchParameter All { get; set; }
/// <summary>
/// <para type="description">The compression algorithm to use.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The compression algorithm.")]
[ValidateSet("zstd", "lzo", "gzip", "none")]
public string? Compress { get; set; }
/// <summary>
/// <para type="description">The node to restrict the backup job to.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The node to restrict the backup job to.")]
public string? Node { get; set; }
/// <summary>
/// <para type="description">Email address(es) to send notifications to.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Email address(es) for notifications.")]
public string? MailTo { get; set; }
/// <summary>
/// <para type="description">When to send email notifications (always or failure).</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "When to send email notifications.")]
[ValidateSet("always", "failure")]
public string? MailNotification { get; set; }
/// <summary>
/// <para type="description">Whether the backup job is enabled. Default is true.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Whether the backup job is enabled.")]
public SwitchParameter Enabled { get; set; } = true;
/// <summary>
/// <para type="description">A comment or description for the backup job.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "A comment or description for the backup job.")]
public string? Comment { get; set; }
/// <summary>
/// <para type="description">Prune-backups configuration string (e.g., "keep-daily=7,keep-weekly=4").</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Prune-backups configuration string.")]
public string? PruneBackups { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess("cluster backup configuration", "New-PveBackupJob"))
return;
var session = GetSession();
var service = new BackupService();
var config = new Dictionary<string, string>
{
["schedule"] = Schedule,
["storage"] = Storage,
["enabled"] = Enabled.IsPresent ? "1" : "0"
};
if (!string.IsNullOrEmpty(Mode)) config["mode"] = Mode!;
if (!string.IsNullOrEmpty(VmId)) config["vmid"] = VmId!;
if (All.IsPresent) config["all"] = "1";
if (!string.IsNullOrEmpty(Compress)) config["compress"] = Compress!;
if (!string.IsNullOrEmpty(Node)) config["node"] = Node!;
if (!string.IsNullOrEmpty(MailTo)) config["mailto"] = MailTo!;
if (!string.IsNullOrEmpty(MailNotification)) config["mailnotification"] = MailNotification!;
if (!string.IsNullOrEmpty(Comment)) config["comment"] = Comment!;
if (!string.IsNullOrEmpty(PruneBackups)) config["prune-backups"] = PruneBackups!;
WriteVerbose("Creating backup job...");
service.CreateBackupJob(session, config);
}
}
}
@@ -0,0 +1,36 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Backup
{
/// <summary>
/// <para type="synopsis">Removes a scheduled backup job from Proxmox VE.</para>
/// <para type="description">
/// Deletes a backup job from the Proxmox VE cluster configuration.
/// This operation is destructive and requires confirmation unless -Force is specified.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveBackupJob",
SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
public sealed class RemovePveBackupJobCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The backup job ID to remove.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The backup job ID.")]
public string Id { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"backup job '{Id}'", "Remove-PveBackupJob"))
return;
var session = GetSession();
var service = new BackupService();
WriteVerbose($"Removing backup job '{Id}'...");
service.RemoveBackupJob(session, Id);
}
}
}
@@ -0,0 +1,125 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Backup
{
/// <summary>
/// <para type="synopsis">Updates a scheduled backup job on Proxmox VE.</para>
/// <para type="description">
/// Modifies properties of an existing backup job. Only specified parameters are
/// updated; omitted parameters retain their current values.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Set, "PveBackupJob", SupportsShouldProcess = true)]
public sealed class SetPveBackupJobCmdlet : PveCmdletBase
{
/// <summary>
/// <para type="description">The backup job ID to update.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The backup job ID.")]
public string Id { get; set; } = string.Empty;
/// <summary>
/// <para type="description">Updated cron-style schedule expression.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Cron-style schedule expression (e.g., '0 2 * * *').")]
public string? Schedule { get; set; }
/// <summary>
/// <para type="description">Updated target storage for backups.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The target storage for backups.")]
public string? Storage { get; set; }
/// <summary>
/// <para type="description">Updated backup mode (snapshot, suspend, or stop).</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The backup mode.")]
[ValidateSet("snapshot", "suspend", "stop")]
public string? Mode { get; set; }
/// <summary>
/// <para type="description">Updated comma-separated list of VM IDs.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Comma-separated list of VM IDs to back up.")]
public string? VmId { get; set; }
/// <summary>
/// <para type="description">When specified, backs up all VMs.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Back up all VMs.")]
public SwitchParameter All { get; set; }
/// <summary>
/// <para type="description">Updated compression algorithm.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The compression algorithm.")]
[ValidateSet("zstd", "lzo", "gzip", "none")]
public string? Compress { get; set; }
/// <summary>
/// <para type="description">Updated node restriction.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "The node to restrict the backup job to.")]
public string? Node { get; set; }
/// <summary>
/// <para type="description">Updated email address(es) for notifications.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Email address(es) for notifications.")]
public string? MailTo { get; set; }
/// <summary>
/// <para type="description">Updated notification policy.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "When to send email notifications.")]
[ValidateSet("always", "failure")]
public string? MailNotification { get; set; }
/// <summary>
/// <para type="description">Enable or disable the backup job.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Enable or disable the backup job.")]
public SwitchParameter Enabled { get; set; }
/// <summary>
/// <para type="description">Updated comment or description.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "A comment or description for the backup job.")]
public string? Comment { get; set; }
/// <summary>
/// <para type="description">Updated prune-backups configuration string.</para>
/// </summary>
[Parameter(Mandatory = false, HelpMessage = "Prune-backups configuration string.")]
public string? PruneBackups { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"backup job '{Id}'", "Set-PveBackupJob"))
return;
var session = GetSession();
var service = new BackupService();
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Schedule)) config["schedule"] = Schedule!;
if (!string.IsNullOrEmpty(Storage)) config["storage"] = Storage!;
if (!string.IsNullOrEmpty(Mode)) config["mode"] = Mode!;
if (!string.IsNullOrEmpty(VmId)) config["vmid"] = VmId!;
if (All.IsPresent) config["all"] = "1";
if (!string.IsNullOrEmpty(Compress)) config["compress"] = Compress!;
if (!string.IsNullOrEmpty(Node)) config["node"] = Node!;
if (!string.IsNullOrEmpty(MailTo)) config["mailto"] = MailTo!;
if (!string.IsNullOrEmpty(MailNotification)) config["mailnotification"] = MailNotification!;
if (MyInvocation.BoundParameters.ContainsKey("Enabled")) config["enabled"] = Enabled.IsPresent ? "1" : "0";
if (!string.IsNullOrEmpty(Comment)) config["comment"] = Comment!;
if (!string.IsNullOrEmpty(PruneBackups)) config["prune-backups"] = PruneBackups!;
WriteVerbose($"Updating backup job '{Id}'...");
service.UpdateBackupJob(session, Id, config);
}
}
}
@@ -0,0 +1,70 @@
using System;
using System.Linq;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Get, "PveFirewallAlias")]
[OutputType(typeof(PveFirewallAlias))]
public class GetPveFirewallAliasCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Optional alias name to filter by.")]
public string? Name { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Getting firewall aliases at level '{level}'...");
var aliases = service.GetAliases(session, level, Node, vmid);
if (!string.IsNullOrEmpty(Name))
{
var filtered = aliases.Where(a => string.Equals(a.Name, Name, StringComparison.OrdinalIgnoreCase)).ToArray();
WriteObject(filtered, true);
}
else
{
WriteObject(aliases, true);
}
}
}
}
@@ -0,0 +1,34 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Get, "PveFirewallGroup")]
[OutputType(typeof(PveFirewallGroup), typeof(PveFirewallRule))]
public class GetPveFirewallGroupCmdlet : PveCmdletBase
{
[Parameter(Mandatory = false, Position = 0, HelpMessage = "The security group name. If specified, returns rules within the group.")]
public string? Group { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new FirewallService();
if (!string.IsNullOrEmpty(Group))
{
WriteVerbose($"Getting firewall rules for security group '{Group}'...");
var rules = service.GetGroupRules(session, Group!);
WriteObject(rules, true);
}
else
{
WriteVerbose("Getting firewall security groups...");
var groups = service.GetGroups(session);
WriteObject(groups, true);
}
}
}
}
@@ -0,0 +1,70 @@
using System;
using System.Linq;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Get, "PveFirewallIpSet")]
[OutputType(typeof(PveFirewallIpSet))]
public class GetPveFirewallIpSetCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Optional IP set name to filter by.")]
public string? Name { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Getting firewall IP sets at level '{level}'...");
var ipSets = service.GetIpSets(session, level, Node, vmid);
if (!string.IsNullOrEmpty(Name))
{
var filtered = ipSets.Where(s => string.Equals(s.Name, Name, StringComparison.OrdinalIgnoreCase)).ToArray();
WriteObject(filtered, true);
}
else
{
WriteObject(ipSets, true);
}
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Get, "PveFirewallIpSetEntry")]
[OutputType(typeof(PveFirewallIpSetEntry))]
public class GetPveFirewallIpSetEntryCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
public string Name { get; set; } = string.Empty;
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Getting entries for IP set '{Name}' at level '{level}'...");
var entries = service.GetIpSetEntries(session, level, Name, Node, vmid);
WriteObject(entries, true);
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Get, "PveFirewallOptions")]
[OutputType(typeof(PveFirewallOptions))]
public class GetPveFirewallOptionsCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Getting firewall options at level '{level}'...");
var options = service.GetOptions(session, level, Node, vmid);
WriteObject(options);
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Get, "PveFirewallRef")]
[OutputType(typeof(PveFirewallRef))]
public class GetPveFirewallRefCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Optional type filter for references.")]
public string? Type { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Getting firewall references at level '{level}'...");
var refs = service.GetRefs(session, level, Type, Node, vmid);
WriteObject(refs, true);
}
}
}
@@ -0,0 +1,70 @@
using System;
using System.Linq;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Get, "PveFirewallRule")]
[OutputType(typeof(PveFirewallRule))]
public class GetPveFirewallRuleCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Optional rule position to filter by.")]
public int? Position { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Getting firewall rules at level '{level}'...");
var rules = service.GetRules(session, level, Node, vmid);
if (Position.HasValue)
{
var filtered = rules.Where(r => r.Pos == Position.Value).ToArray();
WriteObject(filtered, true);
}
else
{
WriteObject(rules, true);
}
}
}
}
@@ -0,0 +1,68 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.New, "PveFirewallAlias", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public class NewPveFirewallAliasCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The alias name.")]
public string Name { get; set; } = string.Empty;
[Parameter(Mandatory = true, HelpMessage = "The CIDR network address.")]
public string Cidr { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "Optional comment for the alias.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall alias '{Name}' ({Level})", "Create"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Creating firewall alias '{Name}' at level '{level}'...");
service.CreateAlias(session, level, Name, Cidr, Comment, Node, vmid);
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.New, "PveFirewallGroup", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public class NewPveFirewallGroupCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The security group name.")]
public string Group { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "Optional comment for the security group.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"firewall security group '{Group}'", "Create"))
return;
var session = GetSession();
var service = new FirewallService();
WriteVerbose($"Creating firewall security group '{Group}'...");
service.CreateGroup(session, Group, Comment);
}
}
}
@@ -0,0 +1,65 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.New, "PveFirewallIpSet", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public class NewPveFirewallIpSetCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
public string Name { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "Optional comment for the IP set.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall IP set '{Name}' ({Level})", "Create"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Creating firewall IP set '{Name}' at level '{level}'...");
service.CreateIpSet(session, level, Name, Comment, Node, vmid);
}
}
}
@@ -0,0 +1,71 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.New, "PveFirewallIpSetEntry", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public class NewPveFirewallIpSetEntryCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
public string Name { get; set; } = string.Empty;
[Parameter(Mandatory = true, HelpMessage = "The CIDR network address to add.")]
public string Cidr { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "Exclude this entry from the IP set match.")]
public SwitchParameter NoMatch { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Optional comment for the entry.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"IP set entry '{Cidr}' in '{Name}' ({Level})", "Create"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Adding entry '{Cidr}' to IP set '{Name}' at level '{level}'...");
service.AddIpSetEntry(session, level, Name, Cidr, NoMatch.IsPresent, Comment, Node, vmid);
}
}
}
@@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.New, "PveFirewallRule", SupportsShouldProcess = true)]
[OutputType(typeof(PveFirewallRule))]
public class NewPveFirewallRuleCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The rule type: in, out, or group.")]
[ValidateSet("in", "out", "group")]
public string Type { get; set; } = string.Empty;
[Parameter(Mandatory = true, HelpMessage = "The rule action: ACCEPT, DROP, or REJECT.")]
[ValidateSet("ACCEPT", "DROP", "REJECT")]
public string Action { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "Enable the rule.")]
public SwitchParameter Enable { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Source address or alias.")]
public string? Source { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Destination address or alias.")]
public string? Dest { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Protocol (e.g., tcp, udp, icmp).")]
public string? Proto { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Destination port.")]
public string? Dport { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Source port.")]
public string? Sport { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Rule comment.")]
public string? Comment { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Macro name.")]
public string? Macro { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Log level.")]
public string? Log { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Network interface.")]
public string? Iface { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall rule ({Level})", "Create"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
var config = new Dictionary<string, string>
{
["type"] = Type,
["action"] = Action
};
if (Enable.IsPresent)
config["enable"] = "1";
if (!string.IsNullOrEmpty(Source))
config["source"] = Source!;
if (!string.IsNullOrEmpty(Dest))
config["dest"] = Dest!;
if (!string.IsNullOrEmpty(Proto))
config["proto"] = Proto!;
if (!string.IsNullOrEmpty(Dport))
config["dport"] = Dport!;
if (!string.IsNullOrEmpty(Sport))
config["sport"] = Sport!;
if (!string.IsNullOrEmpty(Comment))
config["comment"] = Comment!;
if (!string.IsNullOrEmpty(Macro))
config["macro"] = Macro!;
if (!string.IsNullOrEmpty(Log))
config["log"] = Log!;
if (!string.IsNullOrEmpty(Iface))
config["iface"] = Iface!;
WriteVerbose($"Creating firewall rule at level '{level}'...");
service.CreateRule(session, level, config, Node, vmid);
}
}
}
@@ -0,0 +1,62 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Remove, "PveFirewallAlias", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(void))]
public class RemovePveFirewallAliasCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The alias name to remove.")]
public string Name { get; set; } = string.Empty;
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall alias '{Name}' ({Level})", "Remove"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Removing firewall alias '{Name}' at level '{level}'...");
service.RemoveAlias(session, level, Name, Node, vmid);
}
}
}
@@ -0,0 +1,27 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Remove, "PveFirewallGroup", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(void))]
public class RemovePveFirewallGroupCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The security group name to remove.")]
public string Group { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"firewall security group '{Group}'", "Remove"))
return;
var session = GetSession();
var service = new FirewallService();
WriteVerbose($"Removing firewall security group '{Group}'...");
service.RemoveGroup(session, Group);
}
}
}
@@ -0,0 +1,62 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Remove, "PveFirewallIpSet", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(void))]
public class RemovePveFirewallIpSetCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The IP set name to remove.")]
public string Name { get; set; } = string.Empty;
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall IP set '{Name}' ({Level})", "Remove"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Removing firewall IP set '{Name}' at level '{level}'...");
service.RemoveIpSet(session, level, Name, Node, vmid);
}
}
}
@@ -0,0 +1,65 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Remove, "PveFirewallIpSetEntry", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(void))]
public class RemovePveFirewallIpSetEntryCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
public string Name { get; set; } = string.Empty;
[Parameter(Mandatory = true, HelpMessage = "The CIDR network address to remove.")]
public string Cidr { get; set; } = string.Empty;
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"IP set entry '{Cidr}' from '{Name}' ({Level})", "Remove"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Removing entry '{Cidr}' from IP set '{Name}' at level '{level}'...");
service.RemoveIpSetEntry(session, level, Name, Cidr, Node, vmid);
}
}
}
@@ -0,0 +1,62 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Remove, "PveFirewallRule", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
[OutputType(typeof(void))]
public class RemovePveFirewallRuleCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The rule position to remove.")]
public int Position { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall rule at position {Position} ({Level})", "Remove"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Removing firewall rule at position {Position} ({level})...");
service.RemoveRule(session, level, Position, Node, vmid);
}
}
}
@@ -0,0 +1,68 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Set, "PveFirewallAlias", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public class SetPveFirewallAliasCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The alias name to update.")]
public string Name { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The updated CIDR network address.")]
public string? Cidr { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Updated comment for the alias.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall alias '{Name}' ({Level})", "Update"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
WriteVerbose($"Updating firewall alias '{Name}' at level '{level}'...");
service.UpdateAlias(session, level, Name, Cidr, Comment, Node, vmid);
}
}
}
@@ -0,0 +1,72 @@
using System;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Set, "PveFirewallIpSetEntry", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public class SetPveFirewallIpSetEntryCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The IP set name.")]
public string Name { get; set; } = string.Empty;
[Parameter(Mandatory = true, HelpMessage = "The CIDR network address to update.")]
public string Cidr { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "Exclude this entry from the IP set match.")]
public SwitchParameter NoMatch { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Updated comment for the entry.")]
public string? Comment { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"IP set entry '{Cidr}' in '{Name}' ({Level})", "Update"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
bool? nomatch = NoMatch.IsPresent ? true : (bool?)null;
WriteVerbose($"Updating entry '{Cidr}' in IP set '{Name}' at level '{level}'...");
service.UpdateIpSetEntry(session, level, Name, Cidr, nomatch, Comment, Node, vmid);
}
}
}
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Set, "PveFirewallOptions", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public class SetPveFirewallOptionsCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable or disable the firewall.")]
public SwitchParameter Enable { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Default policy for incoming traffic: ACCEPT, DROP, or REJECT.")]
[ValidateSet("ACCEPT", "DROP", "REJECT")]
public string? PolicyIn { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Default policy for outgoing traffic: ACCEPT, DROP, or REJECT.")]
[ValidateSet("ACCEPT", "DROP", "REJECT")]
public string? PolicyOut { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Log level for incoming traffic.")]
public string? LogLevelIn { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Log level for outgoing traffic.")]
public string? LogLevelOut { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable DHCP.")]
public SwitchParameter Dhcp { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable NDP.")]
public SwitchParameter Ndp { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable MAC address filter.")]
public SwitchParameter MacFilter { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable IP filter.")]
public SwitchParameter IpFilter { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall options ({Level})", "Update"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
var config = new Dictionary<string, string>();
if (Enable.IsPresent)
config["enable"] = "1";
if (!string.IsNullOrEmpty(PolicyIn))
config["policy_in"] = PolicyIn!;
if (!string.IsNullOrEmpty(PolicyOut))
config["policy_out"] = PolicyOut!;
if (!string.IsNullOrEmpty(LogLevelIn))
config["log_level_in"] = LogLevelIn!;
if (!string.IsNullOrEmpty(LogLevelOut))
config["log_level_out"] = LogLevelOut!;
if (Dhcp.IsPresent)
config["dhcp"] = "1";
if (Ndp.IsPresent)
config["ndp"] = "1";
if (MacFilter.IsPresent)
config["macfilter"] = "1";
if (IpFilter.IsPresent)
config["ipfilter"] = "1";
WriteVerbose($"Setting firewall options at level '{level}'...");
service.SetOptions(session, level, config, Node, vmid);
}
}
}
@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Firewall;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Firewall
{
[Cmdlet(VerbsCommon.Set, "PveFirewallRule", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
public class SetPveFirewallRuleCmdlet : PveCmdletBase
{
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The firewall level: Cluster, Node, Vm, or Container.")]
[ValidateSet("Cluster", "Node", "Vm", "Container")]
public string Level { get; set; } = string.Empty;
[Parameter(Mandatory = false, HelpMessage = "The node name. Required when Level is Node, Vm, or Container.")]
public string? Node { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The VM/Container ID. Required when Level is Vm or Container.")]
[ValidateRange(100, 999999999)]
public int VmId { get; set; }
[Parameter(Mandatory = true, HelpMessage = "The rule position to update.")]
public int Position { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The rule type: in, out, or group.")]
[ValidateSet("in", "out", "group")]
public string? Type { get; set; }
[Parameter(Mandatory = false, HelpMessage = "The rule action: ACCEPT, DROP, or REJECT.")]
[ValidateSet("ACCEPT", "DROP", "REJECT")]
public string? Action { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Enable the rule.")]
public SwitchParameter Enable { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Source address or alias.")]
public string? Source { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Destination address or alias.")]
public string? Dest { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Protocol (e.g., tcp, udp, icmp).")]
public string? Proto { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Destination port.")]
public string? Dport { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Source port.")]
public string? Sport { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Rule comment.")]
public string? Comment { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Macro name.")]
public string? Macro { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Log level.")]
public string? Log { get; set; }
[Parameter(Mandatory = false, HelpMessage = "Network interface.")]
public string? Iface { get; set; }
protected override void ProcessRecord()
{
var level = Level;
if (!string.Equals(level, "Cluster", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrEmpty(Node))
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("Node is required when Level is not Cluster."),
"NodeRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (string.Equals(level, "Vm", StringComparison.OrdinalIgnoreCase) ||
string.Equals(level, "Container", StringComparison.OrdinalIgnoreCase))
{
if (VmId == 0)
{
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("VmId is required when Level is Vm or Container."),
"VmIdRequired", ErrorCategory.InvalidArgument, null));
return;
}
}
if (!ShouldProcess($"firewall rule at position {Position} ({Level})", "Update"))
return;
var session = GetSession();
var service = new FirewallService();
int? vmid = VmId > 0 ? VmId : (int?)null;
var config = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(Type))
config["type"] = Type!;
if (!string.IsNullOrEmpty(Action))
config["action"] = Action!;
if (Enable.IsPresent)
config["enable"] = "1";
if (!string.IsNullOrEmpty(Source))
config["source"] = Source!;
if (!string.IsNullOrEmpty(Dest))
config["dest"] = Dest!;
if (!string.IsNullOrEmpty(Proto))
config["proto"] = Proto!;
if (!string.IsNullOrEmpty(Dport))
config["dport"] = Dport!;
if (!string.IsNullOrEmpty(Sport))
config["sport"] = Sport!;
if (!string.IsNullOrEmpty(Comment))
config["comment"] = Comment!;
if (!string.IsNullOrEmpty(Macro))
config["macro"] = Macro!;
if (!string.IsNullOrEmpty(Log))
config["log"] = Log!;
if (!string.IsNullOrEmpty(Iface))
config["iface"] = Iface!;
WriteVerbose($"Updating firewall rule at position {Position} ({level})...");
service.UpdateRule(session, level, Position, config, Node, vmid);
}
}
}
@@ -0,0 +1,41 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Network;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Lists SDN controllers in Proxmox VE.</para>
/// <para type="description">
/// Returns Software-Defined Networking controller definitions.
/// Optionally filters by controller identifier.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveSdnController")]
[OutputType(typeof(PveSdnController))]
public class GetPveSdnControllerCmdlet : PveCmdletBase
{
/// <summary>Optional controller identifier filter.</summary>
[Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by controller identifier.")]
public string? Controller { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new NetworkService();
WriteVerbose("Getting SDN controllers...");
var controllers = service.GetSdnControllers(session);
foreach (var item in controllers)
{
if (!string.IsNullOrEmpty(Controller) &&
!string.Equals(item.Controller, Controller, System.StringComparison.OrdinalIgnoreCase))
continue;
WriteObject(item);
}
}
}
}
@@ -0,0 +1,41 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Network;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Lists SDN DNS plugins in Proxmox VE.</para>
/// <para type="description">
/// Returns Software-Defined Networking DNS plugin definitions.
/// Optionally filters by DNS identifier.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveSdnDns")]
[OutputType(typeof(PveSdnDns))]
public class GetPveSdnDnsCmdlet : PveCmdletBase
{
/// <summary>Optional DNS plugin identifier filter.</summary>
[Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by DNS plugin identifier.")]
public string? Dns { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new NetworkService();
WriteVerbose("Getting SDN DNS plugins...");
var plugins = service.GetSdnDnsPlugins(session);
foreach (var item in plugins)
{
if (!string.IsNullOrEmpty(Dns) &&
!string.Equals(item.Dns, Dns, System.StringComparison.OrdinalIgnoreCase))
continue;
WriteObject(item);
}
}
}
}
@@ -0,0 +1,41 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Models.Network;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Lists SDN IPAM plugins in Proxmox VE.</para>
/// <para type="description">
/// Returns Software-Defined Networking IPAM plugin definitions.
/// Optionally filters by IPAM identifier.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "PveSdnIpam")]
[OutputType(typeof(PveSdnIpam))]
public class GetPveSdnIpamCmdlet : PveCmdletBase
{
/// <summary>Optional IPAM identifier filter.</summary>
[Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by IPAM plugin identifier.")]
public string? Ipam { get; set; }
protected override void ProcessRecord()
{
var session = GetSession();
var service = new NetworkService();
WriteVerbose("Getting SDN IPAM plugins...");
var ipams = service.GetSdnIpams(session);
foreach (var item in ipams)
{
if (!string.IsNullOrEmpty(Ipam) &&
!string.Equals(item.Ipam, Ipam, System.StringComparison.OrdinalIgnoreCase))
continue;
WriteObject(item);
}
}
}
}
@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Creates a new SDN controller in Proxmox VE.</para>
/// <para type="description">
/// Adds a new Software-Defined Networking controller.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveSdnController", SupportsShouldProcess = true)]
public class NewPveSdnControllerCmdlet : PveCmdletBase
{
/// <summary>The controller identifier.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The controller identifier.")]
public string Controller { get; set; } = string.Empty;
/// <summary>The controller type.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The controller type.")]
[ValidateSet("evpn", "bgp")]
public string Type { 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($"SDN Controller '{Controller}'", "Create PVE SDN Controller"))
return;
var session = GetSession();
var service = new NetworkService();
WriteVerbose($"Creating SDN controller '{Controller}' of type '{Type}'...");
var data = new Dictionary<string, string>
{
["controller"] = Controller,
["type"] = Type
};
if (Asn.HasValue) data["asn"] = Asn.Value.ToString();
if (!string.IsNullOrEmpty(Peers)) data["peers"] = Peers!;
if (!string.IsNullOrEmpty(Node)) data["node"] = Node!;
service.CreateSdnController(session, data);
}
}
}
@@ -0,0 +1,65 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Creates a new SDN DNS plugin in Proxmox VE.</para>
/// <para type="description">
/// Adds a new Software-Defined Networking DNS plugin.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveSdnDns", SupportsShouldProcess = true)]
public class NewPveSdnDnsCmdlet : PveCmdletBase
{
/// <summary>The DNS plugin identifier.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The DNS plugin identifier.")]
public string Dns { get; set; } = string.Empty;
/// <summary>The DNS plugin type.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The DNS plugin type.")]
[ValidateSet("powerdns")]
public string Type { get; set; } = string.Empty;
/// <summary>The URL of the DNS service API.</summary>
[Parameter(Mandatory = true, HelpMessage = "The URL of the DNS service API.")]
public string Url { get; set; } = string.Empty;
/// <summary>The API key for the DNS service.</summary>
[Parameter(Mandatory = true, HelpMessage = "The API key for the DNS service.")]
public string Key { get; set; } = string.Empty;
/// <summary>The IPv6 reverse zone mask length.</summary>
[Parameter(Mandatory = false, HelpMessage = "The IPv6 reverse zone mask length.")]
public int? ReverseMaskV6 { get; set; }
/// <summary>The TTL (time-to-live) for DNS records.</summary>
[Parameter(Mandatory = false, HelpMessage = "The TTL (time-to-live) for DNS records.")]
public int? Ttl { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"SDN DNS '{Dns}'", "Create PVE SDN DNS"))
return;
var session = GetSession();
var service = new NetworkService();
WriteVerbose($"Creating SDN DNS plugin '{Dns}' of type '{Type}'...");
var data = new Dictionary<string, string>
{
["dns"] = Dns,
["type"] = Type,
["url"] = Url,
["key"] = Key
};
if (ReverseMaskV6.HasValue) data["reversemaskv6"] = ReverseMaskV6.Value.ToString();
if (Ttl.HasValue) data["ttl"] = Ttl.Value.ToString();
service.CreateSdnDnsPlugin(session, data);
}
}
}
@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Creates a new SDN IPAM plugin in Proxmox VE.</para>
/// <para type="description">
/// Adds a new Software-Defined Networking IPAM plugin.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.New, "PveSdnIpam", SupportsShouldProcess = true)]
public class NewPveSdnIpamCmdlet : PveCmdletBase
{
/// <summary>The IPAM plugin identifier.</summary>
[Parameter(Mandatory = true, Position = 0, HelpMessage = "The IPAM plugin identifier.")]
public string Ipam { get; set; } = string.Empty;
/// <summary>The IPAM plugin type.</summary>
[Parameter(Mandatory = true, Position = 1, HelpMessage = "The IPAM plugin type.")]
[ValidateSet("pve", "netbox", "phpipam")]
public string Type { get; set; } = string.Empty;
/// <summary>The URL of the external IPAM service.</summary>
[Parameter(Mandatory = false, HelpMessage = "The URL of the external IPAM service.")]
public string? Url { get; set; }
/// <summary>The API token for the external IPAM service.</summary>
[Parameter(Mandatory = false, HelpMessage = "The API token for the external IPAM service.")]
public string? Token { get; set; }
/// <summary>The configuration section identifier.</summary>
[Parameter(Mandatory = false, HelpMessage = "The configuration section identifier.")]
public int? Section { get; set; }
protected override void ProcessRecord()
{
if (!ShouldProcess($"SDN IPAM '{Ipam}'", "Create PVE SDN IPAM"))
return;
var session = GetSession();
var service = new NetworkService();
WriteVerbose($"Creating SDN IPAM plugin '{Ipam}' of type '{Type}'...");
var data = new Dictionary<string, string>
{
["ipam"] = Ipam,
["type"] = Type
};
if (!string.IsNullOrEmpty(Url)) data["url"] = Url!;
if (!string.IsNullOrEmpty(Token)) data["token"] = Token!;
if (Section.HasValue) data["section"] = Section.Value.ToString();
service.CreateSdnIpam(session, data);
}
}
}
@@ -0,0 +1,32 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Removes an SDN controller from Proxmox VE.</para>
/// <para type="description">
/// Deletes the specified Software-Defined Networking controller.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveSdnController", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public class RemovePveSdnControllerCmdlet : PveCmdletBase
{
/// <summary>The controller identifier to remove.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The controller identifier to remove.")]
public string Controller { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"SDN Controller '{Controller}'", "Remove PVE SDN Controller"))
return;
var session = GetSession();
var service = new NetworkService();
WriteVerbose($"Removing SDN controller '{Controller}'...");
service.RemoveSdnController(session, Controller);
}
}
}
@@ -0,0 +1,32 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Removes an SDN DNS plugin from Proxmox VE.</para>
/// <para type="description">
/// Deletes the specified Software-Defined Networking DNS plugin.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveSdnDns", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public class RemovePveSdnDnsCmdlet : PveCmdletBase
{
/// <summary>The DNS plugin identifier to remove.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The DNS plugin identifier to remove.")]
public string Dns { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"SDN DNS '{Dns}'", "Remove PVE SDN DNS"))
return;
var session = GetSession();
var service = new NetworkService();
WriteVerbose($"Removing SDN DNS plugin '{Dns}'...");
service.RemoveSdnDnsPlugin(session, Dns);
}
}
}
@@ -0,0 +1,32 @@
using System.Management.Automation;
using PSProxmoxVE.Core.Services;
namespace PSProxmoxVE.Cmdlets.Network
{
/// <summary>
/// <para type="synopsis">Removes an SDN IPAM plugin from Proxmox VE.</para>
/// <para type="description">
/// Deletes the specified Software-Defined Networking IPAM plugin.
/// Requires Proxmox VE 8.0 or later.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveSdnIpam", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public class RemovePveSdnIpamCmdlet : PveCmdletBase
{
/// <summary>The IPAM plugin identifier to remove.</summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The IPAM plugin identifier to remove.")]
public string Ipam { get; set; } = string.Empty;
protected override void ProcessRecord()
{
if (!ShouldProcess($"SDN IPAM '{Ipam}'", "Remove PVE SDN IPAM"))
return;
var session = GetSession();
var service = new NetworkService();
WriteVerbose($"Removing SDN IPAM plugin '{Ipam}'...");
service.RemoveSdnIpam(session, Ipam);
}
}
}
@@ -10,7 +10,7 @@ namespace PSProxmoxVE.Cmdlets.Users
/// Built-in roles cannot be removed.
/// </para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "PveRole", SupportsShouldProcess = true)]
[Cmdlet(VerbsCommon.Remove, "PveRole", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public class RemovePveRoleCmdlet : PveCmdletBase
{
/// <summary>The role identifier to remove.</summary>
+502
View File
@@ -442,5 +442,507 @@
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallRule -->
<View>
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallRule</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallRule</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Pos</Label>
<Width>5</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Type</Label>
<Width>6</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Action</Label>
<Width>8</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Proto</Label>
<Width>6</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Dport</Label>
<Width>12</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Source</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Dest</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Enable</Label>
<Width>7</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Pos</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Action</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Proto</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Dport</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Source</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Dest</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.Enable -eq 1) { 'Yes' } else { 'No' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallGroup -->
<View>
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallGroup</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallGroup</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Group</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Group</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallAlias -->
<View>
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallAlias</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallAlias</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Cidr</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Cidr</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSet -->
<View>
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSet</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSet</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSetEntry -->
<View>
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSetEntry</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallIpSetEntry</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Cidr</Label>
<Width>25</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>NoMatch</Label>
<Width>8</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Cidr</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.NoMatch -eq 1) { 'Yes' } else { 'No' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Firewall.PveFirewallOptions -->
<View>
<Name>PSProxmoxVE.Core.Models.Firewall.PveFirewallOptions</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Firewall.PveFirewallOptions</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Enable</Label>
<Width>8</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>PolicyIn</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>PolicyOut</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>LogLevelIn</Label>
<Width>12</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>LogLevelOut</Label>
<Width>12</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<ScriptBlock>if ($_.Enable -eq 1) { 'Yes' } else { 'No' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<PropertyName>PolicyIn</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>PolicyOut</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>LogLevelIn</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>LogLevelOut</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Backup.PveBackupJob -->
<View>
<Name>PSProxmoxVE.Core.Models.Backup.PveBackupJob</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Backup.PveBackupJob</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Id</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Enabled</Label>
<Width>8</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Schedule</Label>
<Width>16</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Storage</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Mode</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>VmId</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Comment</Label>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Id</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>if ($_.Enabled -eq 1) { 'Yes' } else { 'No' }</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Schedule</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Storage</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Mode</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>VmId</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Comment</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Network.PveSdnIpam -->
<View>
<Name>PSProxmoxVE.Core.Models.Network.PveSdnIpam</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Network.PveSdnIpam</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Ipam</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Type</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Ipam</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Network.PveSdnDns -->
<View>
<Name>PSProxmoxVE.Core.Models.Network.PveSdnDns</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Network.PveSdnDns</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Dns</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Type</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Url</Label>
<Width>40</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Dns</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Url</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<!-- PSProxmoxVE.Core.Models.Network.PveSdnController -->
<View>
<Name>PSProxmoxVE.Core.Models.Network.PveSdnController</Name>
<ViewSelectedBy>
<TypeName>PSProxmoxVE.Core.Models.Network.PveSdnController</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Controller</Label>
<Width>20</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Type</Label>
<Width>10</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Asn</Label>
<Width>10</Width>
<Alignment>Right</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Peers</Label>
<Width>30</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Node</Label>
<Width>15</Width>
<Alignment>Left</Alignment>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Controller</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Type</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Asn</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Peers</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Node</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
+46 -1
View File
@@ -170,7 +170,52 @@
# Tasks
'Get-PveTask',
'Wait-PveTask'
'Wait-PveTask',
# Firewall
'Get-PveFirewallRule',
'New-PveFirewallRule',
'Set-PveFirewallRule',
'Remove-PveFirewallRule',
'Get-PveFirewallGroup',
'New-PveFirewallGroup',
'Remove-PveFirewallGroup',
'Get-PveFirewallAlias',
'New-PveFirewallAlias',
'Set-PveFirewallAlias',
'Remove-PveFirewallAlias',
'Get-PveFirewallIpSet',
'New-PveFirewallIpSet',
'Remove-PveFirewallIpSet',
'Get-PveFirewallIpSetEntry',
'New-PveFirewallIpSetEntry',
'Set-PveFirewallIpSetEntry',
'Remove-PveFirewallIpSetEntry',
'Get-PveFirewallOptions',
'Set-PveFirewallOptions',
'Get-PveFirewallRef',
# Backup
'New-PveBackup',
'Get-PveBackupJob',
'New-PveBackupJob',
'Set-PveBackupJob',
'Remove-PveBackupJob',
# SDN — IPAM
'Get-PveSdnIpam',
'New-PveSdnIpam',
'Remove-PveSdnIpam',
# SDN — DNS
'Get-PveSdnDns',
'New-PveSdnDns',
'Remove-PveSdnDns',
# SDN — Controller
'Get-PveSdnController',
'New-PveSdnController',
'Remove-PveSdnController'
)
# Variables to export from this module