fix: allocate a real VMID for Copy-PveVm/Copy-PveContainer and honor -Storage

Copy-PveVm and Copy-PveContainer defaulted newid to 0 when -NewVmId
was omitted, and never sent -Storage on the clone request even
though both cmdlets declare it. Both cmdlets now allocate via
ClusterConfigService.GetNextId when -NewVmId is null, and forward
-Storage into the clone form body. Since PVE rejects storage on a
linked clone, -Storage without -Full now fails fast client-side
instead of failing later against the API.

NewPveVmCmdlet and ImportPveOvaCmdlet hand-rolled the same
GET cluster/nextid call with a manual JObject parse; both now go
through ClusterConfigService.GetNextId so a response without a
data field raises the service's diagnosable InvalidOperationException
rather than a NullReferenceException.

Closes #135
This commit is contained in:
goodolclint-claude[bot]
2026-09-02 19:20:23 +00:00
committed by GitHub
parent 8ec09c2b84
commit 21209fc7ad
5 changed files with 29 additions and 18 deletions
@@ -362,7 +362,8 @@ namespace PSProxmoxVE.Core.Services
int newid,
string? hostname = null,
string? targetNode = null,
bool full = true)
bool full = true,
string? storage = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
@@ -374,6 +375,7 @@ namespace PSProxmoxVE.Core.Services
};
if (!string.IsNullOrEmpty(hostname)) formData["hostname"] = hostname!;
if (!string.IsNullOrEmpty(targetNode)) formData["target"] = targetNode!;
if (!string.IsNullOrEmpty(storage)) formData["storage"] = storage!;
IPveHttpClient client = _injectedClient ?? new PveHttpClient(session);
try
@@ -72,6 +72,13 @@ namespace PSProxmoxVE.Cmdlets.Containers
protected override void ProcessRecord()
{
if (!string.IsNullOrEmpty(Storage) && !Full.IsPresent)
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("-Storage is only valid together with -Full; PVE rejects a target storage on a linked clone.", nameof(Storage)),
"StorageRequiresFullClone",
ErrorCategory.InvalidArgument,
Storage));
var target = TargetNode ?? SourceNode;
if (!ShouldProcess($"Container {VmId} on node '{SourceNode}' to new container on node '{target}'", "Copy-PveContainer"))
return;
@@ -80,14 +87,18 @@ namespace PSProxmoxVE.Cmdlets.Containers
var containerService = new ContainerService();
WriteVerbose($"Cloning container {VmId}...");
var newid = NewVmId ?? new ClusterConfigService().GetNextId(session);
if (!NewVmId.HasValue)
WriteVerbose($"Auto-assigned container ID: {newid}");
var task = containerService.CloneContainer(
session,
SourceNode,
VmId,
NewVmId ?? 0,
newid,
NewName,
TargetNode,
Full.IsPresent);
Full.IsPresent,
Storage);
if (Wait.IsPresent)
{
+11 -2
View File
@@ -72,6 +72,13 @@ namespace PSProxmoxVE.Cmdlets.Vms
protected override void ProcessRecord()
{
if (!string.IsNullOrEmpty(Storage) && !Full.IsPresent)
ThrowTerminatingError(new ErrorRecord(
new PSArgumentException("-Storage is only valid together with -Full; PVE rejects a target storage on a linked clone.", nameof(Storage)),
"StorageRequiresFullClone",
ErrorCategory.InvalidArgument,
Storage));
var target = TargetNode ?? SourceNode;
if (!ShouldProcess($"VM {VmId} on node '{SourceNode}' to new VM on node '{target}'", "Copy-PveVm"))
return;
@@ -80,8 +87,10 @@ namespace PSProxmoxVE.Cmdlets.Vms
var vmService = new VmService();
WriteVerbose($"Cloning VM {VmId}...");
var newid = NewVmId ?? 0;
PveTask Issue() => vmService.CloneVm(session, SourceNode, VmId, newid, NewName, TargetNode, Full.IsPresent);
var newid = NewVmId ?? new ClusterConfigService().GetNextId(session);
if (!NewVmId.HasValue)
WriteVerbose($"Auto-assigned VM ID: {newid}");
PveTask Issue() => vmService.CloneVm(session, SourceNode, VmId, newid, NewName, TargetNode, Full.IsPresent, Storage);
var task = Wait.IsPresent
? InvokeGuestTask(session, SourceNode, Issue)
@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using System.Net;
using Newtonsoft.Json.Linq;
using PSProxmoxVE.Core.Client;
using PSProxmoxVE.Core.Exceptions;
using PSProxmoxVE.Core.Models.Vms;
using PSProxmoxVE.Core.Services;
@@ -151,10 +149,7 @@ namespace PSProxmoxVE.Cmdlets.Vms
}
else
{
using var allocClient = new PveHttpClient(session);
var nextIdJson = allocClient.GetAsync("cluster/nextid").GetAwaiter().GetResult();
var nextIdData = JObject.Parse(nextIdJson)["data"];
vmId = int.Parse(nextIdData!.ToString());
vmId = new ClusterConfigService().GetNextId(session);
WriteVerbose($"Auto-assigned VM ID: {vmId}");
}
@@ -1,7 +1,5 @@
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;
using PSProxmoxVE.Core.Utilities;
@@ -214,11 +212,7 @@ namespace PSProxmoxVE.Cmdlets.Vms
}
else
{
// Auto-allocate the next available VM ID from the cluster.
using var allocClient = new PveHttpClient(session);
var nextIdJson = allocClient.GetAsync("cluster/nextid").GetAwaiter().GetResult();
var nextIdData = JObject.Parse(nextIdJson)["data"];
config["vmid"] = int.Parse(nextIdData!.ToString());
config["vmid"] = new ClusterConfigService().GetNextId(session);
}
if (!string.IsNullOrEmpty(Name))
config["name"] = Name!;