Commit Graph

44 Commits

Author SHA1 Message Date
Clint Branham 598e9edef2 fix: replace FormUrlEncodedContent with minimal form encoding
.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>
2026-03-25 17:40:00 -05:00
Clint Branham 2e126b96a3 fix: CloudInit regenerate calls PUT endpoint instead of GET dump
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>
2026-03-25 14:06:28 -05:00
copilot-swe-agent[bot] 4a63055c10 chore: remove pre-correction D013-compliance.md and fix JsonHelper XML doc comment nullability
Co-authored-by: GoodOlClint <151449+GoodOlClint@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoodOlClint/PSProxmoxVE/sessions/2feecd30-2ba1-40bc-b1b5-c1fcc485a319
2026-03-25 16:42:29 +00:00
Clint Branham 10c277af10 fix: resolve F085 — PveClusterJoinInfo Nodelist/Totem D013 violations
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>
2026-03-25 11:31:05 -05:00
Clint Branham 3ce19a721c feat: emit native types instead of Newtonsoft JObject/JArray in public APIs
Replace all Newtonsoft.Json.Linq types (JObject, JArray, JToken) exposed
in public service methods, model properties, and cmdlet OutputTypes with
native .NET types (Dictionary<string, object?>, List<Dictionary<string,
object?>>, PSObject).

New utilities:
- JsonHelper: ToNative, ToDictionary, ToListOfDictionaries for recursive
  JToken→native conversion
- NativeListConverter / NativeDictionaryConverter: JsonConverters for
  model properties that deserialize JArray/JObject to native types

Services updated (8 methods):
- NodeService: GetNodeConfig, GetNodeDns → Dictionary<string, object?>
- BackupService: GetNotBackedUp → List<Dictionary<string, object?>>
- ClusterConfigService: GetClusterConfig, GetTotem, GetQdevice → Dictionary
- HaService: GetManagerStatus → Dictionary<string, object?>
- VmService: GetGuestExecStatus → Dictionary<string, object?>

Models updated (2 properties):
- PvePool.Members: JArray → List<Dictionary<string, object?>>
- PveHaRule.Properties: JObject → Dictionary<string, object?>

Cmdlets updated (5):
- GetPveClusterConfigCmdlet: OutputType JToken → Dictionary<string, object>
- GetPveNodeConfigCmdlet: iteration updated for Dictionary
- GetPveNodeDnsCmdlet: iteration updated for Dictionary
- GetPveBackupInfoCmdlet: iteration updated for List<Dictionary>
- InvokePveVmGuestExecCmdlet: polling updated for Dictionary

Added D013 to DECISIONS.md: cmdlets must emit only native or module-defined types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:12:57 -05:00
Clint Branham 51b51354ef fix: address second round of Copilot review feedback
- 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>
2026-03-24 18:47:43 -05:00
GoodOlClint e54f9fb749 Merge branch 'main' into feat/cluster-config-ha 2026-03-24 18:41:20 -05:00
Clint Branham f1a3676723 fix: address Copilot review feedback on PR #27
- 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>
2026-03-24 18:40:10 -05:00
copilot-swe-agent[bot] f400336803 fix: narrow exception catches in PingGuestAgent and ImportPveOvaCmdlet
Co-authored-by: GoodOlClint <151449+GoodOlClint@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoodOlClint/PSProxmoxVE/sessions/e7930807-5536-4c55-96f8-0712aee44e00
2026-03-24 23:35:27 +00:00
Clint Branham da2037d0c5 feat: add cluster config and HA management cmdlets (F060, F053)
Cluster Config (F060) — 11 new cmdlets:
- Get-PveClusterStatus, Get-PveClusterNextId
- Get/Set-PveClusterOption (datacenter settings)
- Get-PveClusterConfig, Get-PveClusterConfigNode
- Add/Remove-PveClusterConfigNode (cluster membership)
- Get-PveClusterJoinInfo, Add-PveClusterMember (join workflow)
- New-PveCluster (create cluster)

