Corsinvest.ProxmoxVE.Api.psm1

# SPDX-FileCopyrightText: Copyright Corsinvest Srl
# SPDX-License-Identifier: MIT

#Requires -Version 6.0

class PveValidateVmId : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues() { return Get-PveVm | Select-Object -ExpandProperty vmid }
}

class PveValidateVmName : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        return Get-PveVm | Where-Object { $_.status -ne 'unknown' } | Select-Object -ExpandProperty name
    }
}

class PveValidateNode : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues() { return Get-PveNodes | Select-Object -ExpandProperty node }
}

class PveTicket {
    [string] $HostName = ''
    [int] $Port = 8006
    [bool] $SkipCertificateCheck = $true
    [string] $Ticket = ''
    [string] $CSRFPreventionToken = ''
    [string] $ApiToken = ''
}

class PveResponse {
    #Contain real response of Proxmox VE
    #Is converted in object Json response
    [PSCustomObject] $Response
    [int] $StatusCode = 200
    [string] $ReasonPhrase
    [bool] $IsSuccessStatusCode = $true
    [string] $RequestResource
    [hashtable] $Parameters
    [string] $Method
    [string] $ResponseType

    [bool] ResponseInError() { return $null -ne $this.Response.error }
    [PSCustomObject] ToTable() { return $this.Response.data | Format-Table -Property * }
    [PSCustomObject] ToData() { return $this.Response.data }
    [void] ToCsv([string] $filename) { $this.Response.data | Export-Csv $filename }
    [void] ToGridView() { $this.Response.data | Out-GridView -Title "View Result Data" }
}

$Global:PveTicketLast = $null

##########
## CORE ##
##########
#region Core

function Test-PortQuick {
    param (
        [string]$HostName,
        [int]$Port,
        [int]$Timeout = 5000 # Timeout in millisecondi
    )

    $ret = $false
    try {
        $tcpClient = New-Object System.Net.Sockets.TcpClient
        $tcpClient.ReceiveTimeout = $Timeout
        $tcpClient.SendTimeout = $Timeout

        $connection = $tcpClient.BeginConnect($HostName, $Port, $null, $null)
        if ($connection.AsyncWaitHandle.WaitOne($Timeout, $false)) {
            $tcpClient.EndConnect($connection)
            $ret = $true
        }
        else {
            #Write-Host "Timeout for ${HostName}:${Port}."
        }
    }
    catch {
        Write-Debug "Port $Port on $HostName It is NOT reachable."
    }
    finally {
        $tcpClient.Close() | Out-Null
    }

    return $ret
}

