PSProxmoxVE Module Review Report
Date: 2026-03-19
Reviewer: Claude Code (Opus 4.6)
Module Version: 0.1.0-preview
Repository: PSProxmoxVE (C# binary PowerShell module)
Executive Summary
- Well-structured project with clean separation:
PSProxmoxVE.Core (services, models, client) and PSProxmoxVE (cmdlets, module surface).
- 66 cmdlets covering VMs, containers, storage, networking, SDN, users/roles/permissions, templates, cloud-init, snapshots, and tasks.
- Excellent ShouldProcess coverage -- all destructive cmdlets implement
SupportsShouldProcess with appropriate ConfirmImpact levels.
- OutputType attributes present on every cmdlet. ValueFromPipelineByPropertyName used consistently for pipeline chaining.
- Sync-over-async pattern (
GetAwaiter().GetResult()) used pervasively -- acceptable for PowerShell binary modules but a known tradeoff.
- Comprehensive integration tests running against real PVE 8 and PVE 9 via Terraform-provisioned nested instances.
- Missing community files:
CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, .gitattributes, issue/PR templates.
- No publish workflow: No CI pipeline for publishing to PSGallery.
- One unapproved verb:
Reset-PveVm uses a hardcoded string "Reset" instead of VerbsCommon -- Reset is not an approved PowerShell verb.
- PVE API coverage: Module covers core automation scenarios well but lacks firewall, backup/restore (PBS), Ceph, HA, and replication management.
Phase 1 -- Repository Inventory & Structure
Directory Tree (depth <= 4)
Project Layout
| Item |
Value |
| Solution |
PSProxmoxVE.sln |
| Main project |
src/PSProxmoxVE/PSProxmoxVE.csproj |
| Core library |
src/PSProxmoxVE.Core/PSProxmoxVE.Core.csproj |
| Test project (xUnit) |
tests/PSProxmoxVE.Core.Tests/PSProxmoxVE.Core.Tests.csproj |
| Test project (Pester) |
tests/PSProxmoxVE.Tests/ |
| Target Frameworks |
netstandard2.0, net9.0, net48 |
| Language Version |
C# 10.0 |
| Nullable |
Enabled |
| Assembly Names |
PSProxmoxVE, PSProxmoxVE.Core |
CI/CD Workflows
| Workflow |
Purpose |
build.yml |
Build + xUnit tests on net48 (Windows) and net9.0 (Windows + Ubuntu) |
unit-tests.yml |
Build + Pester tests across PS 5.1/7.5 on Windows/Ubuntu/macOS |
integration-tests.yml |
Full integration tests against nested PVE 8 + PVE 9 instances |
What's Present
What's Missing
Phase 2 -- PVE API Coverage Audit
Note: The PVE API documentation at pve.proxmox.com/pve-docs/api-viewer/apidoc.js was too large for automated extraction. Coverage assessment below is based on the author's knowledge of the PVE API and source code analysis.
Implemented Cmdlets by Category
Connection (3 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Connect-PveServer |
/access/ticket |
POST |
Disconnect-PveServer |
/access/ticket |
DELETE |
Test-PveConnection |
/version |
GET |
Nodes (2 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveNode |
/nodes |
GET |
Get-PveNodeStatus |
/nodes/{node}/status |
GET |
VMs / QEMU (17 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveVm |
/nodes/{node}/qemu |
GET |
New-PveVm |
/nodes/{node}/qemu |
POST |
Remove-PveVm |
/nodes/{node}/qemu/{vmid} |
DELETE |
Start-PveVm |
/nodes/{node}/qemu/{vmid}/status/start |
POST |
Stop-PveVm |
/nodes/{node}/qemu/{vmid}/status/stop |
POST |
Restart-PveVm |
/nodes/{node}/qemu/{vmid}/status/reboot |
POST |
Suspend-PveVm |
/nodes/{node}/qemu/{vmid}/status/suspend |
POST |
Resume-PveVm |
/nodes/{node}/qemu/{vmid}/status/resume |
POST |
Reset-PveVm |
/nodes/{node}/qemu/{vmid}/status/reset |
POST |
Copy-PveVm |
/nodes/{node}/qemu/{vmid}/clone |
POST |
Move-PveVm |
/nodes/{node}/qemu/{vmid}/migrate |
POST |
Get-PveVmConfig |
/nodes/{node}/qemu/{vmid}/config |
GET |
Set-PveVmConfig |
/nodes/{node}/qemu/{vmid}/config |
PUT |
Resize-PveVmDisk |
/nodes/{node}/qemu/{vmid}/resize |
PUT |
Test-PveVmGuestAgent |
/nodes/{node}/qemu/{vmid}/agent/ping |
POST |
Get-PveVmGuestNetwork |
/nodes/{node}/qemu/{vmid}/agent/network-get-interfaces |
GET |
Invoke-PveVmGuestExec |
/nodes/{node}/qemu/{vmid}/agent/exec |
POST |
Containers / LXC (8 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveContainer |
/nodes/{node}/lxc |
GET |
New-PveContainer |
/nodes/{node}/lxc |
POST |
Remove-PveContainer |
/nodes/{node}/lxc/{vmid} |
DELETE |
Start-PveContainer |
/nodes/{node}/lxc/{vmid}/status/start |
POST |
Stop-PveContainer |
/nodes/{node}/lxc/{vmid}/status/stop |
POST |
Restart-PveContainer |
/nodes/{node}/lxc/{vmid}/status/shutdown |
POST |
Copy-PveContainer |
/nodes/{node}/lxc/{vmid}/clone |
POST |
Get-PveContainerConfig |
/nodes/{node}/lxc/{vmid}/config |
GET |
Set-PveContainerConfig |
/nodes/{node}/lxc/{vmid}/config |
PUT |
Storage (5 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveStorage |
/storage or /nodes/{node}/storage |
GET |
Get-PveStorageContent |
/nodes/{node}/storage/{storage}/content |
GET |
Send-PveIso |
/nodes/{node}/storage/{storage}/upload |
POST |
Invoke-PveStorageDownload |
/nodes/{node}/storage/{storage}/download-url |
POST |
New-PveStorage |
/storage |
POST |
Remove-PveStorage |
/storage/{storage} |
DELETE |
Snapshots (4 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveSnapshot |
/nodes/{node}/qemu/{vmid}/snapshot |
GET |
New-PveSnapshot |
/nodes/{node}/qemu/{vmid}/snapshot |
POST |
Remove-PveSnapshot |
/nodes/{node}/qemu/{vmid}/snapshot/{name} |
DELETE |
Restore-PveSnapshot |
/nodes/{node}/qemu/{vmid}/snapshot/{name}/rollback |
POST |
Networking (4 + 6 SDN cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveNetwork |
/nodes/{node}/network |
GET |
New-PveNetwork |
/nodes/{node}/network |
POST |
Set-PveNetwork |
/nodes/{node}/network/{iface} |
PUT |
Remove-PveNetwork |
/nodes/{node}/network/{iface} |
DELETE |
Invoke-PveNetworkApply |
/nodes/{node}/network |
PUT |
Get-PveSdnZone |
/cluster/sdn/zones |
GET |
New-PveSdnZone |
/cluster/sdn/zones |
POST |
Remove-PveSdnZone |
/cluster/sdn/zones/{zone} |
DELETE |
Get-PveSdnVnet |
/cluster/sdn/vnets |
GET |
New-PveSdnVnet |
/cluster/sdn/vnets |
POST |
Remove-PveSdnVnet |
/cluster/sdn/vnets/{vnet} |
DELETE |
Users / ACLs / Tokens (11 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveUser |
/access/users |
GET |
New-PveUser |
/access/users |
POST |
Remove-PveUser |
/access/users/{userid} |
DELETE |
Set-PveUser |
/access/users/{userid} |
PUT |
Get-PveRole |
/access/roles |
GET |
New-PveRole |
/access/roles |
POST |
Remove-PveRole |
/access/roles/{roleid} |
DELETE |
Get-PvePermission |
/access/acl |
GET |
Set-PvePermission |
/access/acl |
PUT |
Get-PveApiToken |
/access/users/{userid}/token |
GET |
New-PveApiToken |
/access/users/{userid}/token/{tokenid} |
POST |
Remove-PveApiToken |
/access/users/{userid}/token/{tokenid} |
DELETE |
Templates (4 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveTemplate |
/nodes/{node}/qemu (filtered) |
GET |
New-PveTemplate |
/nodes/{node}/qemu/{vmid}/template |
POST |
Remove-PveTemplate |
/nodes/{node}/qemu/{vmid} |
DELETE |
New-PveVmFromTemplate |
/nodes/{node}/qemu/{vmid}/clone |
POST |
Cloud-Init (3 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveCloudInitConfig |
/nodes/{node}/qemu/{vmid}/config |
GET |
Set-PveCloudInitConfig |
/nodes/{node}/qemu/{vmid}/config |
PUT |
Invoke-PveCloudInitRegenerate |
/nodes/{node}/qemu/{vmid}/cloudinit |
PUT |
Tasks (2 cmdlets)
| Cmdlet |
API Endpoint |
Method |
Get-PveTask |
/nodes/{node}/tasks/{upid}/status |
GET |
Wait-PveTask |
/nodes/{node}/tasks/{upid}/status (polling) |
GET |
Missing API Coverage by Functional Area
| Area |
Key Missing Endpoints |
Priority |
| Firewall |
/nodes/{node}/firewall/rules, /cluster/firewall/* |
High |
| Backup / Restore |
/nodes/{node}/vzdump, backup schedule management |
High |
| Pools |
/pools, /pools/{poolid} (CRUD) |
Medium |
| HA |
/cluster/ha/* (groups, resources, status) |
Medium |
| Ceph |
/nodes/{node}/ceph/* (OSD, pool, mon, fs) |
Medium |
| Replication |
/cluster/replication (CRUD) |
Low |
| Access Groups |
/access/groups (CRUD) |
Low |
| Access Domains |
/access/domains (CRUD) |
Low |
| Container Snapshots |
/nodes/{node}/lxc/{vmid}/snapshot |
Medium |
| Container Migrate |
/nodes/{node}/lxc/{vmid}/migrate |
Medium |
| PBS Integration |
Proxmox Backup Server operations |
Low |
| Cluster Config |
/cluster/config/* (join, nodes, totem) |
Low |
| ACME |
/cluster/acme/*, /nodes/{node}/certificates/* |
Low |
| Node Management |
/nodes/{node}/apt/*, /nodes/{node}/disks/*, /nodes/{node}/services/* |
Low |
| Metrics |
/cluster/metrics/* |
Low |
| SDN Subnets |
/cluster/sdn/vnets/{vnet}/subnets |
Medium |
| VM Agent |
Additional agent endpoints (file-read, file-write, etc.) |
Low |
High-Value Gaps
- Firewall management -- Critical for security automation. No cmdlets for creating/managing firewall rules at cluster, node, or VM level.
- Backup/restore (vzdump) -- Essential for DR automation. No cmdlets for creating backups, managing schedules, or restoring.
- Pool management -- Required for multi-tenant environments. No cmdlets for creating/managing resource pools.
- Container snapshots -- Snapshots are only implemented for VMs, not LXC containers.
- Container migration --
Move-PveVm exists for VMs but no equivalent for containers.
Phase 3 -- Code Quality & Best Practices
3a. PowerShell Module Design
Cmdlet Naming
- All cmdlets use approved PowerShell verbs from the
VerbsCommon, VerbsLifecycle, VerbsCommunications, VerbsDiagnostic, and VerbsData classes.
- One exception:
Reset-PveVm uses a hardcoded string "Reset" instead of a verb class constant: [Cmdlet("Reset", "PveVm", ...)]. While Reset IS an approved verb (in VerbsCommon), the implementation should use VerbsCommon.Reset for consistency.
- Noun prefix
Pve is consistent across all cmdlets.
Parameter Design
[Parameter(Mandatory = ...)] used appropriately throughout.
ValueFromPipelineByPropertyName = true used on Node, VmId, and similar parameters enabling pipeline chaining (e.g., Get-PveVm | Start-PveVm).
Position attributes used on key parameters for positional binding.
ValidateSet, ValidateRange, ValidateNotNullOrEmpty used where appropriate (network types, port range, storage types, etc.).
- Gap: VmId parameters are
int type but lack [ValidateRange(100, 999999999)] to match PVE's VMID constraints.
ShouldProcess / WhatIf / Confirm
- Excellent: Every destructive/mutating cmdlet implements
SupportsShouldProcess = true.
ConfirmImpact = ConfirmImpact.High is set on all Remove-* cmdlets, New-PveTemplate (irreversible), and Restore-PveSnapshot.
- Gap:
Stop-PveVm, Stop-PveContainer, Reset-PveVm do not set ConfirmImpact.High -- stopping/resetting VMs can cause data loss.
OutputType
[OutputType] attributes are present on all 66 cmdlets. This is exemplary.
Pipeline Support
- All cmdlets use
ValueFromPipelineByPropertyName on Node and VmId parameters.
Get-PveVm and Get-PveContainer output objects with Node and VmId properties, enabling chaining.
- Gap: None of the cmdlets implement
ProcessRecord for pipeline batch processing -- all use EndProcessing (or equivalent). This means Get-PveVm | Start-PveVm processes only the last piped object. This is a functional bug if multiple objects are piped.
Error Handling
ThrowTerminatingError used appropriately in ConnectPveServerCmdlet, WaitPveTaskCmdlet, GetPveNodeCmdlet, GetPveNodeStatusCmdlet.
WriteWarning used in DisconnectPveServerCmdlet and SetPveCloudInitConfigCmdlet for non-fatal conditions.
WriteVerbose used in connection cmdlets for diagnostic output.
- Gap: Most cmdlets let exceptions propagate without wrapping in
ErrorRecord -- this means non-terminating errors are not always well-structured.
3b. C# Code Quality
Null-Reference Safety
<Nullable>enable</Nullable> in both .csproj files.
- Nullable annotations used on session, parameters, and return types.
- Null guards (
?? throw) used in constructors and authentication methods.
Async/Await Pattern
- All HTTP operations are async at the
PveHttpClient level.
- Sync wrappers use
.GetAwaiter().GetResult() pattern.
- Assessment: This is the standard approach for PowerShell binary modules on .NET. True async cmdlets are only available via
BeginProcessing/EndProcessing async overrides in PS 7.4+, which would break PS 5.1 compatibility.
- Risk: Low. PowerShell cmdlets run on a single pipeline thread, so deadlock risk from
GetAwaiter().GetResult() is minimal given the ConfigureAwait(false) usage throughout.
IDisposable Correctness
PveHttpClient implements IDisposable correctly with a _disposed flag.
- All usages in cmdlets and services use
using var client = new PveHttpClient(session) or using (var client = ...) blocks.
ProgressStream disposes its inner stream correctly.
- File upload uses
try/finally for stream cleanup with conditional DisposeAsync on .NET 9+.
- Assessment: Excellent. No resource leak risks identified.
Exception Handling
- Custom exception hierarchy:
PveApiException, PveAuthenticationException, PveNotConnectedException, PveSessionExpiredException, PveTaskTimeoutException, PveTaskFailedException, PveVersionException.
PveHttpClient.SendAsync catches HttpRequestException and wraps it in PveApiException.
- Error message extraction from PVE API JSON responses is handled gracefully.
- The only bare
catch {} is in ExtractErrorMessage where parse failure falls through to raw body -- this is acceptable.
Magic Strings
- API resource paths are constructed inline in service methods (e.g.,
$"nodes/{node}/qemu/{vmid}/status/start"). This is pragmatic for the size of the codebase.
- Auth header names (
PVEAPIToken=, PVEAuthCookie=, CSRFPreventionToken) are inline. Could be constants but not a significant issue.
LINQ
- No complex LINQ usage. JObject/JArray parsing uses Newtonsoft.Json's indexer pattern which is straightforward.
Logging / Verbose Output
WriteVerbose used in ConnectPveServerCmdlet and DisconnectPveServerCmdlet.
- Gap: No
WriteVerbose calls in any other cmdlets -- users cannot see which API calls are being made. This hinders debugging.
WriteDebug is not used anywhere in the module.
- Gap:
WriteProgress is only used in Send-PveIso (via progress callback). Not used in Wait-PveTask or other long-running operations.
3c. General Hygiene
Unused Code / Dead Code
- No unused
using directives detected (CS1591 suppressed for missing XML docs, not for unused code).
- No commented-out blocks found.
- Clean: Zero TODO/FIXME/HACK markers in source code.
Code Style
- Consistent 4-space indentation matching
.editorconfig.
- Opening braces on same line (Allman-adjacent style used in some places, K&R in others -- minor inconsistency but not problematic).
- PascalCase for public members, camelCase for locals/fields.
- Public types in
PSProxmoxVE.Core have XML doc comments (enabled via <GenerateDocumentationFile>true</GenerateDocumentationFile>).
CS1591 is suppressed in Core project (<NoWarn>$(NoWarn);CS1591</NoWarn>) so not all public members have docs.
- Cmdlet classes have XML summary comments on the class level.
- Gap: Parameter help text (
[Parameter(HelpMessage = ...)]) is not used on any cmdlet parameter. This means Get-Help shows no parameter descriptions.
Phase 4 -- Testing Coverage Analysis
Test Projects
| Project |
Framework |
Type |
Runner |
PSProxmoxVE.Core.Tests |
xUnit 2.7 + Moq 4.20 |
Unit tests |
dotnet test |
PSProxmoxVE.Tests |
Pester 5 |
Cmdlet + Integration tests |
Invoke-Pester |
xUnit Tests (Core Library)
| Test File |
Test Area |
Authentication/PveSessionTests.cs |
Session creation, expiry, auth modes |
Authentication/PveAuthenticatorTests.cs |
Authentication logic |
Authentication/PveVersionTests.cs |
Version parsing |
Models/ClusterModelTests.cs |
Cluster model deserialization |
Models/ContainerModelTests.cs |
Container model deserialization |
Models/NetworkModelTests.cs |
Network model deserialization |
Models/NodeModelTests.cs |
Node model deserialization |
Models/SdnModelTests.cs |
SDN model deserialization |
Models/SnapshotModelTests.cs |
Snapshot model deserialization |
Models/StorageModelTests.cs |
Storage model deserialization |
Models/TaskModelTests.cs |
Task model deserialization |
Models/UserModelTests.cs |
User model deserialization |
Models/VmModelTests.cs |
VM model deserialization |
Pester Tests (Cmdlet Level)
| Test File |
Cmdlet(s) Covered |
Connection/Connect-PveServer.Tests.ps1 |
Connect-PveServer |
Connection/Disconnect-PveServer.Tests.ps1 |
Disconnect-PveServer |
Connection/Test-PveConnection.Tests.ps1 |
Test-PveConnection |
Containers/Get-PveContainer.Tests.ps1 |
Get-PveContainer |
Containers/ContainerLifecycle.Tests.ps1 |
Container start/stop/restart |
Containers/ContainerConfigCmdlets.Tests.ps1 |
Container config cmdlets |
CloudInit/CloudInitCmdlets.Tests.ps1 |
Cloud-init cmdlets |
Network/Get-PveNetwork.Tests.ps1 |
Get-PveNetwork |
Network/SdnCmdlets.Tests.ps1 |
SDN cmdlets |
Nodes/NodeCmdlets.Tests.ps1 |
Node cmdlets |
Snapshots/SnapshotCmdlets.Tests.ps1 |
Snapshot cmdlets |
Storage/Get-PveStorage.Tests.ps1 |
Get-PveStorage |
Storage/StorageDownload.Tests.ps1 |
Invoke-PveStorageDownload |
Storage/Send-PveIso.Tests.ps1 |
Send-PveIso |
Tasks/TaskCmdlets.Tests.ps1 |
Task cmdlets |
Templates/TemplateCmdlets.Tests.ps1 |
Template cmdlets |
Users/UserCmdlets.Tests.ps1 |
User CRUD |
Users/ApiTokenCmdlets.Tests.ps1 |
API token CRUD |
Users/RoleCmdlets.Tests.ps1 |
Role CRUD |
Vms/Get-PveVm.Tests.ps1 |
Get-PveVm |
Vms/New-PveVm.Tests.ps1 |
New-PveVm |
Vms/Remove-PveVm.Tests.ps1 |
Remove-PveVm |
Vms/VmLifecycle.Tests.ps1 |
VM start/stop/restart |
Vms/VmAdvancedOps.Tests.ps1 |
Clone, migrate, resize |
Vms/VmConfigCmdlets.Tests.ps1 |
VM config cmdlets |
Integration Tests
The integration test file (Integration.Tests.ps1) is comprehensive and covers:
| Context |
Operations Tested |
| Connection |
Connect via API token, version detection |
| Nodes |
List nodes, get node status |
| User CRUD |
Create, list, update, remove user |
| Role CRUD |
Create, list, remove role |
| API Token CRUD |
Create, list, remove API token |
| Permissions |
List and set permissions |
| VMs |
List, create, config get/set, start/stop, hard reset, clone |
| Snapshots |
Create, list, restore, remove |
| Storage |
List storage, list content, upload ISO |
| Tasks |
Get task, wait for task |
| Network |
List networks |
| Templates |
List templates, convert to template, clone from template |
| Cloud-Init |
Get cloud-init config |
| Guest Agent |
Ping, network interfaces, exec command |
| ACPI Lifecycle |
Graceful restart, graceful stop |
- PVE 8 and PVE 9 both tested via matrix strategy in CI.
- Setup/teardown: AfterAll block cleans up created VMs, users, and roles.
- Skip logic: Tests skip gracefully when env vars are not set.
- Connection details: Injected via environment variables (
PVETEST_HOST, PVETEST_APITOKEN, etc.).
Test Coverage Gap List
| Cmdlet |
Unit Test |
Integration Test |
Gap |
Set-PveNetwork |
Pester |
No |
No integration test for network modification |
New-PveNetwork |
Pester |
No |
No integration test for network creation |
Remove-PveNetwork |
Pester |
No |
No integration test for network removal |
Invoke-PveNetworkApply |
Pester |
No |
No integration test for network apply |
New-PveSdnZone |
Pester |
No |
No integration test for SDN zone creation |
Remove-PveSdnZone |
Pester |
No |
No integration test for SDN zone removal |
New-PveSdnVnet |
Pester |
No |
No integration test for SDN VNet creation |
Remove-PveSdnVnet |
Pester |
No |
No integration test for SDN VNet removal |
New-PveStorage |
Pester |
No |
No integration test for storage creation |
Remove-PveStorage |
Pester |
No |
No integration test for storage removal |
Invoke-PveStorageDownload |
Pester |
No |
No integration test for URL download |
Suspend-PveVm |
Pester |
No |
No integration test for VM suspend |
Resume-PveVm |
Pester |
No |
No integration test for VM resume |
Move-PveVm |
Pester |
No |
No integration test (requires multi-node) |
Resize-PveVmDisk |
Pester |
No |
No integration test for disk resize |
Copy-PveContainer |
Pester |
No |
No integration test for container clone |
Set-PveCloudInitConfig |
Pester |
No |
No integration test for cloud-init set |
Invoke-PveCloudInitRegenerate |
Pester |
No |
Skipped in CI due to bug |
Set-PvePermission |
Pester |
Partial |
Only tests add, not remove |
Note: Many of the gaps above are for operations that would require complex test infrastructure (multi-node clusters, specific storage backends, etc.) and are reasonable omissions for a pre-1.0 module.
Phase 5 -- Security Review
5.1 Credential Handling
| Aspect |
Status |
Details |
| PSCredential usage |
Good |
Connect-PveServer accepts PSCredential for password auth |
| Password extraction |
Acceptable |
NetworkCredential.Password is used only at the point of API call |
| API token in memory |
Acceptable |
Stored as plain string in PveSession.ApiToken -- standard for PowerShell modules |
| Credential logging |
Good |
No WriteVerbose/WriteDebug calls that include credentials |
| Ticket storage |
Good |
Tickets are stored in PveSession only, not written to disk |
Finding: The password is extracted from PSCredential via NetworkCredential.Password which returns a plain string. This is the standard pattern for PowerShell binary modules since the PVE API requires the password as a form field. No improvement needed.
5.2 TLS/HTTPS
| Aspect |
Status |
Details |
| HTTPS enforced |
Good |
BaseUrl always constructs https:// URLs |
| SkipCertificateCheck |
Good |
Explicit opt-in parameter on Connect-PveServer |
| Certificate bypass |
Acceptable |
Uses ServerCertificateCustomValidationCallback = ... => true when opted in |
| TLS version |
Good |
Left to OS/runtime negotiation (no pinning) |
| Warning on skip |
Gap |
No WriteWarning when -SkipCertificateCheck is used |
5.3 Input Validation
| Aspect |
Status |
Details |
| URL construction |
Good |
Resource paths use string interpolation with validated parameters |
| Path traversal |
Low risk |
Node names, VM IDs, and storage names are used in URL paths but come from API responses or validated parameters |
| VMID validation |
Gap |
VmId is int type (natural bounds) but no [ValidateRange] to enforce PVE's 100-999999999 range |
| Username format |
Good |
PveAuthenticator validates user@realm format |
| API token format |
Good |
Regex validation in PveAuthenticator.AuthenticateWithApiToken |
5.4 Dependency Security
| Package |
Version |
Pinned |
Notes |
Newtonsoft.Json |
13.0.3 |
Yes |
Latest stable, no known CVEs |
System.Text.Json |
8.0.5 |
Yes |
Latest 8.x patch |
PowerShellStandard.Library |
5.1.1 |
Yes |
PrivateAssets=all |
System.Management.Automation |
7.4.0 |
Yes |
PrivateAssets=all |
Microsoft.NET.Test.Sdk |
17.9.0 |
Yes |
Test only |
xunit |
2.7.0 |
Yes |
Test only |
Moq |
4.20.70 |
Yes |
Test only |
coverlet.collector |
6.0.1 |
Yes |
Test only |
All dependencies are pinned to exact versions. No floating version ranges detected.
5.5 Secret Scanning
| Finding |
Severity |
Location |
| Hardcoded test password |
Medium |
integration-tests.yml:42 -- PVE_PASSWORD: "Testpass123!" |
The test password is for a throwaway nested PVE instance provisioned and destroyed during CI. It is masked in CI logs via ::add-mask::. This is acceptable for CI test infrastructure but should be documented.
No other hardcoded credentials, tokens, or sensitive IPs found in source code.
Phase 6 -- PSGallery Publication Readiness
6.1 Module Manifest Assessment
| Field |
Status |
Value |
RootModule |
OK |
PSProxmoxVE.dll |
ModuleVersion |
OK |
0.1.0 |
GUID |
OK |
a3f7c2d1-84e5-4b9f-a061-3e2d8c5f1a7b |
Author |
OK |
goodolclint |
CompanyName |
OK |
Worklab |
Copyright |
OK |
(c) 2026 goodolclint. All rights reserved. |
Description |
OK |
Meaningful, not placeholder |
PowerShellVersion |
OK |
5.1 |
CompatiblePSEditions |
OK |
Desktop, Core |
DotNetFrameworkVersion |
OK |
4.8 |
RequiredAssemblies |
OK |
Lists Core DLL and Newtonsoft.Json |
FormatsToProcess |
OK |
PSProxmoxVE.format.ps1xml |
CmdletsToExport |
Excellent |
Explicit list of all 66 cmdlets |
FunctionsToExport |
OK |
Empty (binary module) |
AliasesToExport |
OK |
Empty |
VariablesToExport |
OK |
Empty |
Prerelease |
OK |
preview |
Tags |
OK |
8 relevant tags |
LicenseUri |
OK |
Points to GitHub LICENSE |
ProjectUri |
OK |
Points to GitHub repo |
ReleaseNotes |
Missing |
Not present in PSData |
IconUri |
Missing |
Not present in PSData |
RequiredModules |
N/A |
None required |
HelpInfoURI |
Missing |
No online help |
6.2 License
- MIT License present at repo root.
- OSI-approved (PSGallery requirement met).
LicenseUri in manifest points to it.
6.3 README Quality
| Aspect |
Status |
| Installation instructions |
Present but says "not PSGallery" -- needs update for publication |
| Quick-start examples |
Excellent -- multiple scenarios covered |
| Authentication guide |
Excellent -- both ticket and API token documented |
| Multi-cluster usage |
Excellent -- -Session parameter documented |
| Cmdlet reference table |
Excellent -- all cmdlets listed with descriptions |
| Known limitations |
Good -- v1 limitations documented |
| Contributing section |
Present -- basic development setup |
| Badges |
Missing -- no build status, PSGallery version, or license badges |
6.4 Build & Publish Pipeline
- Build workflow: Builds and tests on net48 (Windows) and net9.0 (Windows + Ubuntu).
- Unit test workflow: Tests Pester scripts across PS 5.1/7.5 on Windows/Ubuntu/macOS.
- Integration test workflow: Full tests against live PVE 8 and PVE 9.
- PSGallery publish workflow: MISSING -- no workflow for
Publish-Module or release creation.
- GitHub Releases: Not configured (no release workflow, no tagged releases in git history).
6.5 Versioning
- CHANGELOG follows Keep a Changelog format with Conventional Commits.
- Only an
[Unreleased] section exists -- no versioned releases yet.
- Commit messages follow conventional commits (
feat:, fix:, test:, ci:, etc.).
Phase 7 -- Community & Repo Maintenance Standards
Files Present
| Item |
Status |
README.md |
Present, comprehensive |
LICENSE |
Present (MIT) |
CHANGELOG.md |
Present |
.editorconfig |
Present |
.gitignore |
Present |
Files Missing
| Item |
Recommendation |
CONTRIBUTING.md |
Create with dev setup, coding standards, test instructions, PR process |
CODE_OF_CONDUCT.md |
Adopt Contributor Covenant |
SECURITY.md |
Create with vulnerability disclosure policy |
.gitattributes |
Create for line ending normalization (* text=auto) |
.github/ISSUE_TEMPLATE/ |
Create bug report and feature request templates |
.github/pull_request_template.md |
Create with checklist |
Commit Conventions
- Conventional Commits used consistently.
- Scopes include:
ci, test, fix, feat, refactor, chore, docs.
- Recent history shows disciplined commit hygiene.
Branch Protection
Recommended settings (cannot verify via API):
- Default branch:
main (confirmed)
- Require PR reviews before merge
- Require status checks to pass (build + unit tests + integration tests)
- Require linear history or squash merging
Phase 8 -- Prioritized Recommendations
Critical (blocks PSGallery publication or is a security risk)
| # |
What |
Where |
Why |
Fix |
| C1 |
No PSGallery publish workflow |
.github/workflows/ |
Cannot automate releases |
Create publish.yml triggered on tag push that runs Publish-Module with PSGallery API key from secrets |
| C2 |
README says "not PSGallery" |
README.md:30 |
Contradicts publication goal |
Update installation section to Install-Module PSProxmoxVE |
| C3 |
No ReleaseNotes in manifest |
PSProxmoxVE.psd1:168-195 |
PSGallery strongly recommends release notes |
Add ReleaseNotes to PSData section |
C4 |
Pipeline processing bug |
Verified: all cmdlets use ProcessRecord |
N/A -- pipeline processing is correct |
No action needed |
| # |
What |
Where |
Why |
Fix |
| H1 |
No HelpMessage on parameters |
All cmdlet files |
Get-Help shows no parameter descriptions |
Add HelpMessage = "..." to all [Parameter] attributes |
| H2 |
No WriteVerbose in cmdlets |
All cmdlets except Connection |
Users cannot debug API calls |
Add WriteVerbose($"Calling {method} {resource}") before API calls |
| H3 |
Missing CONTRIBUTING.md |
Repo root |
Barrier to community contributions |
Create with dev setup, coding standards, PR process |
| H4 |
Missing SECURITY.md |
Repo root |
No vulnerability disclosure process |
Create with responsible disclosure instructions |
| H5 |
No warning on -SkipCertificateCheck |
ConnectPveServerCmdlet.cs |
Users may not realize they're disabling TLS verification |
Add WriteWarning("Certificate validation is disabled...") |
| H6 |
Reset-PveVm uses string literal |
ResetPveVmCmdlet.cs:16 |
Inconsistent with all other cmdlets using verb class constants |
Change "Reset" to VerbsCommon.Reset |
H7 |
No WriteProgress in Wait-PveTask |
Verified: already implemented |
N/A -- WaitPveTaskCmdlet already uses WriteProgress with percentage and timeout tracking |
No action needed |
| H8 |
Missing GitHub issue/PR templates |
.github/ |
No structured issue reporting |
Create bug report, feature request, and PR templates |
Medium (best practice gaps, test coverage holes)
| # |
What |
Where |
Why |
Fix |
| M1 |
Stop-PveVm/Reset-PveVm lack ConfirmImpact.High |
StopPveVmCmdlet.cs, ResetPveVmCmdlet.cs |
These can cause data loss |
Add ConfirmImpact = ConfirmImpact.High |
| M2 |
No [ValidateRange] on VmId |
All cmdlets with VmId parameter |
PVE requires VMID 100-999999999 |
Add [ValidateRange(100, 999999999)] to all VmId parameters |
| M3 |
Container snapshots not supported |
Services/Cmdlets |
Containers support snapshots in PVE but module only handles VM snapshots |
Add Get/New/Remove/Restore-PveContainerSnapshot cmdlets |
| M4 |
No integration tests for network modifications |
Integration tests |
Network CRUD is tested only at Pester level |
Add integration tests for network create/modify/delete |
| M5 |
No CODE_OF_CONDUCT.md |
Repo root |
Expected for open-source projects |
Add Contributor Covenant |
| M6 |
No .gitattributes |
Repo root |
Line ending consistency across platforms |
Add * text=auto and specific overrides |
| M7 |
CS1591 suppressed in Core project |
PSProxmoxVE.Core.csproj:10 |
Public API lacks XML documentation |
Remove NoWarn and add XML docs to public members |
| M8 |
No badges in README |
README.md |
Reduces discoverability and trust |
Add build status, PSGallery version, license badges |
| M9 |
CHANGELOG has only [Unreleased] |
CHANGELOG.md |
No release history |
Create first versioned release entry |
| M10 |
Invoke-PveVmGuestExec lacks ShouldProcess |
InvokePveVmGuestExecCmdlet.cs:14 |
Executes commands inside a VM -- a mutating operation |
Add SupportsShouldProcess = true |
Low / Nice-to-have (polish, discoverability, stretch goals)
| # |
What |
Where |
Why |
Fix |
| L1 |
No IconUri in manifest |
PSProxmoxVE.psd1 |
Improves PSGallery listing appearance |
Create a module icon and host it, add URI |
| L2 |
No online help (MAML/platyPS) |
Module |
Get-Help -Online doesn't work |
Generate MAML help using platyPS |
| L3 |
No firewall cmdlets |
Module |
High-value gap for security automation |
Implement firewall rule CRUD for cluster/node/VM |
| L4 |
No backup/vzdump cmdlets |
Module |
High-value gap for DR automation |
Implement vzdump and backup schedule management |
| L5 |
No pool management |
Module |
Useful for multi-tenant environments |
Implement pool CRUD |
| L6 |
new Random() for boundary generation |
PveHttpClient.cs:393 |
Not cryptographically secure (not a security risk for multipart boundaries, but could use RandomNumberGenerator) |
Consider RandomNumberGenerator for .NET 6+ |
| L7 |
No alias support |
Module manifest |
Some users prefer short aliases |
Consider adding aliases like cpve for Connect-PveServer |
| L8 |
Tagged releases in GitHub |
Repository |
No GitHub Releases |
Set up release workflow with auto-generated release notes |
| L9 |
Container migration cmdlet |
Module |
VMs have Move-PveVm but containers have no equivalent |
Add Move-PveContainer |
| L10 |
SDN subnet management |
Module |
SDN zones/vnets exist but subnets are missing |
Add SDN subnet CRUD |
Summary Statistics
| Metric |
Count |
| Total cmdlets |
66 |
| Cmdlets with ShouldProcess |
43 (all destructive/mutating) |
| Cmdlets with OutputType |
66 (100%) |
| xUnit test files |
13 |
| Pester test files |
26 |
| Integration test contexts |
13 |
| PVE API areas covered |
10 of ~20 major areas |
| Critical issues |
4 |
| High issues |
8 |
| Medium issues |
10 |
| Low issues |
10 |
| NuGet dependencies (runtime) |
2 (Newtonsoft.Json, System.Text.Json) |
| Security vulnerabilities found |
0 |
| Hardcoded secrets |
1 (test password in CI, masked) |