From 0bf275ea9f78944604326bafe8c8a2cd0fa12061 Mon Sep 17 00:00:00 2001 From: Clint Branham Date: Fri, 20 Mar 2026 12:21:27 -0500 Subject: [PATCH] refactor: improve cmdlet quality across all 66 cmdlets - Add HelpMessage to every [Parameter] attribute - Add [ValidateRange(100, 999999999)] to all VmId parameters - Add ConfirmImpact.High to Stop-PveVm and Reset-PveVm - Add ShouldProcess to Invoke-PveVmGuestExec - Add WriteVerbose before every API call - Add WriteWarning on -SkipCertificateCheck usage in Connect-PveServer Co-Authored-By: Claude Opus 4.6 (1M context) --- .../CloudInit/GetPveCloudInitConfigCmdlet.cs | 6 +- .../InvokePveCloudInitRegenerateCmdlet.cs | 9 +- .../CloudInit/SetPveCloudInitConfigCmdlet.cs | 22 ++--- .../Connection/ConnectPveServerCmdlet.cs | 15 ++-- .../Connection/TestPveConnectionCmdlet.cs | 2 +- .../Containers/CopyPveContainerCmdlet.cs | 19 ++-- .../Containers/GetPveContainerCmdlet.cs | 13 +-- .../Containers/GetPveContainerConfigCmdlet.cs | 6 +- .../GetPveContainerSnapshotCmdlet.cs | 54 ++++++++++++ .../Containers/NewPveContainerCmdlet.cs | 34 ++++---- .../NewPveContainerSnapshotCmdlet.cs | 86 +++++++++++++++++++ .../Containers/RemovePveContainerCmdlet.cs | 12 +-- .../RemovePveContainerSnapshotCmdlet.cs | 78 +++++++++++++++++ .../Containers/RestartPveContainerCmdlet.cs | 11 ++- .../RestorePveContainerSnapshotCmdlet.cs | 81 +++++++++++++++++ .../Containers/SetPveContainerConfigCmdlet.cs | 26 +++--- .../Containers/StartPveContainerCmdlet.cs | 8 +- .../Containers/StopPveContainerCmdlet.cs | 10 ++- .../Cmdlets/Network/GetPveNetworkCmdlet.cs | 7 +- .../Cmdlets/Network/GetPveSdnVnetCmdlet.cs | 5 +- .../Cmdlets/Network/GetPveSdnZoneCmdlet.cs | 3 +- .../Network/InvokePveNetworkApplyCmdlet.cs | 5 +- .../Cmdlets/Network/NewPveNetworkCmdlet.cs | 25 +++--- .../Cmdlets/Network/NewPveSdnVnetCmdlet.cs | 11 +-- .../Cmdlets/Network/NewPveSdnZoneCmdlet.cs | 19 ++-- .../Cmdlets/Network/RemovePveNetworkCmdlet.cs | 5 +- .../Cmdlets/Network/RemovePveSdnVnetCmdlet.cs | 3 +- .../Cmdlets/Network/RemovePveSdnZoneCmdlet.cs | 3 +- .../Cmdlets/Network/SetPveNetworkCmdlet.cs | 27 +++--- .../Cmdlets/Nodes/GetPveNodeCmdlet.cs | 3 +- .../Cmdlets/Nodes/GetPveNodeStatusCmdlet.cs | 3 +- .../Cmdlets/Snapshots/GetPveSnapshotCmdlet.cs | 9 +- .../Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs | 14 +-- .../Snapshots/RemovePveSnapshotCmdlet.cs | 11 ++- .../Snapshots/RestorePveSnapshotCmdlet.cs | 11 ++- .../Cmdlets/Storage/GetPveStorageCmdlet.cs | 10 ++- .../Storage/GetPveStorageContentCmdlet.cs | 8 +- .../Storage/InvokePveStorageDownloadCmdlet.cs | 13 +-- .../Cmdlets/Storage/NewPveStorageCmdlet.cs | 29 ++++--- .../Cmdlets/Storage/RemovePveStorageCmdlet.cs | 3 +- .../Cmdlets/Storage/SendPveIsoCmdlet.cs | 13 +-- .../Cmdlets/Tasks/GetPveTaskCmdlet.cs | 6 +- .../Cmdlets/Tasks/WaitPveTaskCmdlet.cs | 8 +- .../Cmdlets/Templates/GetPveTemplateCmdlet.cs | 5 +- .../Cmdlets/Templates/NewPveTemplateCmdlet.cs | 9 +- .../Templates/NewPveVmFromTemplateCmdlet.cs | 18 ++-- .../Templates/RemovePveTemplateCmdlet.cs | 11 ++- .../Cmdlets/Users/GetPveApiTokenCmdlet.cs | 6 +- .../Cmdlets/Users/GetPvePermissionCmdlet.cs | 6 +- .../Cmdlets/Users/GetPveRoleCmdlet.cs | 3 +- .../Cmdlets/Users/GetPveUserCmdlet.cs | 5 +- .../Cmdlets/Users/NewPveApiTokenCmdlet.cs | 12 +-- .../Cmdlets/Users/NewPveRoleCmdlet.cs | 5 +- .../Cmdlets/Users/NewPveUserCmdlet.cs | 18 ++-- .../Cmdlets/Users/RemovePveApiTokenCmdlet.cs | 5 +- .../Cmdlets/Users/RemovePveRoleCmdlet.cs | 4 +- .../Cmdlets/Users/RemovePveUserCmdlet.cs | 4 +- .../Cmdlets/Users/SetPvePermissionCmdlet.cs | 13 +-- .../Cmdlets/Users/SetPveUserCmdlet.cs | 20 +++-- .../Cmdlets/Vms/CopyPveVmCmdlet.cs | 19 ++-- src/PSProxmoxVE/Cmdlets/Vms/GetPveVmCmdlet.cs | 15 ++-- .../Cmdlets/Vms/GetPveVmConfigCmdlet.cs | 6 +- .../Cmdlets/Vms/GetPveVmGuestNetworkCmdlet.cs | 7 +- .../Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs | 15 ++-- .../Cmdlets/Vms/MovePveVmCmdlet.cs | 12 +-- src/PSProxmoxVE/Cmdlets/Vms/NewPveVmCmdlet.cs | 36 ++++---- .../Cmdlets/Vms/RemovePveVmCmdlet.cs | 12 +-- .../Cmdlets/Vms/ResetPveVmCmdlet.cs | 10 ++- .../Cmdlets/Vms/ResizePveVmDiskCmdlet.cs | 12 +-- .../Cmdlets/Vms/RestartPveVmCmdlet.cs | 11 ++- .../Cmdlets/Vms/ResumePveVmCmdlet.cs | 8 +- .../Cmdlets/Vms/SetPveVmConfigCmdlet.cs | 28 +++--- .../Cmdlets/Vms/StartPveVmCmdlet.cs | 8 +- .../Cmdlets/Vms/StopPveVmCmdlet.cs | 10 ++- .../Cmdlets/Vms/SuspendPveVmCmdlet.cs | 8 +- .../Cmdlets/Vms/TestPveVmGuestAgentCmdlet.cs | 7 +- 76 files changed, 775 insertions(+), 339 deletions(-) create mode 100644 src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerSnapshotCmdlet.cs create mode 100644 src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerSnapshotCmdlet.cs create mode 100644 src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerSnapshotCmdlet.cs create mode 100644 src/PSProxmoxVE/Cmdlets/Containers/RestorePveContainerSnapshotCmdlet.cs diff --git a/src/PSProxmoxVE/Cmdlets/CloudInit/GetPveCloudInitConfigCmdlet.cs b/src/PSProxmoxVE/Cmdlets/CloudInit/GetPveCloudInitConfigCmdlet.cs index 691a149..8022413 100644 --- a/src/PSProxmoxVE/Cmdlets/CloudInit/GetPveCloudInitConfigCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/CloudInit/GetPveCloudInitConfigCmdlet.cs @@ -18,11 +18,12 @@ namespace PSProxmoxVE.Cmdlets.CloudInit public class GetPveCloudInitConfigCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. Accepts pipeline input from Get-PveVm (PveVm.VmId). - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } protected override void ProcessRecord() @@ -30,6 +31,7 @@ namespace PSProxmoxVE.Cmdlets.CloudInit var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Getting cloud-init config for VM {VmId}..."); var json = client.GetAsync($"nodes/{Node}/qemu/{VmId}/config").GetAwaiter().GetResult(); var root = JObject.Parse(json); var data = root["data"]; diff --git a/src/PSProxmoxVE/Cmdlets/CloudInit/InvokePveCloudInitRegenerateCmdlet.cs b/src/PSProxmoxVE/Cmdlets/CloudInit/InvokePveCloudInitRegenerateCmdlet.cs index 4fbf998..c56146f 100644 --- a/src/PSProxmoxVE/Cmdlets/CloudInit/InvokePveCloudInitRegenerateCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/CloudInit/InvokePveCloudInitRegenerateCmdlet.cs @@ -18,15 +18,16 @@ namespace PSProxmoxVE.Cmdlets.CloudInit public class InvokePveCloudInitRegenerateCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. Accepts pipeline input from Get-PveVm (PveVm.VmId). - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// When specified, waits for the regeneration task to complete before returning. - [Parameter] + [Parameter(HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -35,6 +36,8 @@ namespace PSProxmoxVE.Cmdlets.CloudInit return; var session = GetSession(); + + WriteVerbose($"Regenerating cloud-init drive for VM {VmId}..."); var service = new CloudInitService(); var upid = service.RegenerateCloudInitImage(session, Node, VmId); diff --git a/src/PSProxmoxVE/Cmdlets/CloudInit/SetPveCloudInitConfigCmdlet.cs b/src/PSProxmoxVE/Cmdlets/CloudInit/SetPveCloudInitConfigCmdlet.cs index 6978072..b7df92a 100644 --- a/src/PSProxmoxVE/Cmdlets/CloudInit/SetPveCloudInitConfigCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/CloudInit/SetPveCloudInitConfigCmdlet.cs @@ -21,47 +21,48 @@ namespace PSProxmoxVE.Cmdlets.CloudInit public class SetPveCloudInitConfigCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. Accepts pipeline input from Get-PveVm (PveVm.VmId). - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// Cloud-init hostname override. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Cloud-init hostname override.")] public string? Hostname { get; set; } /// Cloud-init default username. Alias: User. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Cloud-init default username.")] [Alias("User")] public string? CiUser { get; set; } /// Cloud-init default user password. Accepts a SecureString. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Cloud-init default user password.")] public System.Security.SecureString? Password { get; set; } /// SSH public keys to inject (one key per element). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "SSH public keys to inject.")] public string[]? SshKeys { get; set; } /// /// IP configuration string (e.g., "ip=192.168.1.50/24,gw=192.168.1.1"). /// Maps to ipconfig0 on the VM. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IP config string (e.g. ip=192.168.1.50/24,gw=...).")] public string? IpConfig0 { get; set; } /// DNS nameserver(s) to inject (space or comma separated). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "DNS nameserver(s) to inject.")] public string? Nameserver { get; set; } /// DNS search domain to inject. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "DNS search domain to inject.")] public string? Searchdomain { get; set; } /// When specified, waits for the config update task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -71,6 +72,7 @@ namespace PSProxmoxVE.Cmdlets.CloudInit if (!ShouldProcess($"VM {VmId} on {Node}", "Set PVE Cloud-Init Config")) return; + WriteVerbose($"Setting cloud-init config for VM {VmId}..."); var config = new Dictionary(); if (!string.IsNullOrEmpty(CiUser)) config["ciuser"] = CiUser!; diff --git a/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs index c418f1d..d62e4cf 100644 --- a/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs @@ -21,17 +21,17 @@ namespace PSProxmoxVE.Cmdlets.Connection private const string ParameterSetApiToken = "ApiToken"; /// Hostname or IP address of the Proxmox VE server. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "Hostname or IP of the Proxmox VE server.")] [ValidateNotNullOrEmpty] public string Server { get; set; } = string.Empty; /// API port. Defaults to 8006. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "API port. Defaults to 8006.")] [ValidateRange(1, 65535)] public int Port { get; set; } = 8006; /// Username and password credential. Username must include a realm, e.g. root@pam. - [Parameter(Mandatory = true, ParameterSetName = ParameterSetCredential)] + [Parameter(Mandatory = true, ParameterSetName = ParameterSetCredential, HelpMessage = "Username and password. Username must include realm (e.g. root@pam).")] [ValidateNotNull] public PSCredential? Credential { get; set; } @@ -39,16 +39,16 @@ namespace PSProxmoxVE.Cmdlets.Connection /// Proxmox VE API token in the format USER@REALM!TOKENID=UUID, /// e.g. root@pam!mytoken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. /// - [Parameter(Mandatory = true, ParameterSetName = ParameterSetApiToken)] + [Parameter(Mandatory = true, ParameterSetName = ParameterSetApiToken, HelpMessage = "API token in USER@REALM!TOKENID=UUID format.")] [ValidateNotNullOrEmpty] public string? ApiToken { get; set; } /// When specified, skips TLS certificate validation for the server. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Skip TLS certificate validation.")] public SwitchParameter SkipCertificateCheck { get; set; } /// When specified, writes the resulting PveSession object to the pipeline. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Output the session object to the pipeline.")] public SwitchParameter PassThru { get; set; } protected override void ProcessRecord() @@ -116,6 +116,9 @@ namespace PSProxmoxVE.Cmdlets.Connection ModuleState.ActiveSession = session; + if (SkipCertificateCheck.IsPresent) + WriteWarning("TLS certificate validation is disabled for this session. Connections are susceptible to man-in-the-middle attacks. Use only in trusted networks or test environments."); + WriteVerbose($"Connected to {Server}:{Port} as {session.AuthMode} (PVE {session.ServerVersion})."); if (PassThru.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Connection/TestPveConnectionCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Connection/TestPveConnectionCmdlet.cs index 77bac3e..7f50a18 100644 --- a/src/PSProxmoxVE/Cmdlets/Connection/TestPveConnectionCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Connection/TestPveConnectionCmdlet.cs @@ -19,7 +19,7 @@ namespace PSProxmoxVE.Cmdlets.Connection /// When specified, writes the PveSession object instead of a boolean. /// Nothing is written if no session is active. /// - [Parameter(Mandatory = false, ParameterSetName = "Detailed")] + [Parameter(Mandatory = false, ParameterSetName = "Detailed", HelpMessage = "Return the session object instead of a boolean.")] public SwitchParameter Detailed { get; set; } protected override void ProcessRecord() diff --git a/src/PSProxmoxVE/Cmdlets/Containers/CopyPveContainerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/CopyPveContainerCmdlet.cs index f505adb..fdc0a17 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/CopyPveContainerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/CopyPveContainerCmdlet.cs @@ -20,31 +20,33 @@ namespace PSProxmoxVE.Cmdlets.Containers /// /// The node on which the source container resides. /// - [Parameter(Mandatory = true)] + [Parameter(Mandatory = true, HelpMessage = "The node where the source container resides.")] public string SourceNode { get; set; } = string.Empty; /// /// The ID of the source container to clone. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// The container ID to assign to the new clone. When omitted, the next available ID is used. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Container ID for the new clone.")] + [ValidateRange(100, 999999999)] public int? NewVmId { get; set; } /// /// The hostname for the new clone. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Hostname for the new clone.")] public string? NewName { get; set; } /// /// The target node for the clone. Defaults to the source node. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Target node for the clone.")] public string? TargetNode { get; set; } /// @@ -53,19 +55,19 @@ namespace PSProxmoxVE.Cmdlets.Containers /// A full clone is required when the source container is not a template. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Perform a full (non-linked) clone.")] public SwitchParameter Full { get; set; } /// /// Target storage pool for the full clone root filesystem. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The storage pool name.")] public string? Storage { get; set; } /// /// When specified, waits for the clone task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -77,6 +79,7 @@ namespace PSProxmoxVE.Cmdlets.Containers var session = GetSession(); var containerService = new ContainerService(); + WriteVerbose($"Cloning container {VmId}..."); var task = containerService.CloneContainer( session, SourceNode, diff --git a/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerCmdlet.cs index 9709284..dd7b0b1 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerCmdlet.cs @@ -23,36 +23,39 @@ namespace PSProxmoxVE.Cmdlets.Containers /// When omitted, containers from all nodes are returned. /// /// - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string? Node { get; set; } /// /// Filter results to the container with this ID. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int? VmId { get; set; } /// /// Filter results to containers whose name matches this value (case-insensitive, contains match). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by name.")] public string? Name { get; set; } /// /// Filter results to containers in the specified status (e.g., "running", "stopped"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by status (e.g. running, stopped).")] public string? Status { get; set; } /// /// Filter results to containers that have the specified tag (substring match against the semicolon-separated tags field). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by tag.")] public string? Tag { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose("Getting containers..."); var service = new ContainerService(); IEnumerable containers = service.GetContainers(session, Node); diff --git a/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerConfigCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerConfigCmdlet.cs index 5cafd67..de3d263 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerConfigCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerConfigCmdlet.cs @@ -20,13 +20,14 @@ namespace PSProxmoxVE.Cmdlets.Containers /// The node on which the container resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the container whose configuration to retrieve. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } protected override void ProcessRecord() @@ -34,6 +35,7 @@ namespace PSProxmoxVE.Cmdlets.Containers var session = GetSession(); var containerService = new ContainerService(); + WriteVerbose($"Getting config for container {VmId} on node '{Node}'..."); var config = containerService.GetContainerConfig(session, Node, VmId); WriteObject(config); } diff --git a/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerSnapshotCmdlet.cs new file mode 100644 index 0000000..9dfd7ee --- /dev/null +++ b/src/PSProxmoxVE/Cmdlets/Containers/GetPveContainerSnapshotCmdlet.cs @@ -0,0 +1,54 @@ +using System.Linq; +using System.Management.Automation; +using PSProxmoxVE.Core.Models.Vms; +using PSProxmoxVE.Core.Services; + +namespace PSProxmoxVE.Cmdlets.Containers +{ + /// + /// Lists snapshots for a Proxmox VE container. + /// + /// Returns all snapshots for the specified LXC container on the given node. + /// VmId can be piped from Get-PveContainer. + /// + /// + [Cmdlet(VerbsCommon.Get, "PveContainerSnapshot")] + [OutputType(typeof(PveSnapshot))] + public class GetPveContainerSnapshotCmdlet : PveCmdletBase + { + /// The Proxmox VE node name. + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] + public string Node { get; set; } = string.Empty; + + /// + /// The container identifier. Accepts pipeline input from Get-PveContainer (PveContainer.VmId). + /// + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] + public int VmId { get; set; } + + /// Optional filter: return only the snapshot with this name. + [Parameter(Mandatory = false, HelpMessage = "Filter by snapshot name.")] + public string? Name { get; set; } + + protected override void ProcessRecord() + { + var session = GetSession(); + + WriteVerbose($"Getting snapshots for container {VmId} on node '{Node}'..."); + var service = new ContainerService(); + + var snapshots = service.GetContainerSnapshots(session, Node, VmId); + + if (!string.IsNullOrEmpty(Name)) + snapshots = snapshots.Where(s => s.Name == Name).ToArray(); + + foreach (var snapshot in snapshots) + { + snapshot.VmId = VmId; + snapshot.Node = Node; + WriteObject(snapshot); + } + } + } +} diff --git a/src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerCmdlet.cs index 6d87b8d..30274db 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerCmdlet.cs @@ -22,49 +22,50 @@ namespace PSProxmoxVE.Cmdlets.Containers /// /// The node on which to create the container. /// - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The container ID to assign. When omitted, the next available ID is used. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int? VmId { get; set; } /// /// The hostname to assign to the container. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The hostname for the container.")] public string? Hostname { get; set; } /// /// Memory limit in MiB. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Memory limit in MiB.")] public int? Memory { get; set; } /// /// Swap size in MiB. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Swap size in MiB.")] public int? Swap { get; set; } /// /// Number of CPU cores to allocate to the container. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Number of CPU cores to allocate.")] public int? Cores { get; set; } /// /// Size of the root filesystem (e.g., "8G"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Size of the root filesystem (e.g. 8G).")] public string? RootFsSize { get; set; } /// /// Storage pool for the root filesystem (e.g., "local-lvm"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Storage pool for the root filesystem.")] public string? RootFsStorage { get; set; } /// @@ -72,19 +73,19 @@ namespace PSProxmoxVE.Cmdlets.Containers /// The OS template to use (e.g., "local:vztmpl/ubuntu-22.04-standard_22.04-1_amd64.tar.zst"). /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "OS template to use for the container.")] public string? OsTemplate { get; set; } /// /// Root password for the container. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Root password for the container.")] public System.Security.SecureString? Password { get; set; } /// /// SSH public keys to inject for the root user (newline-separated). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "SSH public keys for the root user.")] public string? SshPublicKeys { get; set; } /// @@ -93,31 +94,31 @@ namespace PSProxmoxVE.Cmdlets.Containers /// To create a privileged container, explicitly set this to $false. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Run in unprivileged mode (default true).")] public SwitchParameter Unprivileged { get; set; } = new SwitchParameter(true); /// /// Network interface model (e.g., "eth0"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Network interface name (e.g. eth0).")] public string? Network { get; set; } /// /// Network bridge to attach to (e.g., "vmbr0"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Network bridge to attach to (e.g. vmbr0).")] public string? Bridge { get; set; } /// /// When specified, starts the container after creation. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Start the container after creation.")] public SwitchParameter Start { get; set; } /// /// When specified, waits for the creation task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -128,6 +129,7 @@ namespace PSProxmoxVE.Cmdlets.Containers var session = GetSession(); var containerService = new ContainerService(); + WriteVerbose($"Creating container on node '{Node}'..."); var config = new Dictionary(); if (VmId.HasValue) diff --git a/src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerSnapshotCmdlet.cs new file mode 100644 index 0000000..309c084 --- /dev/null +++ b/src/PSProxmoxVE/Cmdlets/Containers/NewPveContainerSnapshotCmdlet.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Client; +using PSProxmoxVE.Core.Models.Vms; + +namespace PSProxmoxVE.Cmdlets.Containers +{ + /// + /// Creates a snapshot of a Proxmox VE container. + /// + /// Takes a snapshot of the specified LXC container. + /// Returns a PveTask. Use -Wait to block until the snapshot completes. + /// + /// + [Cmdlet(VerbsCommon.New, "PveContainerSnapshot", SupportsShouldProcess = true)] + [OutputType(typeof(PveTask))] + public class NewPveContainerSnapshotCmdlet : PveCmdletBase + { + /// The Proxmox VE node name. + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] + public string Node { get; set; } = string.Empty; + + /// The container identifier. Accepts pipeline input from Get-PveContainer (PveContainer.VmId). + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] + public int VmId { get; set; } + + /// The snapshot name (alphanumeric, hyphens and underscores). + [Parameter(Mandatory = true, Position = 2, HelpMessage = "The snapshot name.")] + public string Name { get; set; } = string.Empty; + + /// Optional human-readable description for the snapshot. + [Parameter(Mandatory = false, HelpMessage = "Description for the snapshot.")] + public string? Description { get; set; } + + /// When specified, waits for the snapshot task to complete before returning. + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] + public SwitchParameter Wait { get; set; } + + protected override void ProcessRecord() + { + if (!ShouldProcess($"Container {VmId} on {Node}", $"Create snapshot '{Name}'")) + return; + + var session = GetSession(); + using var client = new PveHttpClient(session); + + WriteVerbose($"Creating snapshot '{Name}' for container {VmId}..."); + var data = new Dictionary + { + ["snapname"] = Name + }; + if (!string.IsNullOrEmpty(Description)) data["description"] = Description!; + + var json = client.PostAsync($"nodes/{Node}/lxc/{VmId}/snapshot", data).GetAwaiter().GetResult(); + var root = JObject.Parse(json); + var upid = root["data"]?.ToString() ?? string.Empty; + + var task = new PveTask { Upid = upid, Node = Node, Status = "running" }; + + if (Wait.IsPresent && !string.IsNullOrEmpty(upid)) + { + task = WaitForTask(client, Node, upid); + } + + WriteObject(task); + } + + private static PveTask WaitForTask(PveHttpClient client, string node, string upid) + { + var encodedUpid = Uri.EscapeDataString(upid); + var statusResource = $"nodes/{node}/tasks/{encodedUpid}/status"; + while (true) + { + System.Threading.Thread.Sleep(2000); + var statusJson = client.GetAsync(statusResource).GetAwaiter().GetResult(); + var statusRoot = JObject.Parse(statusJson); + var d = statusRoot["data"]; + if (d?["status"]?.ToString() == "stopped") + return new PveTask { Upid = upid, Node = node, Status = "stopped", ExitStatus = d["exitstatus"]?.ToString() }; + } + } + } +} diff --git a/src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerCmdlet.cs index f7b38a7..58fdfcd 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerCmdlet.cs @@ -22,13 +22,14 @@ namespace PSProxmoxVE.Cmdlets.Containers /// The node on which the container resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the container to remove. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// @@ -36,7 +37,7 @@ namespace PSProxmoxVE.Cmdlets.Containers /// When specified, also removes the container from HA resource configuration and replication jobs. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Remove all associated resources.")] public SwitchParameter Purge { get; set; } /// @@ -44,13 +45,13 @@ namespace PSProxmoxVE.Cmdlets.Containers /// When specified, bypasses locks and forces removal even if a lock is set on the container. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Force the operation without additional checks.")] public SwitchParameter Force { get; set; } /// /// When specified, waits for the removal task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -61,6 +62,7 @@ namespace PSProxmoxVE.Cmdlets.Containers var session = GetSession(); var containerService = new ContainerService(); + WriteVerbose($"Removing container {VmId} from node '{Node}'..."); var task = containerService.RemoveContainer(session, Node, VmId, Purge.IsPresent); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerSnapshotCmdlet.cs new file mode 100644 index 0000000..f4f0b9d --- /dev/null +++ b/src/PSProxmoxVE/Cmdlets/Containers/RemovePveContainerSnapshotCmdlet.cs @@ -0,0 +1,78 @@ +using System; +using System.Management.Automation; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Client; +using PSProxmoxVE.Core.Models.Vms; + +namespace PSProxmoxVE.Cmdlets.Containers +{ + /// + /// Removes a snapshot from a Proxmox VE container. + /// + /// Deletes the specified snapshot from the LXC container. + /// Returns a PveTask. Use -Wait to block until removal completes. + /// + /// + [Cmdlet(VerbsCommon.Remove, "PveContainerSnapshot", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] + [OutputType(typeof(PveTask))] + public class RemovePveContainerSnapshotCmdlet : PveCmdletBase + { + /// The Proxmox VE node name. + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] + public string Node { get; set; } = string.Empty; + + /// The container identifier. + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] + public int VmId { get; set; } + + /// + /// The snapshot name to remove. Accepts pipeline input from Get-PveContainerSnapshot (PveSnapshot.Name). + /// + [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true, HelpMessage = "The snapshot name to remove.")] + public string Name { get; set; } = string.Empty; + + /// When specified, waits for the removal task to complete before returning. + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] + public SwitchParameter Wait { get; set; } + + protected override void ProcessRecord() + { + var session = GetSession(); + + if (!ShouldProcess($"Container {VmId} snapshot '{Name}' on {Node}", "Remove container snapshot")) + return; + + WriteVerbose($"Removing snapshot '{Name}' from container {VmId}..."); + using var client = new PveHttpClient(session); + + var json = client.DeleteAsync($"nodes/{Node}/lxc/{VmId}/snapshot/{Name}").GetAwaiter().GetResult(); + var root = JObject.Parse(json); + var upid = root["data"]?.ToString() ?? string.Empty; + + var task = new PveTask { Upid = upid, Node = Node, Status = "running" }; + + if (Wait.IsPresent && !string.IsNullOrEmpty(upid)) + { + task = WaitForTask(client, Node, upid); + } + + WriteObject(task); + } + + private static PveTask WaitForTask(PveHttpClient client, string node, string upid) + { + var encodedUpid = Uri.EscapeDataString(upid); + var statusResource = $"nodes/{node}/tasks/{encodedUpid}/status"; + while (true) + { + System.Threading.Thread.Sleep(2000); + var statusJson = client.GetAsync(statusResource).GetAwaiter().GetResult(); + var statusRoot = JObject.Parse(statusJson); + var d = statusRoot["data"]; + if (d?["status"]?.ToString() == "stopped") + return new PveTask { Upid = upid, Node = node, Status = "stopped", ExitStatus = d["exitstatus"]?.ToString() }; + } + } + } +} diff --git a/src/PSProxmoxVE/Cmdlets/Containers/RestartPveContainerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/RestartPveContainerCmdlet.cs index c7e3986..c171f9d 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/RestartPveContainerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/RestartPveContainerCmdlet.cs @@ -21,13 +21,14 @@ namespace PSProxmoxVE.Cmdlets.Containers /// The node on which the container resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the container to restart. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// @@ -35,13 +36,13 @@ namespace PSProxmoxVE.Cmdlets.Containers /// Timeout in seconds for the graceful shutdown phase. Defaults to 60 seconds. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Maximum time to wait for the task.")] public int Timeout { get; set; } = 60; /// /// When specified, waits for both shutdown and start tasks to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -53,6 +54,8 @@ namespace PSProxmoxVE.Cmdlets.Containers var containerService = new ContainerService(); var taskService = new TaskService(); + WriteVerbose($"Restarting container {VmId} on node '{Node}'..."); + // Graceful shutdown var shutdownTask = containerService.ShutdownContainer(session, Node, VmId, Timeout); diff --git a/src/PSProxmoxVE/Cmdlets/Containers/RestorePveContainerSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/RestorePveContainerSnapshotCmdlet.cs new file mode 100644 index 0000000..d5977fb --- /dev/null +++ b/src/PSProxmoxVE/Cmdlets/Containers/RestorePveContainerSnapshotCmdlet.cs @@ -0,0 +1,81 @@ +using System; +using System.Management.Automation; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Client; +using PSProxmoxVE.Core.Models.Vms; + +namespace PSProxmoxVE.Cmdlets.Containers +{ + /// + /// Rolls back a Proxmox VE container to a snapshot. + /// + /// Restores the container state to the specified snapshot, discarding all changes made since + /// the snapshot was taken. This is a destructive operation. Returns a PveTask. + /// Use -Wait to block until rollback completes. + /// + /// + [Cmdlet(VerbsData.Restore, "PveContainerSnapshot", + SupportsShouldProcess = true, + ConfirmImpact = ConfirmImpact.High)] + [OutputType(typeof(PveTask))] + public class RestorePveContainerSnapshotCmdlet : PveCmdletBase + { + /// The Proxmox VE node name. + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] + public string Node { get; set; } = string.Empty; + + /// The container identifier. + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] + public int VmId { get; set; } + + /// + /// The snapshot name to roll back to. Accepts pipeline input from Get-PveContainerSnapshot (PveSnapshot.Name). + /// + [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true, HelpMessage = "The snapshot name to roll back to.")] + public string Name { get; set; } = string.Empty; + + /// When specified, waits for the rollback task to complete before returning. + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] + public SwitchParameter Wait { get; set; } + + protected override void ProcessRecord() + { + var session = GetSession(); + + if (!ShouldProcess($"Container {VmId} on {Node}", $"Restore snapshot '{Name}' (current state will be lost)")) + return; + + WriteVerbose($"Restoring snapshot '{Name}' on container {VmId}..."); + using var client = new PveHttpClient(session); + + var json = client.PostAsync($"nodes/{Node}/lxc/{VmId}/snapshot/{Name}/rollback").GetAwaiter().GetResult(); + var root = JObject.Parse(json); + var upid = root["data"]?.ToString() ?? string.Empty; + + var task = new PveTask { Upid = upid, Node = Node, Status = "running" }; + + if (Wait.IsPresent && !string.IsNullOrEmpty(upid)) + { + task = WaitForTask(client, Node, upid); + } + + WriteObject(task); + } + + private static PveTask WaitForTask(PveHttpClient client, string node, string upid) + { + var encodedUpid = Uri.EscapeDataString(upid); + var statusResource = $"nodes/{node}/tasks/{encodedUpid}/status"; + while (true) + { + System.Threading.Thread.Sleep(2000); + var statusJson = client.GetAsync(statusResource).GetAwaiter().GetResult(); + var statusRoot = JObject.Parse(statusJson); + var d = statusRoot["data"]; + if (d?["status"]?.ToString() == "stopped") + return new PveTask { Upid = upid, Node = node, Status = "stopped", ExitStatus = d["exitstatus"]?.ToString() }; + } + } + } +} diff --git a/src/PSProxmoxVE/Cmdlets/Containers/SetPveContainerConfigCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/SetPveContainerConfigCmdlet.cs index 822e0a2..d1a9abd 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/SetPveContainerConfigCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/SetPveContainerConfigCmdlet.cs @@ -21,61 +21,62 @@ namespace PSProxmoxVE.Cmdlets.Containers /// The node on which the container resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the container to configure. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// The hostname to assign to the container. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The hostname for the container.")] public string? Hostname { get; set; } /// /// Number of CPU cores to allocate to the container. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Number of CPU cores to allocate.")] public int? Cores { get; set; } /// /// Memory limit in MiB. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Memory limit in MiB.")] public int? Memory { get; set; } /// /// Swap size in MiB. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Swap size in MiB.")] public int? Swap { get; set; } /// /// Human-readable description / notes for the container. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Description or notes for the container.")] public string? Description { get; set; } /// /// Semicolon-separated list of tags to assign to the container. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Semicolon-separated list of tags.")] public string? Tags { get; set; } /// /// DNS nameservers (space-separated). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "DNS nameservers (space-separated).")] public string? Nameserver { get; set; } /// /// DNS search domain. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "DNS search domain.")] public string? SearchDomain { get; set; } /// @@ -85,7 +86,7 @@ namespace PSProxmoxVE.Cmdlets.Containers /// Values are merged after named parameters and can override them. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Extra config keys as a hashtable.")] public Hashtable? AdditionalConfig { get; set; } /// @@ -94,7 +95,7 @@ namespace PSProxmoxVE.Cmdlets.Containers /// Maps to the PVE API "delete" parameter. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Comma-separated config keys to delete.")] public string? Delete { get; set; } protected override void ProcessRecord() @@ -105,6 +106,7 @@ namespace PSProxmoxVE.Cmdlets.Containers var session = GetSession(); var containerService = new ContainerService(); + WriteVerbose($"Updating config for container {VmId} on node '{Node}'..."); var config = new Dictionary(); if (!string.IsNullOrEmpty(Hostname)) diff --git a/src/PSProxmoxVE/Cmdlets/Containers/StartPveContainerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/StartPveContainerCmdlet.cs index 6cceea7..9bd21f7 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/StartPveContainerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/StartPveContainerCmdlet.cs @@ -20,19 +20,20 @@ namespace PSProxmoxVE.Cmdlets.Containers /// The node on which the container resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the container to start. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// When specified, waits for the start task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -43,6 +44,7 @@ namespace PSProxmoxVE.Cmdlets.Containers var session = GetSession(); var containerService = new ContainerService(); + WriteVerbose($"Starting container {VmId} on node '{Node}'..."); var task = containerService.StartContainer(session, Node, VmId); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Containers/StopPveContainerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Containers/StopPveContainerCmdlet.cs index 70c2129..b964338 100644 --- a/src/PSProxmoxVE/Cmdlets/Containers/StopPveContainerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Containers/StopPveContainerCmdlet.cs @@ -13,7 +13,7 @@ namespace PSProxmoxVE.Cmdlets.Containers /// Use -Wait to block until the stop task completes. /// /// - [Cmdlet(VerbsLifecycle.Stop, "PveContainer", SupportsShouldProcess = true)] + [Cmdlet(VerbsLifecycle.Stop, "PveContainer", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] [OutputType(typeof(PveTask))] public sealed class StopPveContainerCmdlet : PveCmdletBase { @@ -22,19 +22,20 @@ namespace PSProxmoxVE.Cmdlets.Containers /// The node on which the container resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the container to stop. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The container identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// When specified, waits for the stop task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -45,6 +46,7 @@ namespace PSProxmoxVE.Cmdlets.Containers var session = GetSession(); var containerService = new ContainerService(); + WriteVerbose($"Stopping container {VmId} on node '{Node}'..."); var task = containerService.StopContainer(session, Node, VmId); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Network/GetPveNetworkCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/GetPveNetworkCmdlet.cs index 31c0bdd..7bd7bd9 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/GetPveNetworkCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/GetPveNetworkCmdlet.cs @@ -20,16 +20,16 @@ namespace PSProxmoxVE.Cmdlets.Network /// /// The Proxmox VE node name. Accepts pipeline input from Get-PveNode (PveNode.Name). /// - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] [Alias("NodeName")] public string Node { get; set; } = string.Empty; /// Filter results to this specific interface name (e.g., "vmbr0"). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The network interface name.")] public string? Iface { get; set; } /// Filter by interface type (e.g., "bridge", "bond", "eth", "vlan"). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by interface type (e.g. bridge, bond).")] [ValidateSet("bridge", "bond", "eth", "alias", "vlan", "OVSBridge", "OVSBond", "OVSPort", "OVSIntPort", "any_bridge", "any_local_bridge", IgnoreCase = true)] public string? Type { get; set; } @@ -39,6 +39,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Getting network interfaces on node '{Node}'..."); var resource = $"nodes/{Node}/network"; if (!string.IsNullOrEmpty(Type)) resource += $"?type={Uri.EscapeDataString(Type)}"; diff --git a/src/PSProxmoxVE/Cmdlets/Network/GetPveSdnVnetCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/GetPveSdnVnetCmdlet.cs index 450c7c3..f8a629d 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/GetPveSdnVnetCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/GetPveSdnVnetCmdlet.cs @@ -17,11 +17,11 @@ namespace PSProxmoxVE.Cmdlets.Network public class GetPveSdnVnetCmdlet : PveCmdletBase { /// Filter VNets to a specific zone. - [Parameter(Mandatory = false, Position = 0)] + [Parameter(Mandatory = false, Position = 0, HelpMessage = "The SDN zone name.")] public string? Zone { get; set; } /// Optional VNet identifier to retrieve a specific VNet. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The SDN VNet name.")] public string? Vnet { get; set; } protected override void ProcessRecord() @@ -29,6 +29,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose("Getting SDN VNets..."); var json = client.GetAsync("cluster/sdn/vnets").GetAwaiter().GetResult(); var root = JObject.Parse(json); var data = root["data"] as JArray ?? new JArray(); diff --git a/src/PSProxmoxVE/Cmdlets/Network/GetPveSdnZoneCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/GetPveSdnZoneCmdlet.cs index 8772187..f53277c 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/GetPveSdnZoneCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/GetPveSdnZoneCmdlet.cs @@ -16,7 +16,7 @@ namespace PSProxmoxVE.Cmdlets.Network public class GetPveSdnZoneCmdlet : PveCmdletBase { /// Optional zone identifier to retrieve a specific zone. - [Parameter(Mandatory = false, Position = 0)] + [Parameter(Mandatory = false, Position = 0, HelpMessage = "The SDN zone name.")] public string? Zone { get; set; } protected override void ProcessRecord() @@ -24,6 +24,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose("Getting SDN zones..."); var resource = "cluster/sdn/zones"; var json = client.GetAsync(resource).GetAwaiter().GetResult(); var root = JObject.Parse(json); diff --git a/src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs index d4c03f3..d82b1c3 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs @@ -19,11 +19,11 @@ namespace PSProxmoxVE.Cmdlets.Network public class InvokePveNetworkApplyCmdlet : PveCmdletBase { /// The Proxmox VE node on which to apply network changes. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// When specified, waits for the apply task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -34,6 +34,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Applying network configuration on node '{Node}'..."); var json = client.PutAsync($"nodes/{Node}/network").GetAwaiter().GetResult(); var root = JObject.Parse(json); var upid = root["data"]?.ToString() ?? string.Empty; diff --git a/src/PSProxmoxVE/Cmdlets/Network/NewPveNetworkCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/NewPveNetworkCmdlet.cs index bb65721..83675e3 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/NewPveNetworkCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/NewPveNetworkCmdlet.cs @@ -15,53 +15,53 @@ namespace PSProxmoxVE.Cmdlets.Network public class NewPveNetworkCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The interface name (e.g., "vmbr1", "bond0"). - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The network interface name.")] public string Iface { get; set; } = string.Empty; /// The interface type. - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, HelpMessage = "The interface type (e.g. bridge, bond, vlan).")] [ValidateSet("bridge", "bond", "eth", "alias", "vlan", "OVSBridge", "OVSBond", "OVSPort", "OVSIntPort", IgnoreCase = true)] public string Type { get; set; } = string.Empty; /// IPv4 address for the interface. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv4 address for the interface.")] public string? Address { get; set; } /// IPv4 subnet mask. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv4 subnet mask.")] public string? Netmask { get; set; } /// IPv4 gateway address. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv4 gateway address.")] public string? Gateway { get; set; } /// Bridge ports (space-separated interface names, for bridge type). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Bridge ports (space-separated interface names).")] public string? BridgePorts { get; set; } /// Bond slave interfaces (space-separated names, for bond type). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Bond slave interfaces (space-separated names).")] public string? BondSlaves { get; set; } /// VLAN tag ID (for vlan type). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "VLAN tag ID.")] public int? VlanId { get; set; } /// MTU override. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "MTU override.")] public int? Mtu { get; set; } /// Configure this interface to start automatically at boot. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Start interface automatically at boot.")] public SwitchParameter Autostart { get; set; } /// Optional comments/notes for this interface. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Comments or notes for this interface.")] public string? Comments { get; set; } protected override void ProcessRecord() @@ -72,6 +72,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Creating network interface '{Iface}' on node '{Node}'..."); var data = new Dictionary { ["iface"] = Iface, diff --git a/src/PSProxmoxVE/Cmdlets/Network/NewPveSdnVnetCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/NewPveSdnVnetCmdlet.cs index b6cb1bd..f9d7011 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/NewPveSdnVnetCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/NewPveSdnVnetCmdlet.cs @@ -14,23 +14,23 @@ namespace PSProxmoxVE.Cmdlets.Network public class NewPveSdnVnetCmdlet : PveCmdletBase { /// The VNet identifier (alphanumeric, up to 8 characters). - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The SDN VNet name.")] public string Vnet { get; set; } = string.Empty; /// The SDN zone this VNet belongs to. - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The SDN zone name.")] public string Zone { get; set; } = string.Empty; /// VLAN tag for VLAN-type zones. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "VLAN tag for VLAN-type zones.")] public int? Tag { get; set; } /// Optional alias/description for the VNet. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Alias or description for the VNet.")] public string? Alias { get; set; } /// Enable VLAN awareness on this VNet. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Enable VLAN awareness on this VNet.")] public SwitchParameter VlanAware { get; set; } protected override void ProcessRecord() @@ -41,6 +41,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Creating SDN VNet '{Vnet}'..."); var data = new Dictionary { ["vnet"] = Vnet, diff --git a/src/PSProxmoxVE/Cmdlets/Network/NewPveSdnZoneCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/NewPveSdnZoneCmdlet.cs index 1f4b3be..1ba4193 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/NewPveSdnZoneCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/NewPveSdnZoneCmdlet.cs @@ -14,40 +14,40 @@ namespace PSProxmoxVE.Cmdlets.Network public class NewPveSdnZoneCmdlet : PveCmdletBase { /// The zone identifier (alphanumeric, hyphens allowed). - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The SDN zone name.")] public string Zone { get; set; } = string.Empty; /// The zone type. - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The zone type (e.g. vlan, vxlan, evpn, simple).")] [ValidateSet("vlan", "vxlan", "evpn", "simple", "qinq", IgnoreCase = true)] public string Type { get; set; } = string.Empty; /// VXLAN peer list or multicast address (for vxlan/evpn types). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "VXLAN peer list or multicast address.")] public string? Peers { get; set; } /// Bridge interface this zone attaches to (for vlan/qinq types). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Bridge interface for this zone.")] public string? Bridge { get; set; } /// MTU override for this zone. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "MTU override for this zone.")] public int? Mtu { get; set; } /// DNS server for automatic DNS registration. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "DNS server for automatic registration.")] public string? Dns { get; set; } /// Reverse DNS server. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Reverse DNS server.")] public string? ReverseDns { get; set; } /// DNS zone name for registration. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "DNS zone name for registration.")] public string? DnsZone { get; set; } /// IPAM plugin to use for this zone. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPAM plugin to use.")] public string? Ipam { get; set; } protected override void ProcessRecord() @@ -58,6 +58,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Creating SDN zone '{Zone}'..."); var data = new Dictionary { ["zone"] = Zone, diff --git a/src/PSProxmoxVE/Cmdlets/Network/RemovePveNetworkCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/RemovePveNetworkCmdlet.cs index ef0416d..99a0061 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/RemovePveNetworkCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/RemovePveNetworkCmdlet.cs @@ -14,11 +14,11 @@ namespace PSProxmoxVE.Cmdlets.Network public class RemovePveNetworkCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The interface name to remove. - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The network interface name.")] public string Iface { get; set; } = string.Empty; protected override void ProcessRecord() @@ -29,6 +29,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Removing network interface '{Iface}' on node '{Node}'..."); client.DeleteAsync($"nodes/{Node}/network/{Iface}").GetAwaiter().GetResult(); } } diff --git a/src/PSProxmoxVE/Cmdlets/Network/RemovePveSdnVnetCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/RemovePveSdnVnetCmdlet.cs index 679f79a..11e08ca 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/RemovePveSdnVnetCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/RemovePveSdnVnetCmdlet.cs @@ -13,7 +13,7 @@ namespace PSProxmoxVE.Cmdlets.Network public class RemovePveSdnVnetCmdlet : PveCmdletBase { /// The VNet identifier to remove. - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN VNet name.")] public string Vnet { get; set; } = string.Empty; protected override void ProcessRecord() @@ -24,6 +24,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Removing SDN VNet '{Vnet}'..."); client.DeleteAsync($"cluster/sdn/vnets/{Vnet}").GetAwaiter().GetResult(); } } diff --git a/src/PSProxmoxVE/Cmdlets/Network/RemovePveSdnZoneCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/RemovePveSdnZoneCmdlet.cs index 02d7c5c..5702eab 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/RemovePveSdnZoneCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/RemovePveSdnZoneCmdlet.cs @@ -14,7 +14,7 @@ namespace PSProxmoxVE.Cmdlets.Network public class RemovePveSdnZoneCmdlet : PveCmdletBase { /// The zone identifier to remove. - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The SDN zone name.")] public string Zone { get; set; } = string.Empty; protected override void ProcessRecord() @@ -25,6 +25,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Removing SDN zone '{Zone}'..."); client.DeleteAsync($"cluster/sdn/zones/{Zone}").GetAwaiter().GetResult(); } } diff --git a/src/PSProxmoxVE/Cmdlets/Network/SetPveNetworkCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/SetPveNetworkCmdlet.cs index 91a5457..090f23f 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/SetPveNetworkCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/SetPveNetworkCmdlet.cs @@ -15,55 +15,55 @@ namespace PSProxmoxVE.Cmdlets.Network public class SetPveNetworkCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The interface name to modify. - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The network interface name.")] public string Iface { get; set; } = string.Empty; /// IPv4 address for the interface. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv4 address for the interface.")] public string? Address { get; set; } /// IPv4 subnet mask. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv4 subnet mask.")] public string? Netmask { get; set; } /// IPv4 gateway address. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv4 gateway address.")] public string? Gateway { get; set; } /// IPv6 address for the interface. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv6 address for the interface.")] public string? Address6 { get; set; } /// IPv6 prefix length. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv6 prefix length.")] public int? Netmask6 { get; set; } /// IPv6 gateway address. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "IPv6 gateway address.")] public string? Gateway6 { get; set; } /// Bridge ports (space-separated interface names). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Bridge ports (space-separated interface names).")] public string? BridgePorts { get; set; } /// Bond slave interfaces (space-separated names). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Bond slave interfaces (space-separated names).")] public string? BondSlaves { get; set; } /// MTU override. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "MTU override.")] public int? Mtu { get; set; } /// Configure this interface to start automatically at boot. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Start interface automatically at boot.")] public SwitchParameter Autostart { get; set; } /// Optional comments/notes for this interface. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Comments or notes for this interface.")] public string? Comments { get; set; } protected override void ProcessRecord() @@ -74,6 +74,7 @@ namespace PSProxmoxVE.Cmdlets.Network var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Updating network interface '{Iface}' on node '{Node}'..."); var data = new Dictionary(); if (!string.IsNullOrEmpty(Address)) data["address"] = Address!; diff --git a/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeCmdlet.cs index a8c63f1..bc9038c 100644 --- a/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeCmdlet.cs @@ -20,7 +20,7 @@ namespace PSProxmoxVE.Cmdlets.Nodes public sealed class GetPveNodeCmdlet : PveCmdletBase { /// Optional node name filter. When specified, only the matching node is returned. - [Parameter(Mandatory = false, Position = 0)] + [Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by node name.")] [ValidateNotNullOrEmpty] public string? Name { get; set; } @@ -28,6 +28,7 @@ namespace PSProxmoxVE.Cmdlets.Nodes { var session = GetSession(); + WriteVerbose("Getting cluster nodes..."); string responseBody; try { diff --git a/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeStatusCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeStatusCmdlet.cs index d618fd2..7215fe1 100644 --- a/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeStatusCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeStatusCmdlet.cs @@ -23,7 +23,7 @@ namespace PSProxmoxVE.Cmdlets.Nodes /// /// Name of the node to query. Accepts pipeline input via the PveNode.Name property. /// - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] [ValidateNotNullOrEmpty] public string Node { get; set; } = string.Empty; @@ -31,6 +31,7 @@ namespace PSProxmoxVE.Cmdlets.Nodes { var session = GetSession(); + WriteVerbose($"Getting status for node '{Node}'..."); var resource = $"nodes/{Uri.EscapeDataString(Node)}/status"; string responseBody; diff --git a/src/PSProxmoxVE/Cmdlets/Snapshots/GetPveSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Snapshots/GetPveSnapshotCmdlet.cs index 6f506b6..9339409 100644 --- a/src/PSProxmoxVE/Cmdlets/Snapshots/GetPveSnapshotCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Snapshots/GetPveSnapshotCmdlet.cs @@ -17,22 +17,25 @@ namespace PSProxmoxVE.Cmdlets.Snapshots public class GetPveSnapshotCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The VM identifier. Accepts pipeline input from Get-PveVm (PveVm.VmId). /// - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// Optional filter: return only the snapshot with this name. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by snapshot name.")] public string? Name { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose($"Getting snapshots for VM {VmId} on node '{Node}'..."); var service = new SnapshotService(); var snapshots = service.GetSnapshots(session, Node, VmId); diff --git a/src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs index 85e827e..c144e76 100644 --- a/src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs @@ -19,27 +19,28 @@ namespace PSProxmoxVE.Cmdlets.Snapshots public class NewPveSnapshotCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. Accepts pipeline input from Get-PveVm (PveVm.VmId). - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// The snapshot name (alphanumeric, hyphens and underscores). - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, HelpMessage = "The snapshot name.")] public string Name { get; set; } = string.Empty; /// Optional human-readable description for the snapshot. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Description for the snapshot.")] public string? Description { get; set; } /// When specified, includes the VM memory state in the snapshot. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Include VM memory state in the snapshot.")] public SwitchParameter IncludeVmState { get; set; } /// When specified, waits for the snapshot task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -50,6 +51,7 @@ namespace PSProxmoxVE.Cmdlets.Snapshots var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Creating snapshot '{Name}' for VM {VmId}..."); var data = new Dictionary { ["snapname"] = Name diff --git a/src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs index c961b36..86dfd6b 100644 --- a/src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs @@ -18,21 +18,22 @@ namespace PSProxmoxVE.Cmdlets.Snapshots public class RemovePveSnapshotCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// The snapshot name to remove. Accepts pipeline input from Get-PveSnapshot (PveSnapshot.Name). /// - [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true, HelpMessage = "The snapshot name to remove.")] public string Name { get; set; } = string.Empty; /// When specified, waits for the removal task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -41,6 +42,8 @@ namespace PSProxmoxVE.Cmdlets.Snapshots if (!ShouldProcess($"VM {VmId} snapshot '{Name}' on {Node}", "Remove PVE Snapshot")) return; + + WriteVerbose($"Removing snapshot '{Name}' from VM {VmId}..."); using var client = new PveHttpClient(session); var json = client.DeleteAsync($"nodes/{Node}/qemu/{VmId}/snapshot/{Name}").GetAwaiter().GetResult(); diff --git a/src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs index 8146ffd..2897432 100644 --- a/src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs @@ -21,21 +21,22 @@ namespace PSProxmoxVE.Cmdlets.Snapshots public class RestorePveSnapshotCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// The snapshot name to roll back to. Accepts pipeline input from Get-PveSnapshot (PveSnapshot.Name). /// - [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true, HelpMessage = "The snapshot name to roll back to.")] public string Name { get; set; } = string.Empty; /// When specified, waits for the rollback task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -44,6 +45,8 @@ namespace PSProxmoxVE.Cmdlets.Snapshots if (!ShouldProcess($"VM {VmId} on {Node}", $"Restore snapshot '{Name}' (current state will be lost)")) return; + + WriteVerbose($"Restoring snapshot '{Name}' on VM {VmId}..."); using var client = new PveHttpClient(session); var json = client.PostAsync($"nodes/{Node}/qemu/{VmId}/snapshot/{Name}/rollback").GetAwaiter().GetResult(); diff --git a/src/PSProxmoxVE/Cmdlets/Storage/GetPveStorageCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Storage/GetPveStorageCmdlet.cs index 0cac730..770e757 100644 --- a/src/PSProxmoxVE/Cmdlets/Storage/GetPveStorageCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Storage/GetPveStorageCmdlet.cs @@ -20,25 +20,27 @@ namespace PSProxmoxVE.Cmdlets.Storage /// The Proxmox VE node name. Accepts pipeline input from Get-PveNode (PveNode.Name). /// When omitted the cluster-wide storage list is used. /// - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] [Alias("NodeName")] public string? Node { get; set; } /// Filter results to a specific storage name (e.g., "local", "local-lvm"). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The storage pool name.")] public string? Storage { get; set; } /// Filter results to a specific storage type (e.g., "dir", "nfs", "zfspool"). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by storage type (e.g. dir, nfs, zfspool).")] public string? Type { get; set; } /// Filter results to storages that support the given content type (e.g., "iso", "backup"). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by content type (e.g. iso, backup).")] public string? ContentType { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose("Getting storage pools..."); var service = new StorageService(); var storages = service.GetStorages(session, Node); diff --git a/src/PSProxmoxVE/Cmdlets/Storage/GetPveStorageContentCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Storage/GetPveStorageContentCmdlet.cs index 7382fbb..7387a71 100644 --- a/src/PSProxmoxVE/Cmdlets/Storage/GetPveStorageContentCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Storage/GetPveStorageContentCmdlet.cs @@ -16,22 +16,24 @@ namespace PSProxmoxVE.Cmdlets.Storage public class GetPveStorageContentCmdlet : PveCmdletBase { /// The Proxmox VE node name that hosts the storage. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The storage identifier. Accepts pipeline input from Get-PveStorage (PveStorage.Storage). /// - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The storage pool name.")] public string Storage { get; set; } = string.Empty; /// Filter results to a specific content type (e.g., "iso", "vztmpl", "backup", "images"). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by content type (e.g. iso, vztmpl, backup).")] public string? ContentType { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose($"Getting content for storage '{Storage}' on node '{Node}'..."); var service = new StorageService(); var items = service.GetStorageContent(session, Node, Storage, ContentType); diff --git a/src/PSProxmoxVE/Cmdlets/Storage/InvokePveStorageDownloadCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Storage/InvokePveStorageDownloadCmdlet.cs index 69414d3..c04a5d6 100644 --- a/src/PSProxmoxVE/Cmdlets/Storage/InvokePveStorageDownloadCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Storage/InvokePveStorageDownloadCmdlet.cs @@ -19,28 +19,28 @@ namespace PSProxmoxVE.Cmdlets.Storage public class InvokePveStorageDownloadCmdlet : PveCmdletBase { /// The Proxmox VE node that will perform the download. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The target storage identifier. - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage pool name.")] public string Storage { get; set; } = string.Empty; /// The URL to download the file from. - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, HelpMessage = "The URL to download the file from.")] public string Url { get; set; } = string.Empty; /// The filename to save the downloaded file as on the storage. - [Parameter(Mandatory = true, Position = 3)] + [Parameter(Mandatory = true, Position = 3, HelpMessage = "Filename to save the download as.")] public string Filename { get; set; } = string.Empty; /// The content type category for the downloaded file. Defaults to "iso". - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Content type category. Defaults to iso.")] [ValidateSet("iso", "vztmpl", "backup", "import", IgnoreCase = true)] public string ContentType { get; set; } = "iso"; /// When specified, waits for the download task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -51,6 +51,7 @@ namespace PSProxmoxVE.Cmdlets.Storage var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Downloading '{Url}' to {Node}/{Storage}..."); var resource = $"nodes/{Node}/storage/{Storage}/download-url"; var data = new Dictionary { diff --git a/src/PSProxmoxVE/Cmdlets/Storage/NewPveStorageCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Storage/NewPveStorageCmdlet.cs index 4a9b69f..8e321f1 100644 --- a/src/PSProxmoxVE/Cmdlets/Storage/NewPveStorageCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Storage/NewPveStorageCmdlet.cs @@ -18,61 +18,61 @@ namespace PSProxmoxVE.Cmdlets.Storage public class NewPveStorageCmdlet : PveCmdletBase { /// The unique storage identifier/name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The storage pool name.")] public string Storage { get; set; } = string.Empty; /// The storage type (e.g., "dir", "nfs", "lvm", "zfspool", "cephfs", "rbd"). - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage type (e.g. dir, nfs, lvm, zfspool).")] [ValidateSet("dir", "nfs", "lvm", "lvmthin", "zfspool", "zfs", "cephfs", "rbd", "iscsi", "iscsidirect", "glusterfs", "cifs", "pbs", IgnoreCase = true)] public string Type { get; set; } = string.Empty; /// Comma-separated list of content types to support (e.g., "iso,vztmpl,backup"). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Comma-separated content types to support.")] public string? Content { get; set; } /// Base directory path (for "dir" type storages). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Base directory path (for dir type).")] public string? Path { get; set; } /// NFS/CIFS server hostname or IP address. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "NFS/CIFS server hostname or IP.")] public string? Server { get; set; } /// NFS export path or CIFS share name. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "NFS export path or CIFS share name.")] public string? Export { get; set; } /// LVM volume group name (for "lvm"/"lvmthin" types). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "LVM volume group name.")] public string? VgName { get; set; } /// LVM thin pool name (for "lvmthin" type). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "LVM thin pool name.")] public string? ThinPool { get; set; } /// ZFS pool name (for "zfspool" type). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "ZFS pool name.")] public string? Pool { get; set; } /// Ceph pool name (for "rbd"/"cephfs" types). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Ceph pool name.")] public string? CephPool { get; set; } /// Monitor list for Ceph storages (comma-separated host:port pairs). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Ceph monitor host list.")] public string? MonHost { get; set; } /// Whether this storage is shared across cluster nodes. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Storage is shared across cluster nodes.")] public SwitchParameter Shared { get; set; } /// Whether this storage is enabled. Defaults to enabled. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Create the storage in disabled state.")] public SwitchParameter Disable { get; set; } /// Limit nodes that can access this storage (comma-separated node names). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Limit access to these nodes (comma-separated).")] public string? Nodes { get; set; } protected override void ProcessRecord() @@ -83,6 +83,7 @@ namespace PSProxmoxVE.Cmdlets.Storage var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Creating storage '{Storage}'..."); var data = new Dictionary { ["storage"] = Storage, diff --git a/src/PSProxmoxVE/Cmdlets/Storage/RemovePveStorageCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Storage/RemovePveStorageCmdlet.cs index 4399113..9393ed3 100644 --- a/src/PSProxmoxVE/Cmdlets/Storage/RemovePveStorageCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Storage/RemovePveStorageCmdlet.cs @@ -16,7 +16,7 @@ namespace PSProxmoxVE.Cmdlets.Storage public class RemovePveStorageCmdlet : PveCmdletBase { /// The storage identifier to remove. - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The storage pool name.")] public string Storage { get; set; } = string.Empty; protected override void ProcessRecord() @@ -27,6 +27,7 @@ namespace PSProxmoxVE.Cmdlets.Storage var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Removing storage '{Storage}'..."); client.DeleteAsync($"storage/{Storage}").GetAwaiter().GetResult(); } } diff --git a/src/PSProxmoxVE/Cmdlets/Storage/SendPveIsoCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Storage/SendPveIsoCmdlet.cs index f4a71ba..3281e5e 100644 --- a/src/PSProxmoxVE/Cmdlets/Storage/SendPveIsoCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Storage/SendPveIsoCmdlet.cs @@ -20,33 +20,33 @@ namespace PSProxmoxVE.Cmdlets.Storage public class SendPveIsoCmdlet : PveCmdletBase { /// The Proxmox VE node to upload to. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The target storage identifier (must support "iso" content). - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The storage pool name.")] public string Storage { get; set; } = string.Empty; /// /// The full local path to the ISO file to upload. The file must exist. /// - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, HelpMessage = "Local path to the ISO file to upload.")] [FileExistsValidation] public string Path { get; set; } = string.Empty; /// Optional checksum value to verify the uploaded file. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Checksum value to verify the upload.")] public string? Checksum { get; set; } /// Checksum algorithm used for verification. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Checksum algorithm (md5, sha1, sha256, sha512).")] [ValidateSet("md5", "sha1", "sha256", "sha512", IgnoreCase = true)] public string? ChecksumAlgorithm { get; set; } /// /// When specified, waits for the upload task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -58,6 +58,7 @@ namespace PSProxmoxVE.Cmdlets.Storage var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Uploading ISO to {Node}/{Storage}..."); var resource = $"nodes/{Node}/storage/{Storage}/upload"; var totalBytes = new System.IO.FileInfo(Path).Length; diff --git a/src/PSProxmoxVE/Cmdlets/Tasks/GetPveTaskCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Tasks/GetPveTaskCmdlet.cs index e953709..77da2f2 100644 --- a/src/PSProxmoxVE/Cmdlets/Tasks/GetPveTaskCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Tasks/GetPveTaskCmdlet.cs @@ -17,16 +17,18 @@ namespace PSProxmoxVE.Cmdlets.Tasks public class GetPveTaskCmdlet : PveCmdletBase { /// The node on which the task ran. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The UPID of the task to query. - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The task UPID.")] public string Upid { get; set; } = string.Empty; protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose($"Getting task status for UPID on node '{Node}'..."); var service = new TaskService(); var task = service.GetTask(session, Node, Upid); WriteObject(task); diff --git a/src/PSProxmoxVE/Cmdlets/Tasks/WaitPveTaskCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Tasks/WaitPveTaskCmdlet.cs index a907bdf..8c68b97 100644 --- a/src/PSProxmoxVE/Cmdlets/Tasks/WaitPveTaskCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Tasks/WaitPveTaskCmdlet.cs @@ -21,27 +21,27 @@ namespace PSProxmoxVE.Cmdlets.Tasks public class WaitPveTaskCmdlet : PveCmdletBase { /// The node on which the task is running. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The UPID of the task to wait for. Accepts pipeline input from PveTask (PveTask.Upid). /// - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The task UPID.")] public string Upid { get; set; } = string.Empty; /// /// Maximum time to wait for the task. Defaults to no timeout. /// Example: -Timeout (New-TimeSpan -Minutes 10) /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Maximum time to wait for the task.")] public TimeSpan? Timeout { get; set; } /// /// How frequently to poll the task status. Defaults to 2 seconds. /// Example: -PollInterval (New-TimeSpan -Seconds 5) /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "How often to poll task status.")] public TimeSpan? PollInterval { get; set; } protected override void ProcessRecord() diff --git a/src/PSProxmoxVE/Cmdlets/Templates/GetPveTemplateCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Templates/GetPveTemplateCmdlet.cs index 2ad98d8..dd152b0 100644 --- a/src/PSProxmoxVE/Cmdlets/Templates/GetPveTemplateCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Templates/GetPveTemplateCmdlet.cs @@ -17,11 +17,11 @@ namespace PSProxmoxVE.Cmdlets.Templates public class GetPveTemplateCmdlet : PveCmdletBase { /// The Proxmox VE node name. When omitted, queries all nodes in the cluster. - [Parameter(Mandatory = false, Position = 0)] + [Parameter(Mandatory = false, Position = 0, HelpMessage = "The PVE node name.")] public string? Node { get; set; } /// Filter results by template name. Supports wildcard (*) matching. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by template name (supports wildcards).")] public string? Name { get; set; } protected override void ProcessRecord() @@ -29,6 +29,7 @@ namespace PSProxmoxVE.Cmdlets.Templates var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose("Getting templates..."); var nodesToQuery = new System.Collections.Generic.List(); if (!string.IsNullOrEmpty(Node)) diff --git a/src/PSProxmoxVE/Cmdlets/Templates/NewPveTemplateCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Templates/NewPveTemplateCmdlet.cs index 237d421..9b6f109 100644 --- a/src/PSProxmoxVE/Cmdlets/Templates/NewPveTemplateCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Templates/NewPveTemplateCmdlet.cs @@ -17,15 +17,16 @@ namespace PSProxmoxVE.Cmdlets.Templates public class NewPveTemplateCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier to convert. Accepts pipeline input from Get-PveVm (PveVm.VmId). - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// When specified, waits for the conversion task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -34,6 +35,8 @@ namespace PSProxmoxVE.Cmdlets.Templates if (!ShouldProcess($"VM {VmId} on {Node}", "Convert to PVE Template (irreversible)")) return; + + WriteVerbose($"Converting VM {VmId} to template..."); var service = new TemplateService(); var task = service.CreateTemplate(session, Node, VmId); diff --git a/src/PSProxmoxVE/Cmdlets/Templates/NewPveVmFromTemplateCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Templates/NewPveVmFromTemplateCmdlet.cs index 8de4e73..1d5bdff 100644 --- a/src/PSProxmoxVE/Cmdlets/Templates/NewPveVmFromTemplateCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Templates/NewPveVmFromTemplateCmdlet.cs @@ -18,27 +18,29 @@ namespace PSProxmoxVE.Cmdlets.Templates public class NewPveVmFromTemplateCmdlet : PveCmdletBase { /// The node where the source template resides. Alias: Node. - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The node where the template resides.")] [Alias("Node")] public string TemplateNode { get; set; } = string.Empty; /// The source template VM ID. Accepts pipeline input from Get-PveTemplate (PveVm.VmId). - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// The VM ID for the new cloned VM. - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, HelpMessage = "VM ID for the new cloned VM.")] + [ValidateRange(100, 999999999)] public int NewVmId { get; set; } /// Optional name for the new VM. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Name for the new VM.")] public string? NewName { get; set; } /// /// The target node for the new VM. When omitted, the new VM is created on the same node /// as the template. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Target node for the new VM.")] public string? TargetNode { get; set; } /// @@ -46,11 +48,11 @@ namespace PSProxmoxVE.Cmdlets.Templates /// When omitted, creates a linked clone (shares base disk with template). /// Linked clones require the template to reside on shared storage. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Perform a full (non-linked) clone.")] public SwitchParameter Full { get; set; } /// When specified, waits for the clone task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -60,6 +62,8 @@ namespace PSProxmoxVE.Cmdlets.Templates var session = GetSession(); var service = new VmService(); + + WriteVerbose($"Cloning VM from template {VmId}..."); var task = service.CloneVm( session, TemplateNode, diff --git a/src/PSProxmoxVE/Cmdlets/Templates/RemovePveTemplateCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Templates/RemovePveTemplateCmdlet.cs index edf58ca..07a2391 100644 --- a/src/PSProxmoxVE/Cmdlets/Templates/RemovePveTemplateCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Templates/RemovePveTemplateCmdlet.cs @@ -18,19 +18,20 @@ namespace PSProxmoxVE.Cmdlets.Templates public class RemovePveTemplateCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM/template identifier to remove. Accepts pipeline input from Get-PveVm (PveVm.VmId). - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// When specified, also removes all associated backup files and jobs. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Remove all associated resources.")] public SwitchParameter Purge { get; set; } /// When specified, waits for the deletion task to complete before returning. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -40,6 +41,8 @@ namespace PSProxmoxVE.Cmdlets.Templates var session = GetSession(); var service = new TemplateService(); + + WriteVerbose($"Removing template {VmId}..."); var task = service.RemoveTemplate(session, Node, VmId, Purge.IsPresent); if (Wait.IsPresent && !string.IsNullOrEmpty(task.Upid)) diff --git a/src/PSProxmoxVE/Cmdlets/Users/GetPveApiTokenCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/GetPveApiTokenCmdlet.cs index 3a0d320..d892739 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/GetPveApiTokenCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/GetPveApiTokenCmdlet.cs @@ -21,16 +21,18 @@ namespace PSProxmoxVE.Cmdlets.Users /// The user ID whose tokens to list, in "username@realm" format (e.g., "admin@pam"). /// Accepts pipeline input from Get-PveUser (PveUser.UserId). /// - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The user ID in user@realm format.")] public string UserId { get; set; } = string.Empty; /// Filter to a specific token identifier (e.g., "automation"). - [Parameter(Mandatory = false, Position = 1)] + [Parameter(Mandatory = false, Position = 1, HelpMessage = "The API token identifier.")] public string? TokenId { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose($"Getting API tokens for user '{UserId}'..."); var service = new UserService(); var tokens = service.GetApiTokens(session, UserId); diff --git a/src/PSProxmoxVE/Cmdlets/Users/GetPvePermissionCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/GetPvePermissionCmdlet.cs index c9f98dc..71da372 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/GetPvePermissionCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/GetPvePermissionCmdlet.cs @@ -17,16 +17,18 @@ namespace PSProxmoxVE.Cmdlets.Users public class GetPvePermissionCmdlet : PveCmdletBase { /// Filter results to a specific resource path (e.g., "/", "/vms/100"). - [Parameter(Mandatory = false, Position = 0)] + [Parameter(Mandatory = false, Position = 0, HelpMessage = "Filter by resource path (e.g. /, /vms/100).")] public string? Path { get; set; } /// Filter results to a specific user or group ID. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The user ID in user@realm format.")] public string? UserId { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose("Getting permissions..."); var service = new UserService(); var permissions = service.GetPermissions(session); diff --git a/src/PSProxmoxVE/Cmdlets/Users/GetPveRoleCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/GetPveRoleCmdlet.cs index 3369680..21f7ca8 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/GetPveRoleCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/GetPveRoleCmdlet.cs @@ -17,7 +17,7 @@ namespace PSProxmoxVE.Cmdlets.Users public class GetPveRoleCmdlet : PveCmdletBase { /// Optional role identifier to retrieve a specific role. - [Parameter(Mandatory = false, Position = 0)] + [Parameter(Mandatory = false, Position = 0, HelpMessage = "The role identifier.")] public string? RoleId { get; set; } protected override void ProcessRecord() @@ -25,6 +25,7 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose("Getting roles..."); var json = client.GetAsync("access/roles").GetAwaiter().GetResult(); var root = JObject.Parse(json); var data = root["data"] as JArray ?? new JArray(); diff --git a/src/PSProxmoxVE/Cmdlets/Users/GetPveUserCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/GetPveUserCmdlet.cs index c04efd1..eb69aab 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/GetPveUserCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/GetPveUserCmdlet.cs @@ -20,11 +20,11 @@ namespace PSProxmoxVE.Cmdlets.Users /// Filter results to a specific user ID or pattern (e.g., "admin@pam", "*@pve"). /// Supports wildcard (*) matching. /// - [Parameter(Mandatory = false, Position = 0)] + [Parameter(Mandatory = false, Position = 0, HelpMessage = "The user ID in user@realm format.")] public string? UserId { get; set; } /// When specified, returns only enabled users. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Return only enabled users.")] [Alias("EnabledOnly")] public SwitchParameter Enabled { get; set; } @@ -33,6 +33,7 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose("Getting users..."); var json = client.GetAsync("access/users").GetAwaiter().GetResult(); var root = JObject.Parse(json); var data = root["data"] as JArray ?? new JArray(); diff --git a/src/PSProxmoxVE/Cmdlets/Users/NewPveApiTokenCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/NewPveApiTokenCmdlet.cs index ce4bb01..50d8eb3 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/NewPveApiTokenCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/NewPveApiTokenCmdlet.cs @@ -21,21 +21,21 @@ namespace PSProxmoxVE.Cmdlets.Users /// The user ID to create the token for, in "username@realm" format (e.g., "admin@pam"). /// Accepts pipeline input from Get-PveUser (PveUser.UserId). /// - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The user ID in user@realm format.")] public string UserId { get; set; } = string.Empty; /// The token identifier (alphanumeric, hyphens allowed; e.g., "automation"). - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The API token identifier.")] public string TokenId { get; set; } = string.Empty; /// Optional description for this token. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Description for this token.")] public string? Comment { get; set; } /// /// Token expiry as a Unix timestamp. Use 0 or omit for no expiry. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Token expiry as a Unix timestamp.")] public long? Expire { get; set; } /// @@ -43,7 +43,7 @@ namespace PSProxmoxVE.Cmdlets.Users /// are the intersection of the user's ACLs and any explicit ACLs granted to the token. /// When omitted, the token inherits the full permissions of its user. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Enable token privilege separation.")] public SwitchParameter PrivilegeSeparation { get; set; } protected override void ProcessRecord() @@ -54,6 +54,8 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); var service = new UserService(); + + WriteVerbose($"Creating API token '{TokenId}' for user '{UserId}'..."); var token = service.CreateApiToken( session, UserId, diff --git a/src/PSProxmoxVE/Cmdlets/Users/NewPveRoleCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/NewPveRoleCmdlet.cs index 05938ab..01560b0 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/NewPveRoleCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/NewPveRoleCmdlet.cs @@ -16,14 +16,14 @@ namespace PSProxmoxVE.Cmdlets.Users public class NewPveRoleCmdlet : PveCmdletBase { /// The role identifier/name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The role identifier.")] public string RoleId { get; set; } = string.Empty; /// /// Comma-separated list of privileges to grant this role /// (e.g., "VM.Allocate,VM.Config.CPU,VM.Config.Memory"). /// - [Parameter(Mandatory = false, Position = 1)] + [Parameter(Mandatory = false, Position = 1, HelpMessage = "Comma-separated list of privileges.")] public string? Privileges { get; set; } protected override void ProcessRecord() @@ -34,6 +34,7 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Creating role '{RoleId}'..."); var data = new Dictionary { ["roleid"] = RoleId diff --git a/src/PSProxmoxVE/Cmdlets/Users/NewPveUserCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/NewPveUserCmdlet.cs index 34a0e39..a9a404d 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/NewPveUserCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/NewPveUserCmdlet.cs @@ -18,35 +18,35 @@ namespace PSProxmoxVE.Cmdlets.Users public class NewPveUserCmdlet : PveCmdletBase { /// The user identifier in "user@realm" format (e.g., "jdoe@pve"). - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The user ID in user@realm format.")] public string UserId { get; set; } = string.Empty; /// The user's password (for pve/pam realms). Accepts a SecureString. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Password for the user.")] public System.Security.SecureString? Password { get; set; } /// The user's first name. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The user's first name.")] public string? FirstName { get; set; } /// The user's last name. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The user's last name.")] public string? LastName { get; set; } /// The user's email address. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The user's email address.")] public string? Email { get; set; } /// Comma-separated list of groups to add this user to. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Comma-separated list of groups.")] public string? Groups { get; set; } /// Optional comment/notes for this user. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Comment or notes for this user.")] public string? Comment { get; set; } /// Account expiry as a Unix timestamp. Use 0 for no expiry. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Account expiry as a Unix timestamp.")] public long? Expire { get; set; } protected override void ProcessRecord() @@ -72,6 +72,8 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); var service = new UserService(); + + WriteVerbose($"Creating user '{UserId}'..."); service.CreateUser(session, UserId, config); WriteObject(new PveUser diff --git a/src/PSProxmoxVE/Cmdlets/Users/RemovePveApiTokenCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/RemovePveApiTokenCmdlet.cs index d065f76..b17ca2d 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/RemovePveApiTokenCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/RemovePveApiTokenCmdlet.cs @@ -18,14 +18,14 @@ namespace PSProxmoxVE.Cmdlets.Users /// The user ID that owns the token, in "username@realm" format (e.g., "admin@pam"). /// Accepts pipeline input from Get-PveApiToken (PveApiToken.UserId). /// - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The user ID in user@realm format.")] public string UserId { get; set; } = string.Empty; /// /// The token identifier to remove (e.g., "automation"). /// Accepts pipeline input from Get-PveApiToken (PveApiToken.TokenId). /// - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The API token identifier.")] public string TokenId { get; set; } = string.Empty; protected override void ProcessRecord() @@ -35,6 +35,7 @@ namespace PSProxmoxVE.Cmdlets.Users if (!ShouldProcess($"{UserId}!{TokenId}", "Remove PVE API Token")) return; + WriteVerbose($"Removing API token '{TokenId}' from user '{UserId}'..."); var service = new UserService(); service.RemoveApiToken(session, UserId, TokenId); } diff --git a/src/PSProxmoxVE/Cmdlets/Users/RemovePveRoleCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/RemovePveRoleCmdlet.cs index 98aaa0d..c627068 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/RemovePveRoleCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/RemovePveRoleCmdlet.cs @@ -14,7 +14,7 @@ namespace PSProxmoxVE.Cmdlets.Users public class RemovePveRoleCmdlet : PveCmdletBase { /// The role identifier to remove. - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The role identifier.")] public string RoleId { get; set; } = string.Empty; protected override void ProcessRecord() @@ -24,6 +24,8 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); var service = new UserService(); + + WriteVerbose($"Removing role '{RoleId}'..."); service.RemoveRole(session, RoleId); } } diff --git a/src/PSProxmoxVE/Cmdlets/Users/RemovePveUserCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/RemovePveUserCmdlet.cs index 34506e9..1961968 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/RemovePveUserCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/RemovePveUserCmdlet.cs @@ -13,7 +13,7 @@ namespace PSProxmoxVE.Cmdlets.Users public class RemovePveUserCmdlet : PveCmdletBase { /// The user identifier to remove (e.g., "jdoe@pve"). - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The user ID in user@realm format.")] public string UserId { get; set; } = string.Empty; protected override void ProcessRecord() @@ -23,6 +23,8 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); var service = new UserService(); + + WriteVerbose($"Removing user '{UserId}'..."); service.RemoveUser(session, UserId); } } diff --git a/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs index e3b79af..f0c4795 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs @@ -15,28 +15,28 @@ namespace PSProxmoxVE.Cmdlets.Users public class SetPvePermissionCmdlet : PveCmdletBase { /// The resource path this ACL applies to (e.g., "/", "/vms/100"). - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The resource path (e.g. /, /vms/100).")] public string Path { get; set; } = string.Empty; /// The user or group identifier (e.g., "jdoe@pve" or "admins"). - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, HelpMessage = "The user or group identifier.")] public string UgId { get; set; } = string.Empty; /// The role to assign (e.g., "Administrator", "PVEVMUser"). - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, HelpMessage = "The role to assign (e.g. Administrator).")] public string Role { get; set; } = string.Empty; /// The ACL entry type: "user" or "group". - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "ACL entry type: user or group.")] [ValidateSet("user", "group", IgnoreCase = true)] public string Type { get; set; } = "user"; /// Whether to propagate this ACL to child paths. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Propagate this ACL to child paths.")] public SwitchParameter Propagate { get; set; } /// When specified, removes the ACL entry instead of adding it. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Remove the ACL entry instead of adding it.")] public SwitchParameter Delete { get; set; } protected override void ProcessRecord() @@ -48,6 +48,7 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); using var client = new PveHttpClient(session); + WriteVerbose($"Setting permission for '{UgId}' at '{Path}'..."); var data = new Dictionary { ["path"] = Path, diff --git a/src/PSProxmoxVE/Cmdlets/Users/SetPveUserCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/SetPveUserCmdlet.cs index b3f0277..bab31c7 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/SetPveUserCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/SetPveUserCmdlet.cs @@ -16,39 +16,39 @@ namespace PSProxmoxVE.Cmdlets.Users public class SetPveUserCmdlet : PveCmdletBase { /// The user identifier to update (e.g., "jdoe@pve"). - [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true, HelpMessage = "The user ID in user@realm format.")] public string UserId { get; set; } = string.Empty; /// New password for the user. Accepts a SecureString. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "New password for the user.")] public System.Security.SecureString? Password { get; set; } /// Updated first name. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Updated first name.")] public string? FirstName { get; set; } /// Updated last name. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Updated last name.")] public string? LastName { get; set; } /// Updated email address. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Updated email address.")] public string? Email { get; set; } /// Updated group membership (comma-separated group names). - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Updated group membership (comma-separated).")] public string? Groups { get; set; } /// Updated comment/notes. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Updated comment or notes.")] public string? Comment { get; set; } /// Account expiry as a Unix timestamp. Use 0 to remove expiry. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Account expiry as a Unix timestamp.")] public long? Expire { get; set; } /// Enable or disable the user account. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Enable or disable the user account.")] public bool? Enable { get; set; } protected override void ProcessRecord() @@ -75,6 +75,8 @@ namespace PSProxmoxVE.Cmdlets.Users var session = GetSession(); var service = new UserService(); + + WriteVerbose($"Updating user '{UserId}'..."); service.SetUser(session, UserId, config); } } diff --git a/src/PSProxmoxVE/Cmdlets/Vms/CopyPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/CopyPveVmCmdlet.cs index bd7ed15..6b6237b 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/CopyPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/CopyPveVmCmdlet.cs @@ -20,31 +20,33 @@ namespace PSProxmoxVE.Cmdlets.Vms /// /// The node on which the source VM resides. /// - [Parameter(Mandatory = true)] + [Parameter(Mandatory = true, HelpMessage = "The node where the source VM resides.")] public string SourceNode { get; set; } = string.Empty; /// /// The ID of the source VM to clone. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// The VM ID to assign to the new clone. When omitted, the next available ID is used. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "VM ID for the new clone.")] + [ValidateRange(100, 999999999)] public int? NewVmId { get; set; } /// /// The display name for the new clone. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Display name for the new clone.")] public string? NewName { get; set; } /// /// The target node for the clone. Defaults to the source node. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Target node for the clone.")] public string? TargetNode { get; set; } /// @@ -53,19 +55,19 @@ namespace PSProxmoxVE.Cmdlets.Vms /// A full clone is required when the source VM is not a template. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Perform a full (non-linked) clone.")] public SwitchParameter Full { get; set; } /// /// Target storage pool for the full clone disks. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The storage pool name.")] public string? Storage { get; set; } /// /// When specified, waits for the clone task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -77,6 +79,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Cloning VM {VmId}..."); var newid = NewVmId ?? 0; var task = vmService.CloneVm(session, SourceNode, VmId, newid, NewName, TargetNode, Full.IsPresent); diff --git a/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmCmdlet.cs index a3532c0..ca8de89 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmCmdlet.cs @@ -23,42 +23,45 @@ namespace PSProxmoxVE.Cmdlets.Vms /// When omitted, VMs from all nodes are returned. /// /// - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string? Node { get; set; } /// /// Filter results to the VM with this ID. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int? VmId { get; set; } /// /// Filter results to VMs whose name matches this value (case-insensitive, contains match). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by name.")] public string? Name { get; set; } /// /// Filter results to VMs in the specified status (e.g., "running", "stopped"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by status (e.g. running, stopped).")] public string? Status { get; set; } /// /// Filter results to VMs that have the specified tag (substring match against the semicolon-separated tags field). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Filter by tag.")] public string? Tag { get; set; } /// /// When specified, returns only VMs that are marked as templates. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Return only VMs marked as templates.")] public SwitchParameter TemplatesOnly { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose("Getting VMs..."); var service = new VmService(); IEnumerable vms = service.GetVms(session, Node); diff --git a/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmConfigCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmConfigCmdlet.cs index f4b8696..3adfe85 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmConfigCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmConfigCmdlet.cs @@ -20,13 +20,14 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM whose configuration to retrieve. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } protected override void ProcessRecord() @@ -34,6 +35,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Getting config for VM {VmId} on node '{Node}'..."); var config = vmService.GetVmConfig(session, Node, VmId); WriteObject(config); } diff --git a/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmGuestNetworkCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmGuestNetworkCmdlet.cs index c39fb4e..229b1fd 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmGuestNetworkCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/GetPveVmGuestNetworkCmdlet.cs @@ -17,16 +17,19 @@ namespace PSProxmoxVE.Cmdlets.Vms public sealed class GetPveVmGuestNetworkCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose($"Getting guest network interfaces for VM {VmId}..."); var service = new VmService(); var interfaces = service.GetGuestNetworkInterfaces(session, Node, VmId); diff --git a/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs index 057ee93..96e80b0 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs @@ -11,31 +11,36 @@ namespace PSProxmoxVE.Cmdlets.Vms /// installed and running inside the VM. /// /// - [Cmdlet(VerbsLifecycle.Invoke, "PveVmGuestExec")] + [Cmdlet(VerbsLifecycle.Invoke, "PveVmGuestExec", SupportsShouldProcess = true)] [OutputType(typeof(PSObject))] public sealed class InvokePveVmGuestExecCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// The command to execute inside the guest. - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, HelpMessage = "The command to execute inside the guest.")] public string Command { get; set; } = string.Empty; /// Optional arguments to pass to the command. - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Arguments to pass to the command.")] public string[]? Args { get; set; } protected override void ProcessRecord() { + if (!ShouldProcess($"VM {VmId} on node '{Node}'", $"Execute guest command: {Command}")) + return; + var session = GetSession(); var service = new VmService(); + WriteVerbose($"Executing command on VM {VmId} via guest agent..."); var pid = service.ExecuteGuestCommand(session, Node, VmId, Command, Args); // Poll for completion diff --git a/src/PSProxmoxVE/Cmdlets/Vms/MovePveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/MovePveVmCmdlet.cs index a6e8732..b8e023d 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/MovePveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/MovePveVmCmdlet.cs @@ -21,19 +21,20 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM currently resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to migrate. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// The destination node to migrate the VM to. /// - [Parameter(Mandatory = true)] + [Parameter(Mandatory = true, HelpMessage = "The destination node for migration.")] public string TargetNode { get; set; } = string.Empty; /// @@ -42,13 +43,13 @@ namespace PSProxmoxVE.Cmdlets.Vms /// Requires shared storage between the source and target nodes. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Perform live migration (VM stays running).")] public SwitchParameter Online { get; set; } /// /// When specified, waits for the migration task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -59,6 +60,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Migrating VM {VmId} from '{Node}' to '{TargetNode}'..."); var task = vmService.MigrateVm(session, Node, VmId, TargetNode, Online.IsPresent); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/NewPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/NewPveVmCmdlet.cs index 77a14d5..fb363f2 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/NewPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/NewPveVmCmdlet.cs @@ -21,103 +21,104 @@ namespace PSProxmoxVE.Cmdlets.Vms /// /// The node on which to create the VM. /// - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The VM ID to assign. When omitted, the next available ID is used. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int? VmId { get; set; } /// /// The display name of the VM. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "The display name of the VM.")] public string? Name { get; set; } /// /// Memory size in MiB. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Memory size in MiB.")] public int? Memory { get; set; } /// /// Number of CPU cores per socket. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Number of CPU cores per socket.")] public int? Cores { get; set; } /// /// Number of CPU sockets. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Number of CPU sockets.")] public int? Sockets { get; set; } /// /// Emulated CPU type (e.g., "host", "x86-64-v2-AES"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Emulated CPU type (e.g. host, x86-64-v2-AES).")] public string? CpuType { get; set; } /// /// BIOS type: "seabios" (default) or "ovmf" (UEFI). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "BIOS type: seabios (default) or ovmf (UEFI).")] public string? Bios { get; set; } /// /// Emulated machine type (e.g., "q35", "i440fx"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Emulated machine type (e.g. q35, i440fx).")] public string? Machine { get; set; } /// /// Size of the primary disk (e.g., "32G"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Size of the primary disk (e.g. 32G).")] public string? DiskSize { get; set; } /// /// Storage pool for the primary disk (e.g., "local-lvm"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Storage pool for the primary disk.")] public string? DiskStorage { get; set; } /// /// Disk format (e.g., "raw", "qcow2"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Disk format (e.g. raw, qcow2).")] public string? DiskFormat { get; set; } /// /// Network interface model (e.g., "virtio", "e1000"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Network interface model (e.g. virtio, e1000).")] public string? Network { get; set; } /// /// Network bridge to attach to (e.g., "vmbr0"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Network bridge to attach to (e.g. vmbr0).")] public string? Bridge { get; set; } /// /// Guest OS type hint (e.g., "l26" for Linux 2.6+, "win10"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Guest OS type hint (e.g. l26, win10).")] public string? OsType { get; set; } /// /// When specified, starts the VM after creation. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Start the VM after creation.")] public SwitchParameter Start { get; set; } /// /// When specified, waits for the creation task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -128,6 +129,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Creating VM on node '{Node}'..."); var config = new Dictionary(); if (VmId.HasValue) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/RemovePveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/RemovePveVmCmdlet.cs index 1abe7dc..785433b 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/RemovePveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/RemovePveVmCmdlet.cs @@ -22,13 +22,14 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to remove. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// @@ -36,7 +37,7 @@ namespace PSProxmoxVE.Cmdlets.Vms /// When specified, also removes the VM from HA resource configuration and replication jobs. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Remove all associated resources.")] public SwitchParameter Purge { get; set; } /// @@ -44,13 +45,13 @@ namespace PSProxmoxVE.Cmdlets.Vms /// When specified, bypasses locks and forces removal even if a lock is set on the VM. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Force the operation without additional checks.")] public SwitchParameter Force { get; set; } /// /// When specified, waits for the removal task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -61,6 +62,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Removing VM {VmId} from node '{Node}'..."); var task = vmService.RemoveVm(session, Node, VmId, Purge.IsPresent); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/ResetPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/ResetPveVmCmdlet.cs index 1f564bd..d98a4f2 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/ResetPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/ResetPveVmCmdlet.cs @@ -13,7 +13,7 @@ namespace PSProxmoxVE.Cmdlets.Vms /// Use -Wait to block until the reset task completes. /// /// - [Cmdlet("Reset", "PveVm", SupportsShouldProcess = true)] + [Cmdlet(VerbsCommon.Reset, "PveVm", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] [OutputType(typeof(PveTask))] public sealed class ResetPveVmCmdlet : PveCmdletBase { @@ -22,19 +22,20 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to reset. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// When specified, waits for the reset task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -45,6 +46,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Resetting VM {VmId} on node '{Node}'..."); var task = vmService.ResetVm(session, Node, VmId); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/ResizePveVmDiskCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/ResizePveVmDiskCmdlet.cs index 78c334a..29dbf45 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/ResizePveVmDiskCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/ResizePveVmDiskCmdlet.cs @@ -23,13 +23,14 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM whose disk should be resized. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// @@ -37,7 +38,7 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The disk slot to resize (e.g., "virtio0", "scsi0", "ide0", "sata0"). /// /// - [Parameter(Mandatory = true)] + [Parameter(Mandatory = true, HelpMessage = "The disk slot to resize (e.g. virtio0, scsi0).")] public string Disk { get; set; } = string.Empty; /// @@ -46,13 +47,13 @@ namespace PSProxmoxVE.Cmdlets.Vms /// or a "+" prefix (e.g., "+10G") to grow the disk by the specified amount. /// /// - [Parameter(Mandatory = true)] + [Parameter(Mandatory = true, HelpMessage = "New disk size (e.g. 50G or +10G to grow).")] public string Size { get; set; } = string.Empty; /// /// When specified, waits for the resize task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -63,6 +64,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Resizing disk '{Disk}' on VM {VmId}..."); var task = vmService.ResizeDisk(session, Node, VmId, Disk, Size); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs index b495054..03b6089 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/RestartPveVmCmdlet.cs @@ -21,13 +21,14 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to restart. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// @@ -35,13 +36,13 @@ namespace PSProxmoxVE.Cmdlets.Vms /// Timeout in seconds for the graceful shutdown phase. Defaults to 60 seconds. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Maximum time to wait for the task.")] public int Timeout { get; set; } = 60; /// /// When specified, waits for both shutdown and start tasks to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -53,6 +54,8 @@ namespace PSProxmoxVE.Cmdlets.Vms var vmService = new VmService(); var taskService = new TaskService(); + WriteVerbose($"Restarting VM {VmId} on node '{Node}'..."); + // Graceful shutdown var shutdownTask = vmService.ShutdownVm(session, Node, VmId, Timeout); diff --git a/src/PSProxmoxVE/Cmdlets/Vms/ResumePveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/ResumePveVmCmdlet.cs index 1fd98aa..c17f56c 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/ResumePveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/ResumePveVmCmdlet.cs @@ -20,19 +20,20 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to resume. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// When specified, waits for the resume task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -43,6 +44,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Resuming VM {VmId} on node '{Node}'..."); var task = vmService.ResumeVm(session, Node, VmId); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/SetPveVmConfigCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/SetPveVmConfigCmdlet.cs index 5f09d48..f213cb0 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/SetPveVmConfigCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/SetPveVmConfigCmdlet.cs @@ -21,67 +21,68 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to configure. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// Number of CPU cores per socket. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Number of CPU cores per socket.")] public int? Cores { get; set; } /// /// Number of CPU sockets. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Number of CPU sockets.")] public int? Sockets { get; set; } /// /// Memory size in MiB. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Memory size in MiB.")] public int? Memory { get; set; } /// /// Emulated CPU type (e.g., "host", "x86-64-v2-AES"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Emulated CPU type (e.g. host, x86-64-v2-AES).")] public string? CpuType { get; set; } /// /// Human-readable description / notes for the VM. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Description or notes for the VM.")] public string? Description { get; set; } /// /// Semicolon-separated list of tags to assign to the VM. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Semicolon-separated list of tags.")] public string? Tags { get; set; } /// /// BIOS type: "seabios" or "ovmf". /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "BIOS type: seabios or ovmf.")] public string? Bios { get; set; } /// /// Emulated machine type (e.g., "q35", "i440fx"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Emulated machine type (e.g. q35, i440fx).")] public string? Machine { get; set; } /// /// Guest OS type hint (e.g., "l26", "win10"). /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Guest OS type hint (e.g. l26, win10).")] public string? OsType { get; set; } /// @@ -91,7 +92,7 @@ namespace PSProxmoxVE.Cmdlets.Vms /// Values are merged after named parameters and can override them. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Extra config keys as a hashtable.")] public Hashtable? AdditionalConfig { get; set; } /// @@ -100,7 +101,7 @@ namespace PSProxmoxVE.Cmdlets.Vms /// Maps to the PVE API "delete" parameter. /// /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Comma-separated config keys to delete.")] public string? Delete { get; set; } protected override void ProcessRecord() @@ -111,6 +112,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Updating config for VM {VmId} on node '{Node}'..."); var config = new Dictionary(); if (Cores.HasValue) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/StartPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/StartPveVmCmdlet.cs index d21a446..42da0ac 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/StartPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/StartPveVmCmdlet.cs @@ -20,19 +20,20 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to start. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// When specified, waits for the start task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -43,6 +44,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Starting VM {VmId} on node '{Node}'..."); var task = vmService.StartVm(session, Node, VmId); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/StopPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/StopPveVmCmdlet.cs index 4d46bb2..ba7248c 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/StopPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/StopPveVmCmdlet.cs @@ -13,7 +13,7 @@ namespace PSProxmoxVE.Cmdlets.Vms /// Use -Wait to block until the stop task completes. /// /// - [Cmdlet(VerbsLifecycle.Stop, "PveVm", SupportsShouldProcess = true)] + [Cmdlet(VerbsLifecycle.Stop, "PveVm", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] [OutputType(typeof(PveTask))] public sealed class StopPveVmCmdlet : PveCmdletBase { @@ -22,19 +22,20 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to stop. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// When specified, waits for the stop task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -45,6 +46,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Stopping VM {VmId} on node '{Node}'..."); var task = vmService.StopVm(session, Node, VmId); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/SuspendPveVmCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/SuspendPveVmCmdlet.cs index 898b6a0..5c6878a 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/SuspendPveVmCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/SuspendPveVmCmdlet.cs @@ -21,19 +21,20 @@ namespace PSProxmoxVE.Cmdlets.Vms /// The node on which the VM resides. Accepts pipeline input from a PveNode object's Name property. /// /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// /// The ID of the VM to suspend. Accepts pipeline input. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } /// /// When specified, waits for the suspend task to complete before returning. /// - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, HelpMessage = "Wait for the task to complete before returning.")] public SwitchParameter Wait { get; set; } protected override void ProcessRecord() @@ -44,6 +45,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var session = GetSession(); var vmService = new VmService(); + WriteVerbose($"Suspending VM {VmId} on node '{Node}'..."); var task = vmService.SuspendVm(session, Node, VmId); if (Wait.IsPresent) diff --git a/src/PSProxmoxVE/Cmdlets/Vms/TestPveVmGuestAgentCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/TestPveVmGuestAgentCmdlet.cs index 07aa4de..2cd69b9 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/TestPveVmGuestAgentCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/TestPveVmGuestAgentCmdlet.cs @@ -15,16 +15,19 @@ namespace PSProxmoxVE.Cmdlets.Vms public sealed class TestPveVmGuestAgentCmdlet : PveCmdletBase { /// The Proxmox VE node name. - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] public string Node { get; set; } = string.Empty; /// The VM identifier. - [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true, HelpMessage = "The VM identifier.")] + [ValidateRange(100, 999999999)] public int VmId { get; set; } protected override void ProcessRecord() { var session = GetSession(); + + WriteVerbose($"Pinging guest agent on VM {VmId}..."); var service = new VmService(); WriteObject(service.PingGuestAgent(session, Node, VmId)); }