function Connect-PveCluster {
    <#
.DESCRIPTION
Connect to Proxmox VE Cluster.
.PARAMETER HostsAndPorts
Host and ports
Format 10.1.1.90:8006,10.1.1.91:8006,10.1.1.92:8006.
.PARAMETER SkipCertificateCheck
Skips certificate validation checks.
.PARAMETER Credentials
Username and password, username formatted as user@pam, user@pve, user@yourdomain or user (default domain pam).
.PARAMETER ApiToken
Api Token format USER@REALM!TOKENID=UUID
.PARAMETER Otp
One-time password for Two-factor authentication.
.PARAMETER SkipRefreshPveTicketLast
Skip refresh PveTicket Last global variable
.EXAMPLE
$PveTicket = Connect-PveCluster -HostsAndPorts 192.168.128.115 -Credentials (Get-Credential -Username 'root').
.OUTPUTS
PveTicket. Return ticket connection.
#>

    [CmdletBinding()]
    [OutputType([PveTicket])]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string[]]$HostsAndPorts,

        [pscredential]$Credentials,

        [string]$ApiToken,

        [string]$Otp,

        [switch]$SkipCertificateCheck,

        [switch]$SkipRefreshPveTicketLast
    )

    process {
        $hostName = '';
        $port = 0;

        #find host and port
        foreach ($hostAndPort in $HostsAndPorts) {
            $data = $hostAndPort.Split(':');
            $hostTmp = $data[0];
            $portTmp = 8006;

            if ($data.Length -eq 2 ) { [int32]::TryParse($data[1] , [ref]$portTmp) | Out-Null }

            if (Test-PortQuick -HostName $hostTmp -Port $portTmp -Timeout 2000) {
                $hostName = $hostTmp;
                $port = $portTmp;
                break;
            }
        }

        if ([string]::IsNullOrWhiteSpace($hostName)) { throw 'Host not valid' }
        if ($port -le 0) { throw 'Port not valid' }

        $pveTicket = [PveTicket]::new()
        $pveTicket.HostName = $hostName
        $pveTicket.Port = $port
        $pveTicket.SkipCertificateCheck = $SkipCertificateCheck
        $pveTicket.ApiToken = $ApiToken

        if (-not $ApiToken)
        {
            if (-not $Credentials) {
                $Credentials = Get-Credential -Message 'Proxmox VE Username and password, username formated as user@pam, user@pve, user@yourdomain or user (default domain pam).'
            }

            #not exists domain set default pam
            $userName = $Credentials.UserName
            if ($userName.IndexOf('@') -lt 0) { $userName += '@pam' }

            $parameters = @{
                username = $userName
                password = $Credentials.GetNetworkCredential().Password
            }

            if($PSBoundParameters['Otp']) { $parameters['otp'] = $Otp }

            $response = Invoke-PveRestApi -PveTicket $pveTicket -Method Create -Resource '/access/ticket' -Parameters $parameters

            #erro response
            if (!$response.IsSuccessStatusCode -or $response.StatusCode -le 0) {
                throw $response.ReasonPhrase
            }

            if ($response.Response.data.NeedTFA){
                throw "Couldn't authenticate user: missing Two Factor Authentication (TFA)"
            }

            $pveTicket.Ticket = $response.Response.data.ticket
            $pveTicket.CSRFPreventionToken = $response.Response.data.CSRFPreventionToken
        }

        #last ticket connection
        if ($null -eq $Global:PveTicketLast -or (-not $SkipRefreshPveTicketLast)) {
            $Global:PveTicketLast = $pveTicket
        }

        return $pveTicket
    }
}

