.NET's FormUrlEncodedContent over-encodes characters like : and !
(%3A, %21) in form values. PVE's internal API consumers (specifically
the cluster join process) do not URL-decode these values before using
them, causing fingerprint comparison failures and password mismatches.
Replace with BuildFormContent/EncodeFormValue that only encodes
characters that break form parsing (&, =, +, space, %). This matches
curl's -d behavior and fixes cluster join "Cluster join aborted!" errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RegenerateCloudInitImage was calling GET /cloudinit/dump?type=user
which returns the cloud-init YAML content, not a task UPID. The
cmdlet then passed this YAML string to WaitForTask, causing a 501
error trying to poll a URI like "GET nodes/.../tasks/%23cloud-config..."
Fixed to call PUT /nodes/{node}/qemu/{vmid}/cloudinit which is the
correct regeneration endpoint that returns a UPID.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
D013 compliance scan found 2 remaining Newtonsoft type exposures:
- PveClusterJoinInfo.Nodelist: JArray → List<Dictionary<string, object?>>
- PveClusterJoinInfo.Totem: JObject → Dictionary<string, object?>
Both now use NativeListConverter/NativeDictionaryConverter for
deserialization. Full D013 compliance report added.
Updated findings.json: F085 status open with scan evidence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace null-forgiving operator on Marshal.PtrToStringUni with
null-coalescing fallback (AddPveClusterMemberCmdlet.cs:64)
- GetNextId now throws InvalidOperationException instead of silently
returning 0 when API response cannot be parsed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract ParseLinks helper to PveCmdletBase for shared link parsing
with WriteWarning on malformed entries (was duplicated in 3 cmdlets)
- Fix GetClusterConfig to return data payload, not full API envelope
- Fix OutputType on GetPveClusterConfigCmdlet to JObject
- Fix link doc comments to use correct key format (link0..link7)
- Add null-safe Properties hashtable conversion in HA rule cmdlets
- Use case-insensitive Mode comparison in MovePveHaResourceCmdlet
- Remove unused using directives
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
F039 (regressed): Replace bare catch blocks with filtered catches that
exclude OutOfMemoryException and StackOverflowException, per D004.
- VmService.PingGuestAgent: catch now filters fatal exceptions
- ImportPveOvaCmdlet: catch now filters fatal exceptions + WriteVerbose
F084 (new): Add PveSession format view to PSProxmoxVE.format.ps1xml
that shows only Hostname, Port, AuthMode, IsExpired, and ServerVersion
in default table output. Sensitive properties (Ticket, ApiToken,
CsrfToken) are hidden from default display but remain accessible
via Select-Object * or direct property access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SharpCompress 0.47.3 renamed ReaderFactory.Open() to OpenReader() and
now requires an explicit ReaderOptions parameter. The synchronous API
is still available — no async conversion needed.
Closes#12
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
F058 (critical): Replace while(true) infinite-loop task polling with
TaskService.WaitForTask in 5 container snapshot and storage cmdlets.
F073+F047 (high): Migrate net9.0 → net10.0 across both source .csproj
files, build.yml, publish.yml, and test helper.
F071 (medium): Add Uri.EscapeDataString() to all inline URL path
segments in ~16 cmdlets that bypass service classes (D003).
F062+F063 (medium): Add ConfirmImpact.High to Restart-PveContainer
and Suspend-PveContainer (D006).
F075 (medium): Generate markdown help docs for 89 cmdlets that were
missing documentation (170 total, up from 81).
F072 (low): Remove unused System.Text.Json dependency from Core.csproj.
F074 (low): Raise publish smoke-test threshold from 60 to 150.
F065 (low): Add .github/ISSUE_TEMPLATE/config.yml.
F066 (low): Add CODEOWNERS.
Also fix _TestHelper.ps1 net9.0 → net10.0 framework reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The codebase uses Newtonsoft.Json (JObject.Parse + ToObject<T>) exclusively.
Removed all [JsonPropertyName] attributes and [System.Text.Json.Serialization.
JsonIgnore] attributes from 41 model files, along with the now-unused
using System.Text.Json.Serialization imports. [JsonProperty] (Newtonsoft)
attributes remain as the sole serialization annotations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VmService, ContainerService, StorageService, and SnapshotService now wrap
user-controlled string parameters (node names, storage names) with
Uri.EscapeDataString() in URL path construction, preventing path traversal
or encoding issues. Integer parameters (vmid) are left unwrapped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced 5 bare catch blocks with specific exception types:
- PveHttpClient: catch JsonException for JSON parse fallback
- PveCmdletBase: catch Exception with filter excluding OOM/SOE for polling
- VmService/ContainerService: catch PveApiException|HttpRequestException
for offline node handling
- GetPveVmCmdlet: catch PveApiException for VM enrichment failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Integration test: add -All to New-PveBackupJob call — PVE API
requires either vmid or all when creating a backup job
- Fix New-PveBackupJob Enabled logic: jobs are enabled by default
unless user explicitly passes -Enabled:$false (was broken because
SwitchParameter default=true doesn't set IsPresent)
- Fix XML doc cref warning in NetworkService after RequireSdn removal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the service-layer RequireSdn hard block with a cmdlet-level
RequireVersion helper in PveCmdletBase that supports two tiers:
- Introduced version: hard fail — the API endpoint doesn't exist
- Default version: warning only — feature exists but may not be
enabled; users who manually enabled it can proceed
Applied to:
- SDN Zone/VNet/Subnet: introduced 6.2, default 8.0
- SDN IPAM/DNS/Controller: introduced 6.2, default 8.1
- Import-PveVmDisk, Import-PveOva: introduced 8.1
Also fixes integration tests:
- Remove duplicate OVA Import context
- Use systemd calendar format for backup schedule (PVE 9)
- Add SDN IPAM/DNS/Controller read-only integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 35 new cmdlets bringing the total to 118:
- Firewall (21): rules, groups, aliases, IP sets, options at
cluster/node/VM/container levels
- Backup (5): ad-hoc vzdump and scheduled backup job CRUD
- SDN IPAM/DNS/Controller (9): plugin management for SDN subsystem
Also includes:
- Fix: Remove-PveRole now has ConfirmImpact.High
- Fix: URL-encode snapshot names in API paths
- Refactor: extract auth header strings to constants in PveHttpClient
- Add PSGallery version badge to README
- Full test coverage: 11 JSON fixtures, xUnit model tests, Pester
unit tests, and integration tests for firewall/backup/OVA import
- Updated manifest, format file, CHANGELOG, README, API coverage docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PVE list endpoint reports Status=running for paused VMs. The new
-Detailed switch queries status/current per VM to populate QmpStatus.
- PveVm.EffectiveStatus computed property: returns QmpStatus ?? Status
- PveVm.Status doc comment explains the limitation
- VmService.EnrichVmStatus() queries status/current endpoint
- Get-PveVm -Detailed enriches each VM with qmpstatus, pid, uptime
- format.ps1xml table view uses EffectiveStatus for the Status column
- Integration test asserts EffectiveStatus after suspend/resume
Verified locally: Get-PveVm -Detailed correctly shows 'paused' for
suspended VMs where Get-PveVm (without -Detailed) shows 'running'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Locally verified against PVE 9.1 with Ubuntu 24.04 cloud OVA:
- Use scsi bus (not sata) for imported disks, matching PVE UI behavior
- Set boot=order=scsi0 for first disk
- Parse NIC ResourceSubType from OVF (VmxNet3, E1000, E1000e, etc.)
and map to PVE network model instead of hardcoding virtio
- Add MapNicModel() to OvfMetadata with fallback to e1000 for unknown types
- Integration test verifies name, cores, memory, ostype, scsi0, net0, boot
qmpstatus fix (from previous local testing):
- WaitForStatusTransition polls status/current endpoint for qmpstatus
- PVE reports status=running but qmpstatus=paused for suspended VMs
- SDN subnet ID uses zone prefix: {zone}-{ip}-{prefix}
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Suspend/Resume: move to Linux VM lifecycle context (empty VMs may
not reliably transition to paused state)
- SDN subnet model: dhcp-range is an array, not string
- SDN subnet delete: use PVE-format ID (vnet-ip-prefix) not CIDR
- Container config: trim trailing newline from PVE description
- OVA: use qemu-img to create valid sparse VMDK (add qemu-utils to
test container Dockerfile)
- Enable 'images' content type on local storage for OVA import
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
System.Formats.Tar is not available as a standalone NuGet backport for
netstandard2.0/net48. Use SharpCompress 0.38.0 on all targets for a
single, well-tested code path that handles GNU long filenames, PAX
extended headers, and other TAR variants correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parses OVF metadata client-side from the OVA TAR archive to extract
VM name, CPU, memory, disks, and network adapters. Then uploads the
OVA with content=import, creates the VM, and imports each disk.
- OvfMetadata parser with multi-target TAR support (System.Formats.Tar
on net9.0, manual 512-byte header parsing on netstandard2.0/net48)
- VmService.UploadOva() for content=import uploads
- Import-PveOva cmdlet with -Name/-Memory/-Cores overrides and -Wait
- Pester unit tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Imports disk images (qcow2, raw, vmdk) and OVA archives into VMs
via the PVE API using POST config with import-from syntax.
- VmService.ImportDisk() method using POST /nodes/{node}/qemu/{vmid}/config
- Import-PveVmDisk cmdlet with -Source, -Disk, -TargetStorage, -Format, -Wait
- Pester unit tests
- prepare-test-vm.sh updated to use Import-PveVmDisk instead of SSH qm importdisk
- Set-PveNetwork: add required Type parameter (fixes PVE API 400 error)
- Integration test fix: pass -Type bridge to Set-PveNetwork
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace `new Random()` with `RandomNumberGenerator` in
PveHttpClient.GenerateBoundary(). Uses the static Fill() method on
.NET Core and the disposable Create() pattern on net48/netstandard2.0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add missing XML doc comments to all public types and members in
PSProxmoxVE.Core (Authentication, Exceptions, Models, Client)
- Remove CS1591 suppression from PSProxmoxVE.Core.csproj
- Build succeeds with 0 warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Container snapshots (4 cmdlets):
- Get-PveContainerSnapshot, New-PveContainerSnapshot
- Remove-PveContainerSnapshot, Restore-PveContainerSnapshot
- Service methods in ContainerService
- Pester unit tests
SDN subnet management (3 cmdlets):
- Get-PveSdnSubnet, New-PveSdnSubnet, Remove-PveSdnSubnet
- PveSdnSubnet model and NetworkService methods
- Pester unit tests
Also updates manifest (73 cmdlets), README cmdlet reference, and CHANGELOG.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The net9.0 DLL references System.Runtime 9.0.0.0 which is unavailable
on GitHub runners where pwsh ships with .NET 8.x. Adding netstandard2.0
as a target produces a DLL compatible with any .NET version (PS 5.1 on
.NET Framework through PS 7.x on .NET 8/9).
- Add netstandard2.0 to TargetFrameworks in Core and module csproj
- Update #if NET48 conditionals to include NETSTANDARD2_0
- Switch CI workflows from net9.0 to netstandard2.0 for Pester tests
- Update _TestHelper.ps1 framework search order
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CS8601: Add null-forgiving operator on guarded dictionary assignments
in cmdlets where IsNullOrEmpty check precedes the assignment
- CS8602: Add Assert.NotNull after JObject["data"] in xUnit model tests
(JToken indexer returns nullable on net48)
- CS8604: Add null-forgiving on guarded arguments in NodeService,
WaitPveTaskCmdlet, GetPveTemplateCmdlet, and auth test parameters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CS8604: Use cmdlet's own Node property instead of nullable task.Node
in WaitForTask calls across all VM cmdlets
- CS8618: Make PveSession.ServerVersion nullable (PveVersion?) since it
is set post-construction by PveAuthenticator
- CS8603: Add null-forgiving operator where IsNullOrWhiteSpace guards
guarantee non-null in PveHttpClient
- CS1573: Add XML param documentation to all public service methods
- xUnit1012: Make test parameters nullable (string?) for null test cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- BaseUrl trailing slash and resource path cleanup: PveSession.BaseUrl now
ends with '/', all cmdlet resource strings stripped of leading slashes and
/api2/json/ prefixes so URLs compose correctly
- PveNodeStatus: rename Name -> Node to match JSON field and test expectations
- NewPveVmCmdlet: auto-allocate vmid via GET cluster/nextid when -VmId omitted
- UploadFileAsync (BZ 7389 workarounds):
* Unquoted multipart boundary in Content-Type header
* Quoted name= values in Content-Disposition (embedded double-quotes)
* Content-Disposition set before Content-Type on file part — PVE's parser
closes the connection if Content-Type appears first
- SendPveIsoCmdlet: run upload in Task.Run, track progress atomically with
Interlocked, poll and call WriteProgress only from pipeline thread to avoid
InvalidOperationException from PSCmdlet thread-affinity requirements
- Add debug/Capture-UploadDiff.ps1: mitmproxy capture script used to isolate
the header-ordering bug by diffing raw multipart bytes from PS vs C# module
Integration test result: 11 passed, 0 failed, 4 skipped (expected — no
template/stopped VMs on bare nested PVE test node).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement Get-PveApiToken, New-PveApiToken, and Remove-PveApiToken to
enable automated token management without manual Proxmox VE UI interaction.
Adds PveApiToken model, UserService methods for the token endpoints, and
31 Pester unit tests covering parameters, ShouldProcess, and no-session behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.NET's HttpHeaders.Add() validates the Authorization header value
against the HTTP spec, rejecting PVE's custom PVEAPIToken scheme
that contains @, !, and = in the token string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add service classes for nodes, VMs, containers, storage, snapshots,
network/SDN, users/roles/permissions, templates, cloud-init, tasks,
and cluster. SDN services enforce PVE 8.0+ version guard. Task service
supports polling with configurable timeout and progress callbacks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add C# model classes for all PVE API resources: nodes, VMs, VM config,
containers, storage, network interfaces, SDN zones/vnets, users, roles,
permissions, snapshots, tasks, and cluster status. All models use dual
JSON attributes (System.Text.Json + Newtonsoft.Json), nullable optional
fields, and human-readable ToString() overrides.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PveHttpClient handles auth headers, error parsing, and both sync/async
request methods. ISO upload constructs raw multipart body manually to
avoid .NET MultipartFormDataContent sub-headers that Proxmox pveproxy
mishandles. Streams files in 4MB chunks with progress callback support.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add PveSession, PveAuthenticator, PveVersion, and PveAuthMode. Support
ticket-based auth (username/password with 2hr expiry) and API token auth
(USER@REALM!TOKENID=UUID format). Version detection on connect via
GET /api2/json/version. Custom exception types for API errors, expired
sessions, missing connections, version mismatches, and task failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set up the PSProxmoxVE repository with solution file, project files for
Core library (net10.0+net48), PowerShell module, and xUnit test project.
Includes .gitignore, .editorconfig, LICENSE (MIT), CHANGELOG, and README
with supported version matrix and full cmdlet reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>