diff --git a/src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs index d82b1c3..330455e 100644 --- a/src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Network/InvokePveNetworkApplyCmdlet.cs @@ -1,8 +1,8 @@ -using System; using System.Management.Automation; using Newtonsoft.Json.Linq; using PSProxmoxVE.Core.Client; using PSProxmoxVE.Core.Models.Vms; +using PSProxmoxVE.Core.Services; namespace PSProxmoxVE.Cmdlets.Network { @@ -16,7 +16,7 @@ namespace PSProxmoxVE.Cmdlets.Network /// [Cmdlet(VerbsLifecycle.Invoke, "PveNetworkApply", SupportsShouldProcess = true)] [OutputType(typeof(PveTask))] - public class InvokePveNetworkApplyCmdlet : PveCmdletBase + public sealed class InvokePveNetworkApplyCmdlet : PveCmdletBase { /// The Proxmox VE node on which to apply network changes. [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] @@ -43,25 +43,11 @@ namespace PSProxmoxVE.Cmdlets.Network if (Wait.IsPresent && !string.IsNullOrEmpty(upid)) { - task = WaitForTask(client, Node, upid); + var taskService = new TaskService(); + task = taskService.WaitForTask(session, 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/Snapshots/NewPveSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs index c144e76..e5d1dab 100644 --- a/src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Snapshots/NewPveSnapshotCmdlet.cs @@ -1,9 +1,9 @@ -using System; using System.Collections.Generic; using System.Management.Automation; using Newtonsoft.Json.Linq; using PSProxmoxVE.Core.Client; using PSProxmoxVE.Core.Models.Vms; +using PSProxmoxVE.Core.Services; namespace PSProxmoxVE.Cmdlets.Snapshots { @@ -16,7 +16,7 @@ namespace PSProxmoxVE.Cmdlets.Snapshots /// [Cmdlet(VerbsCommon.New, "PveSnapshot", SupportsShouldProcess = true)] [OutputType(typeof(PveTask))] - public class NewPveSnapshotCmdlet : PveCmdletBase + public sealed class NewPveSnapshotCmdlet : PveCmdletBase { /// The Proxmox VE node name. [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] @@ -67,25 +67,11 @@ namespace PSProxmoxVE.Cmdlets.Snapshots if (Wait.IsPresent && !string.IsNullOrEmpty(upid)) { - task = WaitForTask(client, Node, upid); + var taskService = new TaskService(); + task = taskService.WaitForTask(session, 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/Snapshots/RemovePveSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs index 86dfd6b..bddf737 100644 --- a/src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Snapshots/RemovePveSnapshotCmdlet.cs @@ -1,8 +1,8 @@ -using System; using System.Management.Automation; using Newtonsoft.Json.Linq; using PSProxmoxVE.Core.Client; using PSProxmoxVE.Core.Models.Vms; +using PSProxmoxVE.Core.Services; namespace PSProxmoxVE.Cmdlets.Snapshots { @@ -15,7 +15,7 @@ namespace PSProxmoxVE.Cmdlets.Snapshots /// [Cmdlet(VerbsCommon.Remove, "PveSnapshot", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] [OutputType(typeof(PveTask))] - public class RemovePveSnapshotCmdlet : PveCmdletBase + public sealed class RemovePveSnapshotCmdlet : PveCmdletBase { /// The Proxmox VE node name. [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] @@ -54,25 +54,11 @@ namespace PSProxmoxVE.Cmdlets.Snapshots if (Wait.IsPresent && !string.IsNullOrEmpty(upid)) { - task = WaitForTask(client, Node, upid); + var taskService = new TaskService(); + task = taskService.WaitForTask(session, 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/Snapshots/RestorePveSnapshotCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs index 2897432..a8dd9e2 100644 --- a/src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Snapshots/RestorePveSnapshotCmdlet.cs @@ -1,8 +1,8 @@ -using System; using System.Management.Automation; using Newtonsoft.Json.Linq; using PSProxmoxVE.Core.Client; using PSProxmoxVE.Core.Models.Vms; +using PSProxmoxVE.Core.Services; namespace PSProxmoxVE.Cmdlets.Snapshots { @@ -18,7 +18,7 @@ namespace PSProxmoxVE.Cmdlets.Snapshots SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] [OutputType(typeof(PveTask))] - public class RestorePveSnapshotCmdlet : PveCmdletBase + public sealed class RestorePveSnapshotCmdlet : PveCmdletBase { /// The Proxmox VE node name. [Parameter(Mandatory = true, Position = 0, HelpMessage = "The PVE node name.")] @@ -57,25 +57,11 @@ namespace PSProxmoxVE.Cmdlets.Snapshots if (Wait.IsPresent && !string.IsNullOrEmpty(upid)) { - task = WaitForTask(client, Node, upid); + var taskService = new TaskService(); + task = taskService.WaitForTask(session, 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/Vms/InvokePveVmGuestExecCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs index 96e80b0..f521050 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs @@ -1,3 +1,5 @@ +using System; +using System.Diagnostics; using System.Management.Automation; using PSProxmoxVE.Core.Services; @@ -32,6 +34,11 @@ namespace PSProxmoxVE.Cmdlets.Vms [Parameter(Mandatory = false, HelpMessage = "Arguments to pass to the command.")] public string[]? Args { get; set; } + /// Maximum time in seconds to wait for the command to complete. Defaults to 300 (5 minutes). + [Parameter(Mandatory = false, HelpMessage = "Timeout in seconds to wait for command completion. Defaults to 300.")] + [ValidateRange(1, 3600)] + public int Timeout { get; set; } = 300; + protected override void ProcessRecord() { if (!ShouldProcess($"VM {VmId} on node '{Node}'", $"Execute guest command: {Command}")) @@ -43,11 +50,15 @@ namespace PSProxmoxVE.Cmdlets.Vms WriteVerbose($"Executing command on VM {VmId} via guest agent..."); var pid = service.ExecuteGuestCommand(session, Node, VmId, Command, Args); - // Poll for completion + // Poll for completion with timeout + var sw = Stopwatch.StartNew(); + var deadline = TimeSpan.FromSeconds(Timeout); Newtonsoft.Json.Linq.JObject result; do { System.Threading.Thread.Sleep(1000); + if (sw.Elapsed >= deadline) + throw new TimeoutException($"Guest command did not complete within {Timeout} seconds."); result = service.GetGuestExecStatus(session, Node, VmId, pid); } while (result["exited"]?.ToObject() != 1); @@ -68,7 +79,7 @@ namespace PSProxmoxVE.Cmdlets.Vms var bytes = System.Convert.FromBase64String(encoded); return System.Text.Encoding.UTF8.GetString(bytes); } - catch + catch (FormatException) { return encoded!; }