function Invoke-PveRestApi {
    <#
.DESCRIPTION
Invoke Proxmox VE Rest API
.PARAMETER PveTicket
Ticket data
.PARAMETER Resource
Resource Request
.PARAMETER Method
Method request
.PARAMETER ResponseType
Type request
.PARAMETER Parameters
Parameters request
.EXAMPLE
$PveTicket = Connect-PveCluster -HostsAndPorts '192.168.128.115' -Credentials (Get-Credential -Username 'root').
(Invoke-PveRestApi -PveTicket $PveTicket -Method Get -Resource '/version').Resonse.data

data
----
@{version=5.4; release=15; repoid=d0ec33c6; keyboard=it}
.NOTES
This must be used before any other cmdlets are used
.OUTPUTS
Return object request
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory)]
        [string]$Resource,

        [ValidateNotNullOrEmpty()]
        [ValidateSet('Get', 'Set', 'Create', 'Delete')]
        [string]$Method = 'Get',

        [Parameter()]
        [ValidateSet('json', 'extjs', 'html', 'text', 'png','')]
        [string]$ResponseType = 'json',

        [hashtable]$Parameters
    )

    process {
        #use last ticket
        if ($null -eq $PveTicket) {
            if ($null -ne $Global:PveTicketLast) {
                $PveTicket = $Global:PveTicketLast
            } else {
                throw 'No PveTicket - Cluster Connect missing?'
            }
        }

        #web method
        $restMethod = @{
            Get    = 'Get'
            Set    = 'Put'
            Create = 'Post'
            Delete = 'Delete'
        }[$Method]

        $cookie = New-Object System.Net.Cookie -Property @{
            Name   = 'PVEAuthCookie'
            Path   = '/'
            Domain = $PveTicket.HostName
            Value  = $PveTicket.Ticket
        }

        $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
        $session.cookies.add($cookie)

        $query = ''

        $parametersTmp = @{}

        if ($Parameters -and $Parameters.Count -gt 0 )
        {
             $Parameters.keys | ForEach-Object {
                $parametersTmp[$_] = ($Parameters[$_] -is [switch] -or $Parameters[$_] -is [bool]) `
                                         ? $Parameters[$_] ? 1 : 0 `
                                         : $Parameters[$_]
             }
        }

        if ($parametersTmp.Count -gt 0 -and $('Get', 'Delete').IndexOf($restMethod) -ge 0) {
            Write-Debug 'Parameters:'
            $parametersTmp.keys | ForEach-Object { Write-Debug "$_ => $($parametersTmp[$_])" }

            $query = '?' + (($parametersTmp.Keys | ForEach-Object { "$_=$($parametersTmp[$_])" }) -join '&')
        }

        $response = New-Object PveResponse -Property @{
            Method          = $restMethod
            Parameters      = $parametersTmp
            ResponseType    = $ResponseType
            RequestResource = $Resource
        }

        $headers = @{ CSRFPreventionToken = $PveTicket.CSRFPreventionToken }
        if($PveTicket.ApiToken -ne '') { $headers.Authorization = 'PVEAPIToken ' + $PveTicket.ApiToken }

        $url = "https://$($PveTicket.HostName):$($PveTicket.Port)/api2"
        if($ResponseType -ne '') { $url += "/$ResponseType" }
        $url += "$Resource$query"

        $params = @{
            Uri                  = $url
            Method               = $restMethod
            WebSession           = $session
            SkipCertificateCheck = $PveTicket.SkipCertificateCheck
            Headers              = $headers
        }

        Write-Debug ($params | Format-List | Out-String)

        #body parameters
        if ($parametersTmp.Count -gt 0 -and $('Post', 'Put').IndexOf($restMethod) -ge 0) {
            $params['ContentType'] = 'application/json'
            $params['body'] = ($parametersTmp | ConvertTo-Json)
            Write-Debug "Body: $($params.body | Format-Table | Out-String)"
        }

        try {
            Write-Debug "Params: $($params | Format-Table | Out-String)"

            $response.Response = Invoke-RestMethod @params
        }
        catch {
            $response.StatusCode = $_.Exception.Response.StatusCode
            $response.ReasonPhrase = $_.Exception.Response.ReasonPhrase
            $response.IsSuccessStatusCode = $_.Exception.Response.IsSuccessStatusCode
            if ($response.StatusCode -eq 0) {
                $response.ReasonPhrase = $_.Exception.Message
                $response.StatusCode = -1
            }
        }

        Write-Debug "PveRestApi Response: $($response.Response | Format-Table | Out-String)"
        Write-Debug "PveRestApi IsSuccessStatusCode: $($response.IsSuccessStatusCode)"
        Write-Debug "PveRestApi StatusCode: $($response.StatusCode)"
        Write-Debug "PveRestApi ReasonPhrase: $($response.ReasonPhrase)"

        return $response
    }
}
#endregion

#############
## UTILITY ##
#############

#region Utility
#region Convert Time Windows/Unix
function ConvertTo-PveUnixTime {
<#
.SYNOPSIS
Convert datetime objects to UNIX time.
.DESCRIPTION
Convert System.DateTime objects to UNIX time.
.PARAMETER Date
Date time
.OUTPUTS
[Int32]. Return Unix Time.
#>

    [CmdletBinding()]
    [OutputType([long])]
    param (
        [Parameter(Mandatory,Position = 0,ValueFromPipeline )]
        [DateTime]$Date
    )

    process {
        [long] (New-Object -TypeName System.DateTimeOffset -ArgumentList ($Date)).ToUnixTimeSeconds()
    }
}

