Public/Connect-PVE.ps1
|
function Connect-PVE { <# .SYNOPSIS Opens a connection to a Proxmox VE server or cluster node. .DESCRIPTION Authenticates, verifies the connection by reading /version, and returns a PVEPS.Connection object. Unless -NotDefault is given, the new connection becomes the default used by every cmdlet that is not passed -Server. Credentials can be given as -User and -Password, or as a PSCredential with -Credential. If -Password is left out, it is prompted for without echoing. By default the credentials are a user name and password (ticket authentication). The ticket is renewed automatically before it expires, and the credential is held in memory for the life of the connection so the module can log in again if renewal fails. Users with two-factor authentication enabled cannot use ticket authentication; use an API token. With -Token, the same inputs are an API token instead: the user name is the full token ID (user@realm!tokenname) and the password is the token secret. .PARAMETER Server Host name or IP address (IPv4 or IPv6) of the PVE node. Do not include https:// or a port. .PARAMETER Port API port. Defaults to 8006. .PARAMETER User User name with realm (for example root@pam or admin@pve), or with -Token the full token ID (for example automation@pve!pveps). .PARAMETER Password Password, or with -Token the token secret. Accepts a string or a SecureString. Prompted for if omitted. A plain string typed on the command line can end up in shell history; prefer the prompt or a SecureString in interactive use. .PARAMETER Credential PSCredential holding the user name and password, or with -Token the token ID and secret. .PARAMETER Token Treat the user name and password as an API token ID and secret instead of a login. .PARAMETER SkipCertificateCheck Do not validate the server's TLS certificate, for this connection only. Needed for the self-signed certificate a fresh PVE install uses. .PARAMETER NotDefault Do not make this connection the default. .EXAMPLE Connect-PVE -Server pve01.example.com -User root@pam Prompts for the password. .EXAMPLE Connect-PVE -Server pve01.example.com -User root@pam -Password 'secret' -SkipCertificateCheck .EXAMPLE Connect-PVE -Server 192.0.2.10 -User 'automation@pve!pveps' -Password $tokenSecret -Token .EXAMPLE Connect-PVE -Server pve01.example.com -Credential (Get-Credential root@pam) #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'Password', Justification = 'Deliberately accepts a string or a SecureString; converted to SecureString immediately.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '', Justification = 'User and password parameters requested as an alternative to -Credential.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'Converts a caller-supplied plain-string -Password into a SecureString at the earliest point.')] [CmdletBinding(DefaultParameterSetName = 'UserPassword')] [OutputType('PVEPS.Connection')] param( [Parameter(Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [string]$Server, [ValidateRange(1, 65535)] [int]$Port = 8006, [Parameter(Mandatory, ParameterSetName = 'UserPassword')] [Alias('Username')] [ValidateNotNullOrEmpty()] [string]$User, [Parameter(ParameterSetName = 'UserPassword')] [ValidateScript({ $_ -is [string] -or $_ -is [securestring] }, ErrorMessage = 'Password must be a string or a SecureString.')] [object]$Password, [Parameter(Mandatory, ParameterSetName = 'Credential')] [pscredential] [System.Management.Automation.Credential()] $Credential, [switch]$Token, [switch]$SkipCertificateCheck, [switch]$NotDefault ) if ($Server -match '^[a-z]+://' -or $Server -match '/') { throw "Server must be a host name or IP address only, not a URL: '$Server'. Use -Port for a non-default port." } $hostPart = $Server if ($Server.Contains(':') -and -not $Server.StartsWith('[')) { # Bare IPv6 address. $hostPart = "[$Server]" } if ($PSCmdlet.ParameterSetName -eq 'UserPassword') { if ($null -eq $Password) { $prompt = if ($Token) { "Token secret for $User" } else { "Password for $User" } $Password = Read-Host -Prompt $prompt -AsSecureString } if ($Password -is [string]) { if ($Password.Length -eq 0) { throw 'Password is empty.' } $Password = ConvertTo-SecureString -String $Password -AsPlainText -Force } $Credential = [pscredential]::new($User, $Password) } if ((ConvertFrom-PVESecureString -SecureString $Credential.Password).Length -eq 0) { throw $(if ($Token) { 'Token secret is empty.' } else { 'Password is empty.' }) } if (-not $Token) { if ($Credential.UserName.Contains('!')) { throw "'$($Credential.UserName)' looks like an API token ID. Add -Token to authenticate with a token." } if ($Credential.UserName -notmatch '^[^@\s]+@[^@\s]+$') { throw "User name must include the realm, for example root@pam or admin@pve (got '$($Credential.UserName)'). For an API token, add -Token." } $state = @{ AuthType = 'Ticket' Credential = $Credential Ticket = $null CsrfToken = $null TicketTime = [datetime]::MinValue } } else { if ($Credential.UserName -notmatch '^[^@\s]+@[^@!\s]+![A-Za-z][A-Za-z0-9.\-_]+$') { throw "With -Token, the user name must be the full token ID in the form user@realm!tokenname (got '$($Credential.UserName)')." } $state = @{ AuthType = 'ApiToken' TokenHeader = 'PVEAPIToken={0}={1}' -f $Credential.UserName, (ConvertFrom-PVESecureString -SecureString $Credential.Password) } } $user = $Credential.UserName $connection = [pscustomobject]@{ PSTypeName = 'PVEPS.Connection' Id = [guid]::NewGuid().ToString() Server = $Server Port = $Port BaseUri = 'https://{0}:{1}' -f $hostPart, $Port User = $user AuthType = $state.AuthType Version = $null Release = $null SkipCertificateCheck = [bool]$SkipCertificateCheck ConnectedAt = Get-Date IsConnected = $false } $script:PVEAuthState[$connection.Id] = $state try { if ($state.AuthType -eq 'Ticket') { Write-Verbose "Requesting ticket from $($connection.BaseUri) for $user" Update-PVEAuthTicket -Connection $connection -ForceLogin } $version = Invoke-PVERequest -Connection $connection -Path '/version' -NoRetry } catch { $script:PVEAuthState.Remove($connection.Id) $PSCmdlet.ThrowTerminatingError($_) } $connection.Version = [string](Get-PVEValue $version 'version') $connection.Release = [string](Get-PVEValue $version 'release') $connection.IsConnected = $true $script:PVEConnections.Add($connection) if (-not $NotDefault) { $script:DefaultPVEConnection = $connection } $connection } |