diff --git a/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs
new file mode 100644
index 0000000..c418f1d
--- /dev/null
+++ b/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Management.Automation;
+using PSProxmoxVE.Core.Authentication;
+
+namespace PSProxmoxVE.Cmdlets.Connection
+{
+ ///
+ /// Establishes an authenticated session to a Proxmox VE server.
+ ///
+ /// Connect-PveServer authenticates against the Proxmox VE API using either a
+ /// PSCredential (username/password) or a pre-generated API token, and stores the
+ /// resulting session in module state for use by subsequent cmdlets.
+ ///
+ ///
+ [Cmdlet(VerbsCommunications.Connect, "PveServer",
+ DefaultParameterSetName = ParameterSetCredential)]
+ [OutputType(typeof(PveSession))]
+ public sealed class ConnectPveServerCmdlet : PSCmdlet
+ {
+ private const string ParameterSetCredential = "Credential";
+ private const string ParameterSetApiToken = "ApiToken";
+
+ /// Hostname or IP address of the Proxmox VE server.
+ [Parameter(Mandatory = true, Position = 0)]
+ [ValidateNotNullOrEmpty]
+ public string Server { get; set; } = string.Empty;
+
+ /// API port. Defaults to 8006.
+ [Parameter(Mandatory = false)]
+ [ValidateRange(1, 65535)]
+ public int Port { get; set; } = 8006;
+
+ /// Username and password credential. Username must include a realm, e.g. root@pam.
+ [Parameter(Mandatory = true, ParameterSetName = ParameterSetCredential)]
+ [ValidateNotNull]
+ public PSCredential? Credential { get; set; }
+
+ ///
+ /// Proxmox VE API token in the format USER@REALM!TOKENID=UUID,
+ /// e.g. root@pam!mytoken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
+ ///
+ [Parameter(Mandatory = true, ParameterSetName = ParameterSetApiToken)]
+ [ValidateNotNullOrEmpty]
+ public string? ApiToken { get; set; }
+
+ /// When specified, skips TLS certificate validation for the server.
+ [Parameter(Mandatory = false)]
+ public SwitchParameter SkipCertificateCheck { get; set; }
+
+ /// When specified, writes the resulting PveSession object to the pipeline.
+ [Parameter(Mandatory = false)]
+ public SwitchParameter PassThru { get; set; }
+
+ protected override void ProcessRecord()
+ {
+ PveSession session;
+
+ switch (ParameterSetName)
+ {
+ case ParameterSetCredential:
+ {
+ if (Credential is null)
+ ThrowTerminatingError(new ErrorRecord(
+ new ArgumentNullException(nameof(Credential)),
+ "CredentialRequired",
+ ErrorCategory.InvalidArgument,
+ null));
+
+ var username = Credential!.UserName;
+ var password = Credential.GetNetworkCredential().Password;
+
+ try
+ {
+ session = PveAuthenticator.AuthenticateWithCredentials(
+ Server, Port, SkipCertificateCheck.IsPresent, username, password);
+ }
+ catch (Exception ex)
+ {
+ ThrowTerminatingError(new ErrorRecord(
+ ex,
+ "PveAuthenticationFailed",
+ ErrorCategory.AuthenticationError,
+ Server));
+ return; // unreachable — satisfies compiler
+ }
+ break;
+ }
+
+ case ParameterSetApiToken:
+ {
+ try
+ {
+ session = PveAuthenticator.AuthenticateWithApiToken(
+ Server, Port, SkipCertificateCheck.IsPresent, ApiToken!);
+ }
+ catch (Exception ex)
+ {
+ ThrowTerminatingError(new ErrorRecord(
+ ex,
+ "PveAuthenticationFailed",
+ ErrorCategory.AuthenticationError,
+ Server));
+ return; // unreachable — satisfies compiler
+ }
+ break;
+ }
+
+ default:
+ ThrowTerminatingError(new ErrorRecord(
+ new InvalidOperationException($"Unknown parameter set: {ParameterSetName}"),
+ "UnknownParameterSet",
+ ErrorCategory.InvalidOperation,
+ null));
+ return;
+ }
+
+ ModuleState.ActiveSession = session;
+
+ WriteVerbose($"Connected to {Server}:{Port} as {session.AuthMode} (PVE {session.ServerVersion}).");
+
+ if (PassThru.IsPresent)
+ WriteObject(session);
+ }
+ }
+}
diff --git a/src/PSProxmoxVE/Cmdlets/Connection/DisconnectPveServerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Connection/DisconnectPveServerCmdlet.cs
new file mode 100644
index 0000000..eb9a711
--- /dev/null
+++ b/src/PSProxmoxVE/Cmdlets/Connection/DisconnectPveServerCmdlet.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Management.Automation;
+using PSProxmoxVE.Core.Authentication;
+using PSProxmoxVE.Core.Client;
+
+namespace PSProxmoxVE.Cmdlets.Connection
+{
+ ///
+ /// Closes the active Proxmox VE session.
+ ///
+ /// Disconnect-PveServer invalidates the module-level session. For ticket-based
+ /// sessions it additionally attempts a best-effort DELETE against the
+ /// /access/ticket endpoint to invalidate the server-side ticket. Errors from
+ /// that request are silently ignored so that the local session is always cleared.
+ ///
+ ///
+ [Cmdlet(VerbsCommunications.Disconnect, "PveServer",
+ SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Low)]
+ public sealed class DisconnectPveServerCmdlet : PSCmdlet
+ {
+ protected override void ProcessRecord()
+ {
+ var session = ModuleState.ActiveSession;
+
+ if (session is null)
+ {
+ WriteWarning("No active Proxmox VE session to disconnect.");
+ return;
+ }
+
+ if (!ShouldProcess($"{session.Hostname}:{session.Port}", "Disconnect"))
+ return;
+
+ // Best-effort server-side ticket invalidation for ticket-based auth.
+ if (session.AuthMode == PveAuthMode.Ticket)
+ {
+ try
+ {
+ using var client = new PveHttpClient(session);
+ client.DeleteAsync("/api2/json/access/ticket").GetAwaiter().GetResult();
+ }
+ catch (Exception ex)
+ {
+ // Non-fatal — we still clear the local session below.
+ WriteVerbose($"Server-side ticket invalidation failed (ignored): {ex.Message}");
+ }
+ }
+
+ ModuleState.ActiveSession = null;
+
+ WriteVerbose($"Disconnected from {session.Hostname}:{session.Port}.");
+ }
+ }
+}
diff --git a/src/PSProxmoxVE/Cmdlets/Connection/TestPveConnectionCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Connection/TestPveConnectionCmdlet.cs
new file mode 100644
index 0000000..77bac3e
--- /dev/null
+++ b/src/PSProxmoxVE/Cmdlets/Connection/TestPveConnectionCmdlet.cs
@@ -0,0 +1,42 @@
+using System.Management.Automation;
+using PSProxmoxVE.Core.Authentication;
+
+namespace PSProxmoxVE.Cmdlets.Connection
+{
+ ///
+ /// Tests whether an active, non-expired Proxmox VE session exists.
+ ///
+ /// Without -Detailed, returns $true if a session is present and not expired, otherwise $false.
+ /// With -Detailed, returns the PveSession object (or nothing if there is no active session).
+ ///
+ ///
+ [Cmdlet(VerbsDiagnostic.Test, "PveConnection")]
+ [OutputType(typeof(bool), ParameterSetName = new[] { "Default" })]
+ [OutputType(typeof(PveSession), ParameterSetName = new[] { "Detailed" })]
+ public sealed class TestPveConnectionCmdlet : PSCmdlet
+ {
+ ///
+ /// When specified, writes the PveSession object instead of a boolean.
+ /// Nothing is written if no session is active.
+ ///
+ [Parameter(Mandatory = false, ParameterSetName = "Detailed")]
+ public SwitchParameter Detailed { get; set; }
+
+ protected override void ProcessRecord()
+ {
+ var session = ModuleState.ActiveSession;
+ var isConnected = session is not null && !session.IsExpired;
+
+ if (Detailed.IsPresent)
+ {
+ if (isConnected)
+ WriteObject(session);
+ // If not connected, write nothing — caller can check $null.
+ }
+ else
+ {
+ WriteObject(isConnected);
+ }
+ }
+ }
+}
diff --git a/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeCmdlet.cs
new file mode 100644
index 0000000..f4aa232
--- /dev/null
+++ b/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeCmdlet.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PSProxmoxVE.Core.Client;
+using PSProxmoxVE.Core.Models.Nodes;
+
+namespace PSProxmoxVE.Cmdlets.Nodes
+{
+ ///
+ /// Returns one or more Proxmox VE cluster nodes.
+ ///
+ /// Get-PveNode retrieves all nodes visible to the authenticated session.
+ /// Use -Name to filter to a specific node by exact name.
+ ///
+ ///
+ [Cmdlet(VerbsCommon.Get, "PveNode")]
+ [OutputType(typeof(PveNode))]
+ public sealed class GetPveNodeCmdlet : PveCmdletBase
+ {
+ /// Optional node name filter. When specified, only the matching node is returned.
+ [Parameter(Mandatory = false, Position = 0)]
+ [ValidateNotNullOrEmpty]
+ public string? Name { get; set; }
+
+ protected override void ProcessRecord()
+ {
+ var session = GetSession();
+
+ string responseBody;
+ try
+ {
+ using var client = new PveHttpClient(session);
+ responseBody = client.GetAsync("/api2/json/nodes").GetAwaiter().GetResult();
+ }
+ catch (Exception ex)
+ {
+ ThrowTerminatingError(new ErrorRecord(
+ ex,
+ "GetPveNodeFailed",
+ ErrorCategory.ConnectionError,
+ session.Hostname));
+ return;
+ }
+
+ var json = JObject.Parse(responseBody);
+ var dataToken = json["data"] ?? throw new InvalidOperationException("Response did not contain a 'data' field.");
+
+ var nodes = dataToken.ToObject>(
+ JsonSerializer.CreateDefault()) ?? new List();
+
+ foreach (var node in nodes)
+ {
+ if (Name is null || string.Equals(node.Name, Name, StringComparison.OrdinalIgnoreCase))
+ WriteObject(node);
+ }
+ }
+ }
+}
diff --git a/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeStatusCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeStatusCmdlet.cs
new file mode 100644
index 0000000..6d2e839
--- /dev/null
+++ b/src/PSProxmoxVE/Cmdlets/Nodes/GetPveNodeStatusCmdlet.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Management.Automation;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PSProxmoxVE.Core.Client;
+using PSProxmoxVE.Core.Models.Nodes;
+
+namespace PSProxmoxVE.Cmdlets.Nodes
+{
+ ///
+ /// Returns detailed status information for a Proxmox VE node.
+ ///
+ /// Get-PveNodeStatus retrieves CPU, memory, disk, swap, network and uptime statistics
+ /// for the specified node from the /nodes/{node}/status endpoint.
+ /// The -Node parameter accepts values from the pipeline by property name, so you can
+ /// pipe output from Get-PveNode directly.
+ ///
+ ///
+ [Cmdlet(VerbsCommon.Get, "PveNodeStatus")]
+ [OutputType(typeof(PveNodeStatus))]
+ public sealed class GetPveNodeStatusCmdlet : PveCmdletBase
+ {
+ ///
+ /// Name of the node to query. Accepts pipeline input via the PveNode.Name property.
+ ///
+ [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNullOrEmpty]
+ public string Node { get; set; } = string.Empty;
+
+ protected override void ProcessRecord()
+ {
+ var session = GetSession();
+
+ var resource = $"/api2/json/nodes/{Uri.EscapeDataString(Node)}/status";
+
+ string responseBody;
+ try
+ {
+ using var client = new PveHttpClient(session);
+ responseBody = client.GetAsync(resource).GetAwaiter().GetResult();
+ }
+ catch (Exception ex)
+ {
+ ThrowTerminatingError(new ErrorRecord(
+ ex,
+ "GetPveNodeStatusFailed",
+ ErrorCategory.ConnectionError,
+ Node));
+ return;
+ }
+
+ var json = JObject.Parse(responseBody);
+ var dataToken = json["data"] ?? throw new InvalidOperationException("Response did not contain a 'data' field.");
+
+ var status = dataToken.ToObject(JsonSerializer.CreateDefault())
+ ?? throw new InvalidOperationException("Failed to deserialize node status.");
+
+ // The /nodes/{node}/status response does not include the node name; populate it.
+ if (string.IsNullOrEmpty(status.Name))
+ status.Name = Node;
+
+ WriteObject(status);
+ }
+ }
+}
diff --git a/src/PSProxmoxVE/Cmdlets/PveCmdletBase.cs b/src/PSProxmoxVE/Cmdlets/PveCmdletBase.cs
new file mode 100644
index 0000000..f0f051d
--- /dev/null
+++ b/src/PSProxmoxVE/Cmdlets/PveCmdletBase.cs
@@ -0,0 +1,39 @@
+using System.Management.Automation;
+using PSProxmoxVE.Core.Authentication;
+using PSProxmoxVE.Core.Exceptions;
+
+namespace PSProxmoxVE.Cmdlets
+{
+ ///
+ /// Base class for all PSProxmoxVE cmdlets. Provides optional -Session parameter
+ /// and a helper method to resolve and validate the active session.
+ ///
+ public abstract class PveCmdletBase : PSCmdlet
+ {
+ ///
+ /// An explicit PveSession to use for this cmdlet invocation.
+ /// When omitted, the module-level session stored by Connect-PveServer is used.
+ ///
+ [Parameter(Mandatory = false)]
+ public PveSession? Session { get; set; }
+
+ ///
+ /// Returns the session to use for this cmdlet.
+ /// Resolution order: -Session parameter → ModuleState.ActiveSession.
+ /// Throws if no session is available,
+ /// or if the session ticket has expired.
+ ///
+ protected PveSession GetSession()
+ {
+ var session = Session ?? ModuleState.ActiveSession;
+
+ if (session is null)
+ throw new PveNotConnectedException();
+
+ if (session.IsExpired)
+ throw new PveSessionExpiredException();
+
+ return session;
+ }
+ }
+}
diff --git a/src/PSProxmoxVE/ModuleState.cs b/src/PSProxmoxVE/ModuleState.cs
new file mode 100644
index 0000000..6c5700d
--- /dev/null
+++ b/src/PSProxmoxVE/ModuleState.cs
@@ -0,0 +1,10 @@
+using PSProxmoxVE.Core.Authentication;
+
+namespace PSProxmoxVE
+{
+ /// Module-scoped state container for the active PVE session
+ internal static class ModuleState
+ {
+ internal static PveSession? ActiveSession { get; set; }
+ }
+}