Function ConvertFrom-PveUnixTime {
    <#
.DESCRIPTION
Convert Unix Time in DateTime
.PARAMETER Time
Unix Time
.OUTPUTS
DateTime. Return DateTime from Unix Time.
#>

    [CmdletBinding()]
    [OutputType([DateTime])]
    param (
        [Parameter(Position = 0, Mandatory)]
        [long] $Time
    )

    return [System.DateTimeOffset]::FromUnixTimeSeconds($Time).DateTime
}
#endregion

Function Invoke-PveSpice {
    <#
.DESCRIPTION
Enter Spice VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.PARAMETER Viewer
Path of Spice remove viewer.
- Linux /usr/bin/remote-viewer
- Windows C:\Program Files\VirtViewer v?.?-???\bin\remote-viewer.exe
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$Viewer
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName | Select-Object -First 1
        if ($vm.type -eq 'qemu') {
            $node = $vm.node
            $vmid = $vm.vmid

            $parameters = @{ proxy = $null -eq $PveTicket ? $PveTicketLast.HostName : $PveTicket.HostName }

            $ret = Invoke-PveRestApi -PveTicket $PveTicket -Method Create -ResponseType '' -Resource "/spiceconfig/nodes/$node/qemu/$vmid/spiceproxy" -Parameters $parameters

            Write-Debug "======================================="
            Write-Debug "SPICE Proxy Configuration"
            Write-Debug "======================================="
            Write-Debug $ret
            Write-Debug "======================================="

            $tmp = New-TemporaryFile
            $ret.Response | Out-File $tmp.FullName

            Start-Process -FilePath $Viewer -Args $tmp.FullName
        }
    }
}

#region Task
function Wait-PveTaskIsFinish {
    <#
.DESCRIPTION
Get task is running.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER Upid
Upid task e.g UPID:pve1:00004A1A:0964214C:5EECEF11:vzdump:134:root@pam:
.PARAMETER Wait
Millisecond wait next check
.PARAMETER Timeout
Millisecond timeout
.OUTPUTS
Bool. $True Return task is done within Timeout, $False if not
#>

    [OutputType([bool])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$Upid,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [int]$Wait = 500,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [int]$Timeout = 10000
    )

    process {
        $isRunning = $true;
        if ($Wait -le 0) { $Wait = 500; }
        if ($Timeout -lt $Wait) { $Timeout = $Wait + 5000; }
        $timeStart = [DateTime]::Now

        while ($isRunning -and ([DateTime]::Now - $timeStart).TotalMilliseconds -lt $Timeout) {
            $isRunning = Get-PveTaskIsRunning -PveTicket $PveTicket -Upid $Upid
            Start-Sleep -Milliseconds $Wait
        }

        #check timeout
        return ([DateTime]::Now - $timeStart).TotalMilliseconds -lt $Timeout
    }
}

function Wait-PveTaskIsFinishedWithProgress {
    <#
.DESCRIPTION
Wait for a task to finish, show Powershell Progressbar while waiting
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER Upid
Upid task e.g UPID:pve1:00004A1A:0964214C:5EECEF11:vzdump:134:root@pam:
.PARAMETER Wait
Millisecond wait next check
.PARAMETER Timeout
Millisecond timeout
.PARAMETER ProgressActivityText
Acitivity (Text) for Write-Progress, defaults to Upid when empty
.PARAMETER ProgressStatusText
Status-Text for Write-Progress, default is "Waiting...", is shown in front of remaining time and percent
.PARAMETER ProgessActivityId
Id for Write-Progress, change when other Write-Progress is already shown
.OUTPUTS
Bool. $True Return task is done within Timeout, $False if not
#>

    [OutputType([bool])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$Upid,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [int]$Wait = 500,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [int]$Timeout = 10000,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$ProgressActivityText,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$ProgressStatusText = "Waiting...",

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [int]$ProgessActivityId = 1
    )

    process {
        $isRunning = $true;
        if ($Wait -le 0) { $Wait = 500; }
        if ($Timeout -lt $Wait) { $Timeout = $Wait + 5000; }
        if ($null -eq $ProgressActivityText -OR $ProgressActivityText -eq "") { $ProgressActivityText = $Upid; }
        $timeStart = [DateTime]::Now
        $waitTimeMs = $timeStart
        $timePercent = 0

        while ($isRunning -and ([DateTime]::Now - $timeStart).TotalMilliseconds -lt $Timeout) {
            $waitTimeMs = $([DateTime]::Now - $timeStart).TotalMilliseconds
            $timePercent = $waitTimeMs * (100 / $Timeout)
            Write-Progress -Id $ProgessActivityId -Activity $ProgressActivityText -Status "$($ProgressStatusText) ($([Math]::Round($waitTimeMs/1000))/$([Math]::Round($Timeout/1000)) Seconds)" -PercentComplete $timePercent
            $isRunning = Get-PveTaskIsRunning -PveTicket $PveTicket -Upid $Upid
            Start-Sleep -Milliseconds $Wait
        }

        # end Write-Progress
        Write-Progress -Id $ProgessActivityId -Activity $ProgressActivityText -Completed

        #check timeout
        return ([DateTime]::Now - $timeStart).TotalMilliseconds -lt $Timeout
    }
}