HA Management (F053) — 14 new cmdlets:
- Get/New/Set/Remove-PveHaResource (HA managed VMs/CTs)
- Move-PveHaResource (migrate/relocate via HA manager)
- Get/New/Set/Remove-PveHaGroup (node groups + priorities)
- Get-PveHaStatus (HA manager status)
- Get/New/Set/Remove-PveHaRule (PVE 9.0+ version-gated)

All cmdlets follow established conventions:
- sealed classes with [OutputType]
- ConfirmImpact.High on destructive operations (D006)
- SecureString for password parameter in Add-PveClusterMember (D002)
- Uri.EscapeDataString on all path segments (D003)
- Verb class constants (D011)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:28:11 -05:00
Clint Branham c0a37d5ef3 fix: resolve F039 bare catches and F084 session secret exposure
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>
2026-03-24 18:18:56 -05:00
Clint Branham 053fd4c06a fix(security): add Uri.EscapeDataString to all URL path parameters
20 instances across 5 service files were missing URL encoding on
dynamic path segments (node, iface, zone, vnet). Fixes D003 regression.

- NodeService: 7 methods
- NetworkService: 7 methods (5 node-network + 2 SDN remove)
- CloudInitService: 3 methods
- TaskService: 2 methods (GetTask, GetTaskLog)
- TemplateService: 1 method (CreateTemplate)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 17:40:36 -05:00
Clint Branham d19c6f0c4b fix: upgrade SharpCompress from 0.38.0 to 0.47.3 (#12)
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>
2026-03-23 15:53:56 -05:00
Clint Branham 68dadbfdc0 fix: remediate findings F045, F047, F048, F064, F070, F071, F076-F079
Phase 1 — Trivial fixes:
- F071: Add Uri.EscapeDataString to GetPveTemplateCmdlet node path
- F077: Add ValidateRange(100, 999999999) to GetPveTaskListCmdlet.VmId
- F076: Create .github/dependabot.yml (nuget + github-actions, weekly)
- F079: Fix unit-tests.yml dotnet SDK from 9.0.x to 10.0.x
- F048: Mark wont_fix — sync-over-async accepted for PS 5.1 compat

Phase 2 — Framework targeting (D009 compliance):
- F047: Reduce publishable csproj to netstandard2.0 only, remove all
  #if NET48/NETSTANDARD2_0 conditionals from PveHttpClient.cs,
  restructure build.yml for netstandard2.0 publish + net10.0/net48 tests
- F064: Resolved by F047 — SMA 7.5.0 ItemGroup removed with net10.0 TFM
- F070: Add PS 5.1 smoke-test job to publish.yml (windows-latest)

Phase 3 — IPveHttpClient interface extraction (F045):
- Extract IPveHttpClient interface from PveHttpClient
- Add constructor injection to all 14 service classes
- Services use injected client when available, create+dispose when not

Phase 4 — Service unit tests (F078):
- 196 new xUnit tests across 10 service test files
- All services tested via Moq-mocked IPveHttpClient
- Total test count: 382 (was 186)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:51:37 -05:00
Clint Branham 94424367bf fix: remediate scan-4 findings F058, F062, F063, F071, F072, F073, F074, F075
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>
2026-03-23 09:54:28 -05:00
Clint Branham 0532327d46 refactor: remove redundant System.Text.Json attributes from models
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>
2026-03-22 08:15:23 -05:00
Clint Branham 77849062ab fix: add Uri.EscapeDataString to service URL path interpolations
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>
2026-03-22 08:15:02 -05:00
Clint Branham ec8f3a86e2 fix: use specific exception types in catch blocks
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>
2026-03-22 08:14:51 -05:00
Clint Branham df4cae01bc feat: close API coverage gaps — 51 new cmdlets across 6 areas
Phase 1 — Missing Update/Set operations (10 cmdlets):
- SDN: Set-PveSdnZone/Vnet/Subnet/Controller/Ipam/Dns + Invoke-PveSdnApply
- Set-PveRole, Set-PveStorage, Set-PveApiToken

Phase 2 — Cluster operations (8 cmdlets):
- Get-PveClusterResource (cluster-wide inventory)
- Get-PveTaskList, Stop-PveTask
- Get/New/Set/Remove-PvePool
- Get-PveBackupInfo (unprotected VMs)

Phase 3 — VM & container gaps (14 cmdlets):
- Move-PveVmDisk, Remove-PveVmDisk
- Guest agent: Get-PveVmGuestOsInfo/FsInfo, Read/Write-PveVmGuestFile,
  Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim
- Suspend/Resume-PveContainer, Resize-PveContainerDisk,
  New-PveContainerTemplate, Move-PveContainerVolume,
  Get-PveContainerInterface

Phase 4 — Storage content management (4 cmdlets):
- Get-PveStorageStatus, Remove/Set-PveStorageContent, New-PveStorageDisk

Phase 5 — Node operations (6 cmdlets):
- Get/Set-PveNodeConfig, Get/Set-PveNodeDns, Start/Stop-PveNodeVms

Phase 6 — Access management (9 cmdlets):
- Get/New/Set/Remove-PveGroup, Get/New/Set/Remove-PveDomain, Set-PvePassword

Total: 169 cmdlets (was 118). Includes models, services, format views,
Pester unit tests, manifest updates, README, CHANGELOG, API coverage docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 09:55:19 -05:00
Clint Branham 4b503b08db fix: backup job creation requires vmid or -All, fix Enabled default
- 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>
2026-03-21 09:07:39 -05:00
Clint Branham 27ecca6251 feat: add two-tier version gating (introduced vs default) for cmdlets
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>
2026-03-21 08:40:23 -05:00
Clint Branham a72642d203 feat: add firewall, backup, and SDN IPAM/DNS/controller cmdlets
Add 35 new cmdlets bringing the total to 118:
- Firewall (21): rules, groups, aliases, IP sets, options at
  cluster/node/VM/container levels
- Backup (5): ad-hoc vzdump and scheduled backup job CRUD
- SDN IPAM/DNS/Controller (9): plugin management for SDN subsystem

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 07:51:49 -05:00
Clint Branham 8d5f33f592 feat: add -Detailed switch to Get-PveVm for accurate pause detection
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>
2026-03-20 16:18:38 -05:00
Clint Branham 0fc15d74ee fix: OVA import — scsi bus, boot order, NIC model from OVF, metadata verification
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>
2026-03-20 15:56:03 -05:00
Clint Branham c5396707eb fix(test): fix suspend, SDN, container, and OVA integration test failures
- 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>
2026-03-20 14:49:06 -05:00
Clint Branham 6a03be4af2 refactor: replace manual TAR parser with SharpCompress for OVA reading
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>
2026-03-20 13:16:08 -05:00
Clint Branham ca0cd945a0 feat: add Import-PveOva cmdlet for OVA appliance import
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>
2026-03-20 13:08:31 -05:00
Clint Branham 088221a5f1 feat: add Import-PveVmDisk cmdlet for disk image import
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>
2026-03-20 12:55:36 -05:00
Clint Branham fb4a21a4f9 fix: use cryptographic RNG for multipart boundary generation
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>
2026-03-20 12:31:40 -05:00
Clint Branham 14cbff9877 docs: add XML doc comments to Core public API and remove CS1591 suppression
- 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>
2026-03-20 12:21:54 -05:00
Clint Branham ffec75b461 feat: add container snapshot and SDN subnet cmdlets
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>
2026-03-20 12:21:40 -05:00
Clint Branham b12ffc485d feat: add AdditionalConfig parameter and guest agent cmdlets (closes #2, #3)
Issue #3 — Set-PveVmConfig / Set-PveContainerConfig:
- Add -AdditionalConfig [Hashtable] for arbitrary PVE config keys
  (scsi0, boot, agent, net0, ide2, cicustom, etc.)
- Add -Delete [string] for removing config keys
- Unit tests for both new parameters

Issue #2 — QEMU Guest Agent cmdlets:
- Test-PveVmGuestAgent: pings guest agent, returns bool
- Get-PveVmGuestNetwork: returns guest network interfaces with IPs
- Invoke-PveVmGuestExec: executes command in guest, returns output
- New models: PveGuestNetworkInterface, PveGuestIpAddress
- Service methods in VmService for all agent operations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:06:55 -05:00
Clint Branham b8af36a7fc fix(build): add netstandard2.0 target to eliminate System.Runtime version mismatch
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>
2026-03-19 09:34:40 -05:00
Clint Branham d187b0de52 fix: resolve net48-only nullable warnings (CS8601, CS8602, CS8604)
- 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>
2026-03-19 08:32:57 -05:00
Clint Branham 0c70e11fe8 fix: resolve all C# compiler warnings (CS8604, CS8618, CS8603, CS1573, xUnit1012)
- 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>
2026-03-18 18:11:49 -05:00
Clint Branham 8ea983c0fc fix: resolve all integration test failures against live PVE
- 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>
2026-03-18 12:57:21 -05:00
Clint Branham 94b0fcdad2 feat(users): add API token CRUD cmdlets (closes #1)
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>
2026-03-18 08:25:51 -05:00
Clint Branham 5b0c81a1a8 fix(auth): use TryAddWithoutValidation for Authorization header
.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>
2026-03-17 19:14:03 -05:00
Clint Branham dc9b253195 fix(tests): resolve all 400 unit test failures to reach green
Cmdlet fixes:
- GetPveSnapshotCmdlet: add -Name filter; fix s.SnapName → s.Name
- GetPveStorageCmdlet: add -Storage name filter
- GetPveNetworkCmdlet: add -Iface optional filter
- NewPveNetworkCmdlet: rename Interface → Iface (consistency with Set/Remove)
- SetPveNetworkCmdlet, RemovePveNetworkCmdlet: rename Interface → Iface
- RemovePveNetworkCmdlet, RemovePveSdnZoneCmdlet, RemovePveSdnVnetCmdlet,
  RemovePveUserCmdlet: add ConfirmImpact = ConfirmImpact.High
- SetPveCloudInitConfigCmdlet: rename User → CiUser; move GetSession() first;
  remove duplicate session variable
- SendPveIsoCmdlet: add sha512 to ChecksumAlgorithm ValidateSet
- GetPveUserCmdlet: add -Enabled switch; refactor into MatchesFilters(); fix
  int? comparison (Enabled != 1)
- GetPvePermissionCmdlet: rename UgId → UserId
- SetPvePermissionCmdlet: rename RoleId → Role
- NewPveTemplateCmdlet: add ConfirmImpact.High; move GetSession() before
  ShouldProcess so -WhatIf-less calls throw session error first
- NewPveVmFromTemplateCmdlet: rename Node → TemplateNode with [Alias("Node")]
- RemovePveSnapshotCmdlet, RestorePveSnapshotCmdlet: move GetSession() before
  ShouldProcess so session check precedes confirm prompt

Test fixes:
- All 18 Pester test files: update DLL candidates to net9.0
- Fix foreach+It closure capture using -TestCases pattern
- Remove-PveVm, Remove-PveContainer no-session tests: add -Confirm:$false to
  bypass ConfirmImpact.High prompt before GetSession() check
- Get-PveStorageContent test: add mandatory -Node/-Storage params
- Send-PveIso test: create temp file to satisfy FileExistsValidation
- New-PveVmFromTemplate test: add NewVmId to parameter splat

Tooling:
- Invoke-Tests.ps1: add -FromTerraform, explicit lab params, fix TFM
  auto-detection via Select-Xml, fix Pester import, fix variable scoping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:27:12 -05:00
Clint Branham 52f1978de4 feat(services): implement service layer for all domains
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>
2026-03-17 15:45:23 -05:00
Clint Branham 4ac38f7714 feat(models): implement typed response models for PVE 8.x and 9.x
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>
2026-03-17 15:45:06 -05:00
Clint Branham 2c7e90d185 feat(client): implement HTTP client with manual multipart ISO upload (bugzilla 7389 workaround)
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>
2026-03-17 15:44:12 -05:00
Clint Branham d802b117ba feat(auth): implement ticket and API token authentication with session model
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>
2026-03-17 15:44:00 -05:00
Clint Branham 14d823af28 feat: initialize repository structure and solution
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>
2026-03-17 15:43:50 -05:00