function Get-PveTaskIsRunning {
    <#
.DESCRIPTION
Get task is running.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER Upid
Upid task e.g UPID:pve1:00004A1A:0964214C:5EECEF11:vzdump:134:root@pam:
.OUTPUTS
Bool. Return tas is running.
#>

    [OutputType([bool])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$Upid
    )

    process {
        return (Get-PveNodesTasksStatus -PveTicket $PveTicket -Node $Upid.Split(':')[1] -Upid $Upid).Response.data.status -eq 'running'
    }
}
#endregion

# function Get-PveStorage {
# <#
# .DESCRIPTION
# Get nodes
# .PARAMETER PveTicket
# Ticket data connection.
# .PARAMETER Storage
# The Name of the storage.
# .OUTPUTS
# PSCustomObject. Return Vm Data.
# #>
# [OutputType([PSCustomObject])]
# [CmdletBinding()]
# Param(
# [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
# [PveTicket]$PveTicket,

# [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
# [string]$Storage
# )

# process {
# return $null -eq $Storage

# $data = (Get-PveClusterResources -PveTicket $PveTicket -Type storage).Response.data
# return $null -eq $Storage ?
# $data :
# $data | Where-Object { $_.storage -like $Storage }
# }
# }

function Get-PveNode {
    <#
.DESCRIPTION
Get nodes
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER Node
The Name of the node.
.OUTPUTS
PSCustomObject. Return Node/s data.
#>

    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$Node
    )

    process {
        $data = (Get-PveClusterResources -PveTicket $PveTicket -Type node).Response.data
        if($PSBoundParameters['Node'])
        {
            return $data | Where-Object { $_.node -like $Node }
        }
        else
        {
            return $data
        }
    }
}

function Get-PveVm {
    <#
.DESCRIPTION
Get VMs/CTs from id or name.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The id or name VM/CT comma separated (eg. 100,101,102,TestDebian)
-vmid or -name exclude (e.g. -200,-TestUbuntu)
range 100:107,-105,200:204
'@pool-???' for all VM/CT in specific pool (e.g. @pool-customer1),
'@tag-???' for all VM/CT in specific tags (e.g. @tag-customerA),
'@node-???' for all VM/CT in specific node (e.g. @node-pve1, @node-\$(hostname)),
'@all-???' for all VM/CT in specific host (e.g. @all-pve1, @all-\$(hostname)),
'@all' for all VM/CT in cluster";
.OUTPUTS
PSCustomObject. Return Vm/s data.
#>

    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$VmIdOrName
    )

    process {
        $data = (Get-PveClusterResources -PveTicket $PveTicket -Type vm).Response.data
        if ($PSBoundParameters['VmIdOrName'])
        {
            return $data | Where-Object { VmCheckIdOrName -Vm $_ -VmIdOrName $VmIdOrName }
        }
        else
        {
            return $data
        }
    }
}

function IsNumeric([string]$x) {
    return $null -ne ($x -as [double])
}

function VmCheckIdOrName
{
    [OutputType([bool])]
    Param(
        [PSCustomObject]$Vm,

        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName
    )

    if($VmIdOrName -eq 'all') { return $true }

    foreach ($item in $VmIdOrName.Split(","))
    {
        If($item -like '*:*')
        {
            #range number
            $range = $item.Split(":");
            if(($range.Length -eq 2) -and (IsNumeric($range[0])) -and (IsNumeric($range[1])))
            {
                if (($vm.vmid -ge $range[0]) -and ($vm.vmid -le $range[1])) {
                    return $true
                }
            }
        }
        ElseIf((IsNumeric($item)))
        {
            if($vm.vmid -eq $item) { return $true }
        }
        Elseif($item.IndexOf("all-") -eq 0 -and $item.Substring(4) -eq $vm.node)
        {
            #all vm in node
            return $true
        }
        Elseif($item.IndexOf("@all-") -eq 0 -and $item.Substring(5) -eq $vm.node)
        {
            #all vm in node
            return $true
        }
        Elseif($item.IndexOf("@node-") -eq 0 -and $item.Substring(6) -eq $vm.node)
        {
            #all vm in node
            return $true
        }
        Elseif($item.IndexOf("@pool-") -eq 0 -and $item.Substring(6) -eq $vm.pool)
        {
            #all vm in pool
            return $true
        }
        Elseif($item.IndexOf("@tag-") -eq 0 -and ($vm.tags + "").Split(",").Contains($item.Substring(5)))
        {
            #all vm in tag
            return $true
        }
        ElseIf($vm.name -like $item) {
            #name
            return $true
        }
    }

    return $false
}

function Unlock-PveVm {
    <#
.DESCRIPTION
Unlock VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return Set-PveNodesQemuConfig -PveTicket $PveTicket -node $vm.node -Vmid $vm.vmid -Delete 'lock' -Skiplock:$true }
        ElseIf ($vm.type -eq 'lxc') { return Set-PveNodesLxcConfig -PveTicket $PveTicket -node $vm.node -Vmid $vm.vmid -Delete 'lock' }
    }
}

#region VM status
function Start-PveVm {
    <#
.DESCRIPTION
Start VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return $vm | New-PveNodesQemuStatusStart -PveTicket $PveTicket }
        ElseIf ($vm.type -eq 'lxc') { return $vm | New-PveNodesLxcStatusStart -PveTicket $PveTicket }
    }
}

function Stop-PveVm {
    <#
.DESCRIPTION
Stop VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return $vm | New-PveNodesQemuStatusStop -PveTicket $PveTicket }
        ElseIf ($vm.type -eq 'lxc') { return $vm | New-PveNodesLxcStatusStop -PveTicket $PveTicket }
    }
}

function Suspend-PveVm {
    <#
.DESCRIPTION
Suspend VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return $vm | New-PveNodesQemuStatusSuspend -PveTicket $PveTicket }
        ElseIf ($vm.type -eq 'lxc') { return $vm | New-PveNodesLxcStatusSuspend -PveTicket $PveTicket }
    }
}

function Resume-PveVm {
    <#
.DESCRIPTION
Resume VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return $vm | New-PveNodesQemuStatusResume -PveTicket $PveTicket }
        ElseIf ($vm.type -eq 'lxc') { return $vm | New-PveNodesLxcStatusResume -PveTicket $PveTicket }
    }
}

function Reset-PveVm {
    <#
.DESCRIPTION
Reset VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return $vm | New-PveNodesQemuStatusReset -PveTicket $PveTicket }
        ElseIf ($vm.type -eq 'lxc') { throw "Lxc not implement reset!" }
    }
}
#endregion

#region Snapshot
function Get-PveVmSnapshot {
    <#
.DESCRIPTION
Get snapshots VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return $vm | Get-PveNodesQemuSnapshot -PveTicket $PveTicket }
        ElseIf ($vm.type -eq 'lxc') { return $vm | Get-PveNodesLxcSnapshot -PveTicket $PveTicket }
    }
}

function New-PveVmSnapshot {
    <#
.DESCRIPTION
Create snapshot VM.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.PARAMETER Snapname
The name of the snapshot.
.PARAMETER Description
A textual description or comment.
.PARAMETER Vmstate
Save the vmstate
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$Description,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$Snapname,

        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [switch]$Vmstate = $false
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu')
        {
            if ($Vmstate) {
                return $vm | New-PveNodesQemuSnapshot -PveTicket $PveTicket -Snapname $Snapname -Description $Description -Vmstate
            }
            else
            {
                return $vm | New-PveNodesQemuSnapshot -PveTicket $PveTicket -Snapname $Snapname -Description $Description
            }
        }
        ElseIf ($vm.type -eq 'lxc')
        {
            return $vm | New-PveNodesLxcSnapshot -PveTicket $PveTicket -Snapname $Snapname -Description $Description
        }
    }
}

function Remove-PveVmSnapshot {
    <#
.DESCRIPTION
Delete a VM snapshot.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.PARAMETER Snapname
The name of the snapshot.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$Snapname
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return $vm | Remove-PveNodesQemuSnapshot -PveTicket $PveTicket -Snapname $Snapname }
        ElseIf ($vm.type -eq 'lxc') { return $vm | Remove-PveNodesLxcSnapshot -PveTicket $PveTicket -Snapname $Snapname }
    }
}

function Undo-PveVmSnapshot {
    <#
.DESCRIPTION
Rollback VM state to specified snapshot.
.PARAMETER PveTicket
Ticket data connection.
.PARAMETER VmIdOrName
The (unique) ID or Name of the VM.
.PARAMETER Snapname
The name of the snapshot.
.OUTPUTS
PveResponse. Return response.
#>

    [OutputType([PveResponse])]
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PveTicket]$PveTicket,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$VmIdOrName,

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$Snapname
    )

    process {
        $vm = Get-PveVm -PveTicket $PveTicket -VmIdOrName $VmIdOrName
        if ($vm.type -eq 'qemu') { return $vm | New-PveNodesQemuSnapshotRollback -PveTicket $PveTicket -Snapname $Snapname }
        ElseIf ($vm.type -eq 'lxc') { return $vm | New-PveNodesLxcSnapshotRollback -PveTicket $PveTicket -Snapname $Snapname }
    }
}
#endregion
#endregion

###########
## ALIAS ##
###########

Set-Alias -Name Show-PveSpice -Value Invoke-PveSpice -PassThru
Set-Alias -Name Get-PveTasksStatus -Value Get-PveNodesTasksStatus -PassThru

#MONITORING
Set-Alias -Name Get-PveQemuMonitoring -Value Get-PveNodesQemuRrddata -PassThru
Set-Alias -Name Get-PveQemuMonitoring -Value Get-PveNodesQemuRrddata -PassThru
Set-Alias -Name Get-PveLxcMonitoring -Value Get-PveNodesLxcRrddata -PassThru

#QEMU

## status
Set-Alias -Name Start-PveQemu -Value New-PveNodesQemuStatusStart -PassThru
Set-Alias -Name Stop-PveQemu -Value New-PveNodesQeumStatusStop -PassThru
Set-Alias -Name Suspend-PveQemu -Value New-PveNodesQemuStatusSuspend -PassThru
Set-Alias -Name Resume-PveQemu -Value New-PveNodesQemuStatusResume -PassThru
Set-Alias -Name Reset-PveQemu -Value New-PveNodesQemuStatusReset -PassThru
Set-Alias -Name Restart-PveQemu -Value New-PveNodesQemuStatusReboot -PassThru
Set-Alias -Name Shutdown-PveQemu -Value New-PveNodesQemuStatusShutdown -PassThru

## snapshot
Set-Alias -Name Create-PveQemuSnapshot -Value New-PveNodesQemuSnapshot -PassThru
Set-Alias -Name Remove-PveQemuSnapshot -Value Remove-PveNodesQemuSnapshot -PassThru
Set-Alias -Name Undo-PveQemuSnapshot -Value New-PveNodesQemuSnapshotRollback -PassThru
Set-Alias  -Name Get-PveQemuSnapshot -Value Get-PveNodesQemuSnapshot -PassThru
Set-Alias -Name Get-PveQemuSnapshotConfig -Value Get-PveNodesQemuSnapshotConfig -PassThru
Set-Alias -Name Set-PveQemuSnapshot -Value Set-PveNodesQemuSnapshotConfig -PassThru

## misc
Set-Alias -Name Move-PveQemu -Value New-PveNodesQemuMigrate -PassThru
Set-Alias -Name Copy-PveQemu -Value New-PveNodesQemuClone -PassThru
Set-Alias -Name New-PveQemu -Value New-PveNodesQemu -PassThru
Set-Alias -Name Remove-PveQemu -Value Remove-PveNodesQemu -PassThru
Set-Alias -Name Set-PveQemuConfig -Value Set-PveNodesQemuConfig -PassThru
Set-Alias -Name Get-PveQemuConfig -Value Get-PveNodesQemuConfig -PassThru

#LXC
## status
Set-Alias -Name Start-PveLxc -Value New-PveNodesLxcStatusStart -PassThru
Set-Alias -Name Stop-PveLxc -Value New-PveNodesLxcStatusStop -PassThru
Set-Alias -Name Suspend-PveLxc -Value New-PveNodesLxcStatusSuspend -PassThru
Set-Alias -Name Resume-PveLxc -Value New-PveNodesLxcStatusResume -PassThru
Set-Alias -Name Restart-PveLxc -Value New-PveNodesLxcStatusReboot -PassThru
Set-Alias -Name Shutdown-PveLxc -Value New-PveNodesLxcStatusShutdown

## snapshot
Set-Alias -Name Create-PveLxcSnapshot -Value New-PveNodesLxcSnapshot -PassThru
Set-Alias -Name Remove-PveLxcSnapshot -Value Remove-PveNodesLxcSnapshot -PassThru
Set-Alias -Name Undo-PveLxcSnapshot -Value New-PveNodesLxcSnapshotRollback -PassThru
Set-Alias  -Name Get-PveLxcSnapshot -Value Get-PveNodesLxcSnapshot -PassThru
Set-Alias -Name Get-PveLxcSnapshotConfig -Value Get-PveNodesLxcSnapshotConfig -PassThru
Set-Alias -Name Set-PveLxcSnapshot -Value Set-PveNodesLxcSnapshotConfig -PassThru

## misc
Set-Alias -Name Move-PveLxc -Value New-PveNodesLxcMigrate -PassThru
Set-Alias -Name Copy-PveLxc -Value New-PveNodesLxcClone -PassThru
Set-Alias -Name New-PveLxc -Value New-PveNodesLxc -PassThru
Set-Alias -Name Remove-PveLxc -Value Remove-PveNodesLxc -PassThru
Set-Alias -Name Set-PveLxcConfig -Value Set-PveNodesLxcConfig -PassThru
Set-Alias -Name Get-PveLxcConfig -Value Get-PveNodesLxcConfig -PassThru

#NODE
Set-Alias -Name Update-PveNode -Value New-PveNodesAptUpdate -PassThru
Set-Alias -Name Backup-PveVzdump -Value New-PveNodesVzdump -PassThru
#Set-Alias -Name Stop-PveNode -Value New-PveNodesStatus -Command 'shutdown' -PassThru

#######################
## API AUTOGENERATED ##
#######################
# Load autogenerated API functions (dot-sourced into this module scope)
. "$PSScriptRoot\Corsinvest.ProxmoxVE.Api.Generated.ps1"